# Let's Encrypt (ACME) certificate issuance, per-host HTTP-01. # # Termination & deployment scheme # ------------------------------- # Each NixOS host terminates TLS itself for the subdomains it serves. # There is no central certificate store and no distribution step. # # 1. A host declares which subdomains it serves via `domains = [ ... ]` # in flake.nix (consumed by modules/nginx.nix). # 2. With `infra.acme.enable = true`, the nginx module sets # `enableACME = true; forceSSL = true;` on every served vhost. # 3. security.acme requests one cert per vhost via the HTTP-01 # challenge; nginx answers /.well-known/acme-challenge/ on :80. # 4. On issue/renewal, nginx is reloaded automatically # (`reloadServices`), so the new certificate goes live at once. # 5. Renewals run on a systemd timer; every host (core, deploy, ...) # keeps its own certificates fresh — nothing is deployed by hand. # # Adding a new endpoint (e.g. deploy.domain.com): # - add the host (or add the subdomain to an existing host's `domains`), # - enable `infra.acme` on that host, # - point the subdomain's DNS at the host and leave :80 reachable so # the HTTP-01 challenge can complete. { config, lib, pkgs, ... }: with lib; let cfg = config.infra.acme; in { options.infra.acme = { enable = mkEnableOption "Let's Encrypt (ACME) TLS certificates"; email = mkOption { type = types.str; description = "Account email used for Let's Encrypt"; }; staging = mkOption { type = types.bool; default = false; description = '' Use the Let's Encrypt staging environment (untrusted certificates, no rate limiting) for testing. ''; }; }; config = mkIf cfg.enable { security.acme = { acceptTerms = true; defaults = { inherit (cfg) email; reloadServices = [ "nginx.service" ]; } // optionalAttrs cfg.staging { server = "https://acme-staging-v02.api.letsencrypt.org/directory"; }; }; }; }