{ nixosConfigurations, pkgs, lib, config, ... }: let inherit (lib) types; cfg = config.zpha.services.knotACME; in { options.zpha.services.knotACME = { enable = lib.mkEnableOption ""; nameServers = lib.mkOption { type = types.listOf types.str; }; zone = lib.mkOption { type = types.str; }; zones = lib.mkOption { type = types.listOf types.str; }; keyFile = lib.mkOption { type = types.str; }; }; config = lib.mkIf cfg.enable ( let generateACMERecord = recordName: ((builtins.hashString "sha1" recordName) + ".${cfg.zone}."); machinesWithACMERecords = lib.flip lib.filterAttrs nixosConfigurations ( _machineName: machineConfig: machineConfig.config.security.acme.certs != { } ); getAllDomainsPerMachine = machineName: (lib.pipe nixosConfigurations."${machineName}".config.security.acme.certs [ (lib.mapAttrsToList (domain: cfg: [ domain ] ++ cfg.extraDomainNames)) lib.flatten ]); getACMERecordsPerMachine = machineName: lib.pipe machineName [ getAllDomainsPerMachine (builtins.map (recordName: (generateACMERecord recordName))) ]; generateACMERecordsPerZone = zoneName: (lib.pipe machinesWithACMERecords [ (lib.mapAttrsToList (hostName: _: (getAllDomainsPerMachine 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 = lib.pipe 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 = pkgs.dnsNix.types.zoneToString cfg.zone ( pkgs.dnsNix.evalZone cfg.zone { NS = cfg.nameServers; SOA = { nameServer = lib.elemAt cfg.nameServers 0; adminEmail = "dns@${cfg.zone}"; # Email address with a real `@`! serial = 0; }; } ); }; in { reloadTriggers = [ "${config.zpha.services.knot.dataDir}/acme.zone" ]; serviceConfig = { ExecStartPre = [ (pkgs.writeShellScript "knot-acmeZone-preStart" '' set -eou pipefail cp --dereference ${acmeZoneFile} ${config.zpha.services.knot.dataDir}/acme.zone chmod -R 770 ${config.zpha.services.knot.dataDir}/acme.zone '') ]; ExecReload = lib.mkForce ( pkgs.writeShellScript "knot-reload" '' set -eou pipefail cp --dereference ${acmeZoneFile} ${config.zpha.services.knot.dataDir}/acme.zone chmod -R 770 ${config.zpha.services.knot.dataDir}/acme.zone ${config.services.knot.package}/bin/knotc reload '' ); }; }; zpha.services.knot = { keyFiles = [ cfg.keyFile ]; zones = { "${cfg.zone}" = { file = "${config.zpha.services.knot.dataDir}/acme.zone"; zonefile-sync = 0; zonefile-load = "difference-no-serial"; journal-content = "all"; acl = lib.mkIf ((lib.attrNames machinesWithACMERecords) != [ ]) ( lib.mapAttrsToList (hostName: _: "acme-nix-${hostName}") machinesWithACMERecords ); }; }; extraACL = lib.mapAttrs' (hostName: _: { name = "acme-nix-${hostName}"; value = { key = [ "acme-nix-${hostName}" ]; action = "update"; update-owner = "name"; update-owner-match = "equal"; update-owner-name = getACMERecordsPerMachine hostName; }; }) machinesWithACMERecords; }; } ); }