{ inputs, povSelf, pkgs, lib, config, dnsNix, ... }: let inherit (lib) types; cfg = lib.getAttrFromPath povSelf config; in { options = { enable = { type = types.bool; default = false; }; nameServers = { type = types.listOf types.str; }; zone = { type = types.str; }; zones = { type = types.listOf types.str; }; keyFile = { type = types.str; }; }; config = lib.mkIf cfg.enable ( let generateACMERecord = recordName: ((builtins.hashString "sha1" recordName) + ".${cfg.zone}."); nodesWithACMERecords = ( inputs.self.zaphyraHosts |> lib.filterAttrs (hostName: nodeCfg: nodeCfg.config.security.acme.certs != { }) ); getAllDomainsPerNode = hostName: ( inputs.self.nixosConfigurations.${hostName}.config.security.acme.certs |> lib.mapAttrsToList (domain: cfg: [ domain ] ++ cfg.extraDomainNames) |> lib.flatten ); getACMERecordsPerNode = hostName: (hostName |> getAllDomainsPerNode |> builtins.map (recordName: (generateACMERecord recordName))); generateACMERecordsPerZone = zoneName: ( nodesWithACMERecords |> lib.mapAttrsToList (hostName: _: (getAllDomainsPerNode hostName)) |> lib.flatten |> builtins.filter (lib.hasSuffix zoneName) |> builtins.map (recordName: { name = "_acme-challenge${ if zoneName != recordName then "." else "" }${lib.removeSuffix "${if zoneName != recordName then "." else ""}${zoneName}" recordName}"; value = { CNAME = [ (generateACMERecord recordName) ]; }; }) |> builtins.listToAttrs ); in { dns.allZones = ( cfg.zones |> lib.map ( element: lib.nameValuePair element { subdomains = generateACMERecordsPerZone element; } ) |> lib.listToAttrs ); systemd.services.knot = let acmeZoneFile = pkgs.writeTextFile { name = "${cfg.zone}.zone"; text = dnsNix.types.zoneToString cfg.zone ( dnsNix.evalZone cfg.zone ( with dnsNix.combinators; { NS = cfg.nameServers; SOA = { nameServer = lib.elemAt cfg.nameServers 0; adminEmail = "dns@${cfg.zone}"; # Email address with a real `@`! serial = 0; }; } ) ); }; in { reloadTriggers = [ "${config.modules.services.knot.dataDir}/acme.zone" ]; serviceConfig = { ExecStartPre = [ (pkgs.writeShellScript "knot-acmeZone-preStart" '' set -eou pipefail cp --dereference ${acmeZoneFile} ${config.modules.services.knot.dataDir}/acme.zone chmod -R 770 ${config.modules.services.knot.dataDir}/acme.zone '') ]; ExecReload = lib.mkForce ( pkgs.writeShellScript "knot-reload" '' set -eou pipefail cp --dereference ${acmeZoneFile} ${config.modules.services.knot.dataDir}/acme.zone chmod -R 770 ${config.modules.services.knot.dataDir}/acme.zone ${config.services.knot.package}/bin/knotc reload '' ); }; }; modules.services.knot = { keyFiles = [ cfg.keyFile ]; zones = { "${cfg.zone}" = { file = "${config.modules.services.knot.dataDir}/acme.zone"; zonefile-sync = 0; zonefile-load = "difference-no-serial"; journal-content = "all"; acl = lib.mkIf ((lib.attrNames nodesWithACMERecords) != [ ]) ( nodesWithACMERecords |> lib.mapAttrsToList (hostName: _: "acme-nix-${hostName}") ); }; }; extraACL = ( nodesWithACMERecords |> lib.mapAttrs' ( hostName: _: { name = "acme-nix-${hostName}"; value = { key = [ "acme-nix-${hostName}" ]; action = "update"; update-owner = "name"; update-owner-match = "equal"; update-owner-name = getACMERecordsPerNode hostName; }; } ) ); }; } ); }