zaphyra's git: nixfiles

zaphyra and void's nixfiles

commit 738e987d8e52ff9d2818a5bc4b73e5d62296a4c0
parent 09dda84f5e0ed892a25de80eb8d719df12a1df03
Author: Katja (ctucx) <git@ctu.cx>
Date: Wed, 21 May 2025 13:57:59 +0200

config/nixos/modules/services: add `knotACME`
1 file changed, 126 insertions(+), 0 deletions(-)
A
config/nixos/modules/services/knotACME.nix
|
126
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
diff --git a/config/nixos/modules/services/knotACME.nix b/config/nixos/modules/services/knotACME.nix
@@ -0,0 +1,126 @@
+{
+  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.nixosConfigurations
+      |> 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
+    );
+  
+    modules.services.knot = {
+      keyFiles = [ cfg.keyFile ];
+      zones = {
+        "${cfg.zone}" = {
+          file = toString (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;
+              };
+            }));
+          });
+
+          zonefile-sync = -1;
+          zonefile-load = "difference";
+
+          journal-content = "changes";
+
+          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;
+          };
+        })
+      );
+    };
+  });
+
+}