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
+67
View File
@@ -498,6 +498,73 @@ in
);
};
ddns = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Keep DNS records at a provider pointing at the router's uplink
addresses (ddclient). It checks every `interval` and as soon as an
uplink address changes.
'';
};
protocol = mkOption {
type = types.str;
default = "cloudflare";
example = "dyndns2";
description = "ddclient protocol of the provider, see <https://ddclient.net/protocols.html>.";
};
server = mkOption {
type = types.nullOr types.str;
default = null;
example = "dynupdate.no-ip.com";
description = "The provider's update server, for protocols that need one (`dyndns2`).";
};
username = mkOption {
type = types.str;
default = "token";
description = "Login; `token` for a Cloudflare API token.";
};
passwordFile = mkOption {
type = types.nullOr types.str;
default = null;
example = "/var/lib/router/ddns-secret";
description = "File with the password or API token. Never a path in the Nix store.";
};
zone = mkOption {
type = types.nullOr types.str;
default = null;
example = "example.org";
description = "The zone the names are in, for providers that need it (Cloudflare).";
};
domains = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "home.example.org" ];
description = "Names to keep up to date.";
};
ipv4 = mkOption {
type = types.bool;
default = true;
description = "Update A records with the IPv4 address of the uplink.";
};
ipv6 = mkOption {
type = types.bool;
default = false;
description = "Update AAAA records with the IPv6 address of the uplink.";
};
ssl = mkOption {
type = types.bool;
default = true;
description = "Talk to the provider over HTTPS.";
};
interval = mkOption {
type = types.str;
default = "5min";
description = "How often to check, besides when an address changes (systemd time span).";
};
};
managementPorts = mkOption {
type = types.listOf types.port;
default = [ ];