Add DNS accounts, Let's Encrypt, password change, reboot, HTTP redirect
- router.dnsProviders: named accounts at DNS providers, used by dynamic DNS and certificates (ddclient and lego provider lists in pkgs/router-webui/providers.json). Schema 2 migrates router.ddns. - router.acme: a Let's Encrypt certificate for the web UI via security.acme (DNS-01, resolver 1.1.1.1), served and reloaded by the web UI; Certificate page. - Web UI: password change (web UI and root), reboot button, HTTP to HTTPS redirect (router.webui.httpPort), grouped menu. - Latest kernel (linuxPackages_latest); 7.3 and 7.3.0 are the same kernel for the reboot hint. - SPECS.md: wanted later (REST API, GraphQL, MCP). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AeFqV9dZrK6nipHkPfyM4F
This commit is contained in:
@@ -94,6 +94,11 @@ in
|
||||
type = lib.types.port;
|
||||
default = 443;
|
||||
};
|
||||
httpPort = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.port;
|
||||
default = 80;
|
||||
description = "Redirect plain HTTP on this port to HTTPS (to the LANs and tunnels with `allowManagement`); null: do not listen.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -105,7 +110,12 @@ in
|
||||
if [ -e ${pending} ]; then mv ${pending} ${confirmed}; fi
|
||||
'';
|
||||
}
|
||||
(lib.mkIf cfg.webui.enable { managementPorts = [ cfg.webui.port ]; })
|
||||
(lib.mkIf cfg.webui.enable {
|
||||
managementPorts = [
|
||||
cfg.webui.port
|
||||
]
|
||||
++ lib.optional (cfg.webui.httpPort != null) cfg.webui.httpPort;
|
||||
})
|
||||
];
|
||||
|
||||
networking.hostName = system.hostName or "router";
|
||||
@@ -143,6 +153,19 @@ in
|
||||
TimeoutStartSec = "1h";
|
||||
};
|
||||
};
|
||||
# Puts root's password hash (written by the web UI) into /etc/shadow now,
|
||||
# rather than at the next switch; the web UI itself may not touch /etc.
|
||||
systemd.services.router-root-password = lib.mkIf cfg.webui.enable {
|
||||
description = "Apply root's password from the web UI";
|
||||
restartIfChanged = false;
|
||||
stopIfChanged = false;
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = pkgs.writeShellScript "router-root-password" ''
|
||||
exec ${pkgs.shadow}/bin/usermod --password "$(cat ${cfg.stateDirectory}/root-password)" root
|
||||
'';
|
||||
};
|
||||
};
|
||||
systemd.services.router-confirm = {
|
||||
description = "Confirm the active router configuration";
|
||||
restartIfChanged = false;
|
||||
@@ -165,9 +188,16 @@ in
|
||||
pkgs.iproute2
|
||||
pkgs.wireguard-tools
|
||||
pkgs.qrencode
|
||||
pkgs.mkpasswd # the hash of root's password
|
||||
];
|
||||
serviceConfig = {
|
||||
ExecStart = "${lib.getExe webui} -listen :${toString cfg.webui.port} -config ${cfg.directory} -state ${cfg.stateDirectory}";
|
||||
ExecStart = "${lib.getExe webui} -listen :${toString cfg.webui.port}${
|
||||
lib.optionalString (cfg.webui.httpPort != null) " -http :${toString cfg.webui.httpPort}"
|
||||
}${
|
||||
lib.optionalString (
|
||||
config.router.acme.enable && config.router.acme.domains != [ ]
|
||||
) " -acme-dir /var/lib/acme/${lib.head config.router.acme.domains}"
|
||||
} -config ${cfg.directory} -state ${cfg.stateDirectory}";
|
||||
Restart = "on-failure";
|
||||
# It edits settings and secrets and talks to systemd; nothing else.
|
||||
ProtectSystem = "strict";
|
||||
@@ -192,34 +222,36 @@ in
|
||||
|
||||
# The DHCP clients' names in DNS (router.dns.dynamicLeases): reads Kea's
|
||||
# leases and publishes them in the running Unbound.
|
||||
systemd.services.router-leases-dns = lib.mkIf (config.router.enable && config.router.dns.dynamicLeases) {
|
||||
description = "Publish DHCP clients' names in DNS";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [
|
||||
"unbound.service"
|
||||
"kea-dhcp4-server.service"
|
||||
];
|
||||
path = [ config.services.unbound.package ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${lib.getExe webui} leases-dns -leases /var/lib/kea/dhcp4.leases -static /etc/router/dns-static.json -socket ${config.services.unbound.localControlSocketPath} -run /run/router-leases-dns";
|
||||
RuntimeDirectory = "router-leases-dns";
|
||||
Restart = "always";
|
||||
RestartSec = 5;
|
||||
# It reads leases and talks to Unbound's socket; nothing else.
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
PrivateNetwork = true;
|
||||
PrivateDevices = true;
|
||||
NoNewPrivileges = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
RestrictAddressFamilies = [ "AF_UNIX" ];
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
};
|
||||
};
|
||||
systemd.services.router-leases-dns =
|
||||
lib.mkIf (config.router.enable && config.router.dns.dynamicLeases)
|
||||
{
|
||||
description = "Publish DHCP clients' names in DNS";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [
|
||||
"unbound.service"
|
||||
"kea-dhcp4-server.service"
|
||||
];
|
||||
path = [ config.services.unbound.package ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${lib.getExe webui} leases-dns -leases /var/lib/kea/dhcp4.leases -static /etc/router/dns-static.json -socket ${config.services.unbound.localControlSocketPath} -run /run/router-leases-dns";
|
||||
RuntimeDirectory = "router-leases-dns";
|
||||
Restart = "always";
|
||||
RestartSec = 5;
|
||||
# It reads leases and talks to Unbound's socket; nothing else.
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
PrivateNetwork = true;
|
||||
PrivateDevices = true;
|
||||
NoNewPrivileges = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
RestrictAddressFamilies = [ "AF_UNIX" ];
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
};
|
||||
};
|
||||
|
||||
# Rebuilding on the device must work without internet (the uplink may be
|
||||
# what is being configured). What a changed configuration rebuilds is all
|
||||
|
||||
Reference in New Issue
Block a user