{ config, lib, pkgs, ... } @ toplevel: with lib; let domainOpts = { name, ... }: let domain = name; in { options = with types; { email = mkOption { type = str; description = "Domain administrator email."; default = "admin@${domain}"; }; extra-domains = mkOption { type = listOf str; description = "List of domains to add to this certificate."; default = []; }; local-copies = let localCopyOpts = { name, ... }: let copy = name; in { options = with types; let target-path = "/var/run/${domain}/${copy}"; in { user = mkOption { type = str; description = "User to which this copy belongs."; }; group = mkOption { type = nullOr str; description = "Group to which this copy belongs."; default = null; }; service = mkOption { type = str; description = "systemd job to copy certs."; default = "fudo-${domain}-${copy}-certs.service"; }; certificate = mkOption { type = str; description = "Full path to the local copy certificate."; default = "${target-path}/cert.pem"; }; full-certificate = mkOption { type = str; description = "Full path to the local copy certificate."; default = "${target-path}/fullchain.pem"; }; chain = mkOption { type = str; description = "Full path to the local copy certificate."; default = "${target-path}/chain.pem"; }; private-key = mkOption { type = str; description = "Full path to the local copy certificate."; default = "${target-path}/key.pem"; }; }; }; in mkOption { type = attrsOf (submodule localCopyOpts); description = "Map of copies to make for use by services."; default = {}; }; }; }; head-or-null = lst: if (lst == []) then null else head lst; rm-service-ext = filename: head-or-null (builtins.match "^(.+)\.service$" filename); concatMapAttrs = f: attrs: foldr (a: b: a // b) {} (mapAttrsToList f attrs); hostname = config.instance.hostname; cfg = config.fudo.acme; localDomains = if (hasAttr hostname cfg.host-domains) then cfg.host-domains.${hostname} else {}; optionalStringOr = str: default: if (str != null) then str else default; in { options.fudo.acme = with types; { host-domains = mkOption { type = attrsOf (attrsOf (submodule domainOpts)); description = "Map of host to domains to domain options."; default = { }; }; }; config = { security.acme.certs = mapAttrs (domain: domainOpts: { email = domainOpts.email; extraDomainNames = domainOpts.extra-domains; }) localDomains; systemd = { tmpfiles.rules = let copies = concatMapAttrs (domain: domainOpts: domainOpts.local-copies) localDomains; perms = copyOpts: if (copyOpts.group != null) then "0550" else "0500"; copy-paths = mapAttrsToList (copy: copyOpts: let dir-entry = copyOpts: file: "D \"${dirOf file}\" ${perms copyOpts} ${copyOpts.user} ${optionalStringOr copyOpts.group "-"} - -"; in map (dir-entry copyOpts) [ copyOpts.certificate copyOpts.full-certificate copyOpts.chain copyOpts.private-key ]) copies; in unique (concatMap (i: unique i) copy-paths); services = concatMapAttrs (domain: domainOpts: mapAttrs' (copy: copyOpts: let key-perms = copyOpts: if (copyOpts.group != null) then "0440" else "0400"; source = config.security.acme.certs.${domain}.directory; target = copyOpts.path; install-certs = pkgs.writeShellScript "fudo-install-${domain}-${copy}-certs.sh" '' cp cert.pem ${copyOpts.certificate} chmod 0444 ${copyOpts.certificate} cp full.pem ${copyOpts.full-certificate} chmod 0444 ${copyOpts.full-certificate} cp chain.pem ${copyOpts.chain} chmod 0444 ${copyOpts.chain} cp key.pem ${copyOpts.private-key} chmod ${key-perms copyOpts} ${copyOpts.private-key} ''; remove-certs = pkgs.writeShellScript "fudo-remove-${domain}-${copy}-certs.sh" '' rm -f ${copyOpts.private-key} rm -f ${copyOpts.chain} rm -f ${copyOpts.full-certificate} rm -f ${copyOpts.certificate} ''; in nameValuePair (rm-service-ext copyOpts.service) { description = "Copy ${domain} ACME certs for ${copy}."; after = [ "acme-${domain}.service" ]; serviceConfig = { Type = "oneshot"; ExecStart = install-certs; ExecStop = remove-certs; RemainAfterExit = true; StandardOutput = "journal"; }; }) domainOpts.local-copies) localDomains; }; }; }