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:
@@ -0,0 +1,73 @@
|
||||
# A Let's Encrypt certificate for the web UI, proven with a DNS record (DNS-01):
|
||||
# no port needs to be open to the internet, and wildcards work. NixOS's
|
||||
# security.acme (lego) orders and renews it; the web UI picks up the files.
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.router;
|
||||
acme = cfg.acme;
|
||||
|
||||
# Shared with the web UI: the providers lego knows, and those with a single
|
||||
# token (the variable names the file holding it). The others take an
|
||||
# environment file with whatever variables they need.
|
||||
providers = builtins.fromJSON (builtins.readFile ../../pkgs/router-webui/providers.json);
|
||||
tokenVariables = providers.acmeToken;
|
||||
|
||||
prov =
|
||||
cfg.dnsProviders.${if acme.provider == null then "" else acme.provider} or {
|
||||
type = "none";
|
||||
secretFile = null;
|
||||
environmentFile = null;
|
||||
};
|
||||
token = tokenVariables ? ${prov.type};
|
||||
secret = if token then prov.secretFile else prov.environmentFile;
|
||||
has = acme.provider != null && cfg.dnsProviders ? ${acme.provider};
|
||||
name = if acme.domains == [ ] then "unset" else lib.head acme.domains;
|
||||
in
|
||||
{
|
||||
config = lib.mkIf (cfg.enable && acme.enable) {
|
||||
assertions = [
|
||||
{
|
||||
assertion = acme.domains != [ ];
|
||||
message = "router.acme: no domains for the certificate.";
|
||||
}
|
||||
{
|
||||
assertion = has;
|
||||
message = "router.acme.provider must name a connection in router.dnsProviders.";
|
||||
}
|
||||
{
|
||||
assertion = !has || lib.elem prov.type providers.acme;
|
||||
message = "router.acme: lego has no DNS provider ${prov.type}.";
|
||||
}
|
||||
{
|
||||
assertion = !has || !lib.elem prov.type providers.acme || (secret != null && !lib.hasPrefix "/nix/store" secret);
|
||||
message = "router.dnsProviders.${toString acme.provider} needs ${if token then "secretFile" else "environmentFile"} for certificates, a file outside the Nix store (the secret must not be copied into it).";
|
||||
}
|
||||
{
|
||||
assertion = acme.email != null;
|
||||
message = "router.acme.email: Let's Encrypt needs a contact address.";
|
||||
}
|
||||
{
|
||||
assertion = acme.acceptTerms;
|
||||
message = "router.acme.acceptTerms: the Let's Encrypt subscriber agreement must be accepted.";
|
||||
}
|
||||
];
|
||||
|
||||
security.acme = {
|
||||
# Ours is the assertion above.
|
||||
acceptTerms = true;
|
||||
defaults.email = acme.email;
|
||||
certs.${name} = {
|
||||
extraDomainNames = lib.tail acme.domains;
|
||||
dnsProvider = prov.type;
|
||||
inherit (acme) dnsResolver;
|
||||
credentialFiles = lib.optionalAttrs token { ${tokenVariables.${prov.type}} = prov.secretFile; };
|
||||
environmentFile = lib.mkIf (!token) prov.environmentFile;
|
||||
server = lib.mkIf acme.staging "https://acme-staging-v02.api.letsencrypt.org/directory";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user