Add dynamic DNS and show the running kernel on the overview

router.ddns keeps provider records on the uplink's addresses with ddclient,
run by a timer and by a watcher that starts it when an address changes. The
Dynamic DNS page edits it; the secret is a private file on the router. The
overview shows the running kernel and says when the current system boots a
different one. PLAN.md records why the A/B image approach was not pursued.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AeFqV9dZrK6nipHkPfyM4F
This commit is contained in:
2026-10-30 13:35:57 +01:00
co-authored by Claude Sonnet 5.5
parent c198ffbe37
commit a36cebc827
19 changed files with 732 additions and 8 deletions
+44
View File
@@ -310,6 +310,50 @@ let
};
expect = [ "ports are only valid with protocol tcp or udp" ];
};
ddnsValid = {
router = {
lans.main = lan { };
ddns = {
enable = true;
domains = [ "home.example.org" ];
passwordFile = "/var/lib/router/ddns-secret";
};
};
expect = [ ];
};
ddnsSecretInTheStore = {
router = {
lans.main = lan { };
ddns = {
enable = true;
domains = [ "home.example.org" ];
passwordFile = "/nix/store/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-secret";
};
};
expect = [ "outside the Nix store" ];
};
ddnsWithoutNames = {
router = {
lans.main = lan { };
ddns = {
enable = true;
passwordFile = "/var/lib/router/ddns-secret";
};
};
expect = [ "no domains to update" ];
};
ddnsWithoutAnyFamily = {
router = {
lans.main = lan { };
ddns = {
enable = true;
domains = [ "home.example.org" ];
passwordFile = "/var/lib/router/ddns-secret";
ipv4 = false;
};
};
expect = [ "neither IPv4 nor IPv6" ];
};
ruleSourcePortsWithoutProtocol = {
router = {
lans = {