Commit a270c97

HPCesia <me@hpcesia.com>
2026-09-05 08:47:47
headscale: split into per-feature files
Assisted-by: opencode:deepseek-v4-flash
1 parent 5662f1c
Changed files (5)
modules/services/headscale/_lib.nix
@@ -0,0 +1,109 @@
+{lib}: let
+  inherit (import ../direct-dns/_lib.nix {inherit lib;}) mkDirectEntries;
+
+  hexDigitValue = c:
+    lib.lists.findFirstIndex (d: d == c) 0 (lib.stringToCharacters "0123456789abcdef");
+
+  mkDerpRegionId = domain:
+    900
+    + (
+      builtins.foldl' (acc: c: lib.mod (acc * 16 + hexDigitValue c) 99) 0
+      (lib.stringToCharacters (builtins.hashString "sha256" domain))
+    );
+
+  mkDerpNodeLines = r:
+    lib.concatStringsSep "\n" (
+      ["      - name: ${r.h.name}"]
+      ++ ["        regionid: ${toString r.regionId}"]
+      ++ ["        hostname: ${r.dcfg.domain}"]
+      ++ lib.optionals (r.v4 != null) ["        ipv4: \"${r.v4}\""]
+      ++ lib.optionals (r.v6 != null) ["        ipv6: \"${r.v6}\""]
+      ++ ["        stunport: 3478"]
+      ++ ["        derpport: 443"]
+      ++ ["        stunonly: false"]
+    );
+
+  mkDerpRegionBlock = r:
+    lib.concatStringsSep "\n" (
+      ["  ${toString r.regionId}:"]
+      ++ ["    regionid: ${toString r.regionId}"]
+      ++ ["    regioncode: ${r.h.name}"]
+      ++ ["    regionname: ${r.h.name}"]
+      ++ ["    nodes:"]
+      ++ [(mkDerpNodeLines r)]
+    );
+
+  mkTailnetEntries = reverseProxy: let
+    uniqueByDomain = lib.foldl' (acc: e:
+      if builtins.any (x: x.domain == e.domain) acc
+      then acc
+      else acc ++ [e]) [];
+  in
+    uniqueByDomain (
+      lib.concatMap (
+        r:
+          lib.mapAttrsToList (domain: conf: {
+            inherit domain;
+            source = r.source.host;
+          }) (
+            lib.filterAttrs (_: conf: conf.tailscale or false) r.value
+          )
+      )
+      reverseProxy
+    );
+
+  mkDnsRecords = {
+    reverseProxy,
+    tailnetDomain,
+    dnsDomain,
+  }: let
+    tailnetEntries = mkTailnetEntries reverseProxy;
+
+    duplicateDomains =
+      lib.filter (domain: builtins.length (lib.filter (e: e.domain == domain) tailnetEntries) > 1)
+      (lib.unique (map (e: e.domain) tailnetEntries));
+
+    assertNoDuplicates =
+      lib.assertMsg (duplicateDomains == [])
+      "Multiple hosts declare the same tailnet domain: ${lib.concatStringsSep ", " duplicateDomains}";
+
+    assertDomainMatches =
+      lib.assertMsg (dnsDomain == tailnetDomain)
+      ("Headscale MagicDNS base domain '${dnsDomain}' does not match the tailnet domain"
+        + "'${tailnetDomain}' used by the caddy tailnet module (hardcoded in modules/services/caddy/quirks.nix).");
+  in
+    assert assertNoDuplicates;
+    assert assertDomainMatches;
+      lib.concatMap (e: let
+        v4 = e.source.address.ipv4.tailscale;
+        v6 = e.source.address.ipv6.tailscale;
+      in
+        assert lib.assertMsg (v4 != null || v6 != null)
+        ("Tailnet domain '${e.domain}' is declared by host '${e.source.name}' which has no tailscale address configured"
+          + "(see `address` in modules/hosts/schema.nix).");
+          lib.optionals (v4 != null) [
+            {
+              name = e.domain;
+              type = "A";
+              value = v4;
+            }
+          ]
+          ++ lib.optionals (v6 != null) [
+            {
+              name = e.domain;
+              type = "AAAA";
+              value = v6;
+            }
+          ])
+      tailnetEntries;
+in {
+  inherit
+    mkDirectEntries
+    mkDnsRecords
+    ;
+
+  configFilePath = "/var/lib/headscale/config.yaml";
+  dnsRecordsFilePath = "/var/lib/headscale/dns-records.json";
+
+  inherit mkDerpRegionId mkDerpRegionBlock;
+}
modules/services/headscale/default.nix
@@ -0,0 +1,150 @@
+{
+  den,
+  lib,
+  ...
+}: {
+  den.aspects.services.headscale = {
+    includes = [
+      den.aspects.services.headscale.derp
+      den.aspects.services.headscale.dns
+    ];
+
+    settings.host = {
+      domain = lib.mkOption {
+        type = lib.types.nullOr lib.types.str;
+        default = null;
+      };
+      port = lib.mkOption {
+        type = lib.types.port;
+        default = 8080;
+      };
+      derp = {
+        enable = lib.mkEnableOption "Enable Headscale's builtin DERP server";
+        port = lib.mkOption {
+          type = lib.types.port;
+          default = 3478;
+        };
+      };
+      dns = {
+        enable = lib.mkEnableOption "Enable Headscale's Magic DNS";
+        domain = lib.mkOption {
+          type = lib.types.str;
+          default = "ts.net";
+        };
+      };
+    };
+
+    persist = {
+      directories = [
+        {
+          directory = "/var/lib/headscale";
+          user = "headscale";
+          group = "headscale";
+          mode = "0700";
+        }
+      ];
+    };
+
+    reverseProxy = {host, ...}: let
+      cfg = host.settings.services.headscale;
+    in {
+      ${cfg.domain} = {
+        port = cfg.port;
+      };
+    };
+
+    nixos = {
+      host,
+      pkgs,
+      config,
+      reverseProxy,
+      ...
+    }: let
+      inherit
+        (import ./_lib.nix {inherit lib;})
+        configFilePath
+        dnsRecordsFilePath
+        mkDnsRecords
+        ;
+
+      cfg = host.settings.services.headscale;
+      hostAddr = host.address;
+
+      tailnetDomain = den.aspects.services.caddy.tailnetDomain;
+
+      dnsRecords = mkDnsRecords {
+        inherit reverseProxy tailnetDomain;
+        dnsDomain = cfg.dns.domain;
+      };
+
+      nixDnsRecords =
+        pkgs.writeText "headscale-dns-records.json"
+        (builtins.toJSON dnsRecords);
+
+      mergeHeadscaleState = pkgs.writeShellScript "merge-headscale-state" ''
+        set -euo pipefail
+
+        # Merge main configuration
+        if [ -f "${configFilePath}" ]; then
+          ${lib.getExe pkgs.yq-go} eval-all '. as $item ireduce ({}; . * $item)' \
+            "${configFilePath}" \
+            "${config.services.headscale.configFile}" \
+            > "${configFilePath}.tmp" \
+            && mv "${configFilePath}.tmp" "${configFilePath}"
+        else
+          cp "${config.services.headscale.configFile}" "${configFilePath}"
+          chmod 0640 "${configFilePath}"
+        fi
+
+        # Merge DNS extra records
+        if [ ! -f "${dnsRecordsFilePath}" ]; then
+          cp "${nixDnsRecords}" "${dnsRecordsFilePath}"
+          chmod 0640 "${dnsRecordsFilePath}"
+        fi
+
+        ${lib.getExe pkgs.yq-go} eval-all '. as $item ireduce ([]; . + $item) | unique_by(.name + "|" + .type)' \
+          "${nixDnsRecords}" \
+          "${config.vaultix.templates.headscale-direct-records.path}" \
+          "${dnsRecordsFilePath}" \
+          > "${dnsRecordsFilePath}.tmp" \
+          && mv "${dnsRecordsFilePath}.tmp" "${dnsRecordsFilePath}"
+      '';
+    in {
+      services.headscale = {
+        enable = true;
+        address = "127.0.0.1";
+        port = cfg.port;
+        settings = {
+          server_url = "https://${cfg.domain}";
+          database.type = "sqlite";
+          tls_cert_path = null; # Use webserver for TLS instead.
+          tls_key_path = null;
+          prefixes = {
+            v4 = "100.64.0.0/10";
+            v6 = "fd7a:115c:a1e0::/48";
+            allocation = "random";
+          };
+          derp.paths = [config.vaultix.templates.headscale-derp-map.path];
+        };
+      };
+
+      systemd.services.headscale = let
+        hsCfg = config.services.headscale;
+      in {
+        serviceConfig = {
+          ExecStartPre = [mergeHeadscaleState];
+          EnvironmentFile =
+            lib.mkIf (hostAddr.ipv4.secret.name != null || hostAddr.ipv6.secret.name != null)
+            config.vaultix.templates.headscale-env.path;
+        };
+
+        script = lib.mkForce ''
+          ${lib.optionalString (hsCfg.settings.database.postgres.password_file != null) ''
+            export HEADSCALE_DATABASE_POSTGRES_PASS="$(head -n1 ${lib.escapeShellArg hsCfg.settings.database.postgres.password_file})"
+          ''}
+          exec ${lib.getExe hsCfg.package} serve --config ${configFilePath}
+        '';
+      };
+    };
+  };
+}
modules/services/headscale/derp.nix
@@ -0,0 +1,127 @@
+{den, ...}: {
+  den.aspects.services.headscale.derp = {host, ...}: {
+    nixos = {
+      lib,
+      config,
+      ...
+    }: let
+      inherit
+        (import ./_lib.nix {inherit lib;})
+        mkDerpRegionBlock
+        mkDerpRegionId
+        ;
+
+      cfg = host.settings.services.headscale;
+      hostAddr = host.address;
+
+      derpHosts = let
+        allHosts = builtins.concatMap builtins.attrValues (builtins.attrValues den.hosts);
+      in
+        builtins.filter (h: h.hasAspect den.aspects.services.tailscale-derp) allHosts;
+
+      derpRegions =
+        map (h: let
+          dcfg = h.settings.services.tailscale-derp;
+          v4 = h.address.ipv4;
+          v6 = h.address.ipv6;
+        in {
+          inherit h dcfg;
+          regionId = mkDerpRegionId dcfg.domain;
+          v4 =
+            if v4.clearText != null
+            then v4.clearText
+            else if v4.secret.name != null && v4.secret.file != null
+            then config.vaultix.placeholder.${v4.secret.name}
+            else null;
+          v6 =
+            if v6.clearText != null
+            then v6.clearText
+            else if v6.secret.name != null && v6.secret.file != null
+            then config.vaultix.placeholder.${v6.secret.name}
+            else null;
+        })
+        derpHosts;
+
+      duplicateDerpDomains = let
+        domains = map (r: r.dcfg.domain) derpRegions;
+      in
+        lib.filter (d: builtins.length (lib.filter (r: r.dcfg.domain == d) derpRegions) > 1) (lib.unique domains);
+
+      collidingDerpRegionIds = lib.filter (r: builtins.length (lib.filter (r': r'.regionId == r.regionId) derpRegions) > 1) derpRegions;
+
+      derpersWithoutAddress = lib.filter (r: r.v4 == null && r.v6 == null) derpRegions;
+
+      assertUniqueDerpDomains =
+        lib.assertMsg (duplicateDerpDomains == [])
+        "Multiple DERP hosts declare the same domain: ${lib.concatStringsSep ", " duplicateDerpDomains}";
+
+      assertUniqueDerpRegionIds =
+        lib.assertMsg (collidingDerpRegionIds == [])
+        ("DERP region ID collision between hosts: "
+          + lib.concatStringsSep ", " (lib.unique (map (r: r.h.name) collidingDerpRegionIds)));
+
+      assertDerpersHaveAddress =
+        lib.assertMsg (derpersWithoutAddress == [])
+        ("DERP hosts have no public IPv4 or IPv6 (clearText or secret): "
+          + lib.concatStringsSep ", " (map (r: r.h.name) derpersWithoutAddress));
+
+      derpMapYaml = assert assertUniqueDerpDomains;
+      assert assertUniqueDerpRegionIds;
+      assert assertDerpersHaveAddress; (
+        if derpRegions == []
+        then "regions: {}\n"
+        else "regions:\n" + lib.concatStringsSep "\n" (map mkDerpRegionBlock derpRegions) + "\n"
+      );
+
+      hostsWithIpv4Secret =
+        builtins.filter (h: h.address.ipv4.secret.name != null && h.address.ipv4.secret.file != null) derpHosts;
+      hostsWithIpv6Secret =
+        builtins.filter (h: h.address.ipv6.secret.name != null && h.address.ipv6.secret.file != null) derpHosts;
+    in {
+      services.headscale.settings.derp.server = lib.optionalAttrs (cfg.derp.enable) {
+        enabled = true;
+        stun_listen_addr = "0.0.0.0:${toString cfg.derp.port}";
+        verify_clients = true;
+        region_id = 999;
+        region_code = "headscale";
+        region_name = "Headscale Embedded DERP";
+        ipv4 = lib.mkIf (hostAddr.ipv4.clearText != null) hostAddr.ipv4.clearText;
+        ipv6 = lib.mkIf (hostAddr.ipv6.clearText != null) hostAddr.ipv6.clearText;
+      };
+
+      networking.firewall.allowedUDPPorts = lib.optional (cfg.derp.enable) cfg.derp.port;
+
+      vaultix.secrets = lib.mkMerge (
+        (map (h: {${h.address.ipv4.secret.name}.file = h.address.ipv4.secret.file;}) hostsWithIpv4Secret)
+        ++ (map (h: {${h.address.ipv6.secret.name}.file = h.address.ipv6.secret.file;}) hostsWithIpv6Secret)
+      );
+
+      vaultix.templates.headscale-derp-map = {
+        content = derpMapYaml;
+        owner = config.services.headscale.user;
+        group = config.services.headscale.group;
+        mode = "0400";
+      };
+
+      vaultix.templates.headscale-env =
+        lib.mkIf (hostAddr.ipv4.secret.name != null || hostAddr.ipv6.secret.name != null)
+        {
+          content = lib.concatLines (
+            (
+              lib.optional
+              (hostAddr.ipv4.secret.name != null)
+              "HEADSCALE_DERP_SERVER_IPV4=${config.vaultix.placeholder.${hostAddr.ipv4.secret.name}}"
+            )
+            ++ (
+              lib.optional
+              (hostAddr.ipv6.secret.name != null)
+              "HEADSCALE_DERP_SERVER_IPV6=${config.vaultix.placeholder.${hostAddr.ipv6.secret.name}}"
+            )
+          );
+          owner = config.services.headscale.user;
+          group = config.services.headscale.group;
+          mode = "0400";
+        };
+    };
+  };
+}
modules/services/headscale/dns.nix
@@ -0,0 +1,72 @@
+{
+  den.aspects.services.headscale.dns = {
+    nixos = {
+      host,
+      reverseProxy,
+      lib,
+      config,
+      ...
+    }: let
+      inherit (import ./_lib.nix {inherit lib;}) dnsRecordsFilePath mkDirectEntries;
+
+      cfg = host.settings.services.headscale;
+
+      directEntries = mkDirectEntries reverseProxy;
+
+      mkRecordValue = ip:
+        if ip.kind == "clear"
+        then ip.value
+        else config.vaultix.placeholder.${ip.name};
+
+      directRecords = lib.concatMap (e:
+        lib.optionals (e.v4 != null) [
+          {
+            name = e.domain;
+            type = "A";
+            value = mkRecordValue e.v4;
+          }
+        ]
+        ++ lib.optionals (e.v6 != null) [
+          {
+            name = e.domain;
+            type = "AAAA";
+            value = mkRecordValue e.v6;
+          }
+        ])
+      directEntries;
+
+      directSecretIps = lib.unique (lib.concatMap (e:
+        lib.optional (e.v4 != null && e.v4.kind == "secret") e.v4
+        ++ lib.optional (e.v6 != null && e.v6.kind == "secret") e.v6)
+      directEntries);
+    in {
+      services.headscale.settings.dns = {
+        magic_dns = cfg.dns.enable;
+        override_local_dns = cfg.dns.enable;
+        base_domain = cfg.dns.domain;
+        nameservers.global = [
+          # IPv4
+          "119.29.29.29" # DNSPod
+          "223.5.5.5" # AliDNS
+          # IPv6
+          "2400:3200::1" # AliDNS
+          "2606:4700:4700::1111" # Cloudflare
+        ];
+        extra_records_path = dnsRecordsFilePath;
+      };
+
+      vaultix.secrets = lib.listToAttrs (map (s: {
+          name = s.name;
+          value.file = s.file;
+        })
+        directSecretIps);
+
+      vaultix.templates.headscale-direct-records = {
+        content = builtins.toJSON directRecords;
+        owner = config.services.headscale.user;
+        group = config.services.headscale.group;
+        mode = "0400";
+      };
+    };
+  };
+}
modules/services/headscale.nix
@@ -1,390 +0,0 @@
-{
-  den,
-  lib,
-  ...
-}: {
-  den.aspects.services.headscale = {
-    settings.host = {
-      domain = lib.mkOption {
-        type = lib.types.nullOr lib.types.str;
-        default = null;
-      };
-      port = lib.mkOption {
-        type = lib.types.port;
-        default = 8080;
-      };
-      derp = {
-        enable = lib.mkEnableOption "Enable Headscale's builtin DERP server";
-        port = lib.mkOption {
-          type = lib.types.port;
-          default = 3478;
-        };
-      };
-      dns = {
-        enable = lib.mkEnableOption "Enable Headscale's Magic DNS";
-        domain = lib.mkOption {
-          type = lib.types.str;
-          default = "ts.net";
-        };
-      };
-    };
-
-    persist = {
-      directories = [
-        {
-          directory = "/var/lib/headscale";
-          user = "headscale";
-          group = "headscale";
-          mode = "0700";
-        }
-      ];
-    };
-
-    reverseProxy = {host, ...}: let
-      cfg = host.settings.services.headscale;
-    in {
-      ${cfg.domain} = {
-        port = cfg.port;
-      };
-    };
-
-    nixos = {
-      host,
-      pkgs,
-      config,
-      reverseProxy,
-      ...
-    }: let
-      cfg = host.settings.services.headscale;
-      hostAddr = host.address;
-
-      configFilePath = "/var/lib/headscale/config.yaml";
-      dnsRecordsFilePath = "/var/lib/headscale/dns-records.json";
-
-      tailnetDomain = den.aspects.services.caddy.tailnetDomain;
-
-      inherit (import ./direct-dns/_lib.nix {inherit lib;}) mkDirectEntries;
-
-      derpHosts = let
-        allHosts = builtins.concatMap builtins.attrValues (builtins.attrValues den.hosts);
-      in
-        builtins.filter (h: h.hasAspect den.aspects.services.tailscale-derp) allHosts;
-
-      hexDigitValue = c:
-        lib.lists.findFirstIndex (d: d == c) 0 (lib.stringToCharacters "0123456789abcdef");
-
-      mkDerpRegionId = domain:
-        900
-        + (
-          builtins.foldl' (acc: c: lib.mod (acc * 16 + hexDigitValue c) 99) 0
-          (lib.stringToCharacters (builtins.hashString "sha256" domain))
-        );
-
-      derpRegions =
-        map (h: let
-          dcfg = h.settings.services.tailscale-derp;
-          v4 = h.address.ipv4;
-          v6 = h.address.ipv6;
-        in {
-          inherit h dcfg;
-          regionId = mkDerpRegionId dcfg.domain;
-          v4 =
-            if v4.clearText != null
-            then v4.clearText
-            else if v4.secret.name != null && v4.secret.file != null
-            then config.vaultix.placeholder.${v4.secret.name}
-            else null;
-          v6 =
-            if v6.clearText != null
-            then v6.clearText
-            else if v6.secret.name != null && v6.secret.file != null
-            then config.vaultix.placeholder.${v6.secret.name}
-            else null;
-        })
-        derpHosts;
-
-      duplicateDerpDomains = let
-        domains = map (r: r.dcfg.domain) derpRegions;
-      in
-        lib.filter (d: builtins.length (lib.filter (r: r.dcfg.domain == d) derpRegions) > 1) (lib.unique domains);
-
-      collidingDerpRegionIds = lib.filter (r: builtins.length (lib.filter (r': r'.regionId == r.regionId) derpRegions) > 1) derpRegions;
-
-      derpersWithoutAddress = lib.filter (r: r.v4 == null && r.v6 == null) derpRegions;
-
-      assertUniqueDerpDomains =
-        lib.assertMsg (duplicateDerpDomains == [])
-        "Multiple DERP hosts declare the same domain: ${lib.concatStringsSep ", " duplicateDerpDomains}";
-
-      assertUniqueDerpRegionIds =
-        lib.assertMsg (collidingDerpRegionIds == [])
-        ("DERP region ID collision between hosts: "
-          + lib.concatStringsSep ", " (lib.unique (map (r: r.h.name) collidingDerpRegionIds)));
-
-      assertDerpersHaveAddress =
-        lib.assertMsg (derpersWithoutAddress == [])
-        ("DERP hosts have no public IPv4 or IPv6 (clearText or secret): "
-          + lib.concatStringsSep ", " (map (r: r.h.name) derpersWithoutAddress));
-
-      mkDerpNodeLines = r:
-        lib.concatStringsSep "\n" (
-          ["      - name: ${r.h.name}"]
-          ++ ["        regionid: ${toString r.regionId}"]
-          ++ ["        hostname: ${r.dcfg.domain}"]
-          ++ lib.optionals (r.v4 != null) ["        ipv4: \"${r.v4}\""]
-          ++ lib.optionals (r.v6 != null) ["        ipv6: \"${r.v6}\""]
-          ++ ["        stunport: 3478"]
-          ++ ["        derpport: 443"]
-          ++ ["        stunonly: false"]
-        );
-
-      mkDerpRegionBlock = r:
-        lib.concatStringsSep "\n" (
-          ["  ${toString r.regionId}:"]
-          ++ ["    regionid: ${toString r.regionId}"]
-          ++ ["    regioncode: ${r.h.name}"]
-          ++ ["    regionname: ${r.h.name}"]
-          ++ ["    nodes:"]
-          ++ [(mkDerpNodeLines r)]
-        );
-
-      derpMapYaml = assert assertUniqueDerpDomains;
-      assert assertUniqueDerpRegionIds;
-      assert assertDerpersHaveAddress; (
-        if derpRegions == []
-        then "regions: {}\n"
-        else "regions:\n" + lib.concatStringsSep "\n" (map mkDerpRegionBlock derpRegions) + "\n"
-      );
-
-      hostsWithIpv4Secret =
-        builtins.filter (h: h.address.ipv4.secret.name != null && h.address.ipv4.secret.file != null) derpHosts;
-      hostsWithIpv6Secret =
-        builtins.filter (h: h.address.ipv6.secret.name != null && h.address.ipv6.secret.file != null) derpHosts;
-
-      # Tailnet entries (from all hosts): emit A/AAAA records pointing at the
-      # owning host's tailscale addresses so MagicDNS resolves them.
-      tailnetEntries = let
-        uniqueByDomain = lib.foldl' (acc: e:
-          if builtins.any (x: x.domain == e.domain) acc
-          then acc
-          else acc ++ [e]) [];
-      in
-        uniqueByDomain (
-          lib.concatMap (
-            r:
-              lib.mapAttrsToList (domain: conf: {
-                inherit domain;
-                source = r.source.host;
-              }) (
-                lib.filterAttrs (_: conf: conf.tailscale or false) r.value
-              )
-          )
-          reverseProxy
-        );
-
-      duplicateDomains =
-        lib.filter (domain: builtins.length (lib.filter (e: e.domain == domain) tailnetEntries) > 1)
-        (lib.unique (map (e: e.domain) tailnetEntries));
-
-      dnsRecords = let
-        assertNoDuplicates =
-          lib.assertMsg (duplicateDomains == [])
-          "Multiple hosts declare the same tailnet domain: ${lib.concatStringsSep ", " duplicateDomains}";
-
-        assertDomainMatches =
-          lib.assertMsg (cfg.dns.domain == tailnetDomain)
-          ("Headscale MagicDNS base domain '${cfg.dns.domain}' does not match the tailnet domain"
-            + "'${tailnetDomain}' used by the caddy tailnet module (hardcoded in modules/services/caddy/quirks.nix).");
-      in
-        assert assertNoDuplicates;
-        assert assertDomainMatches;
-          lib.concatMap (e: let
-            v4 = e.source.address.ipv4.tailscale;
-            v6 = e.source.address.ipv6.tailscale;
-          in
-            assert lib.assertMsg (v4 != null || v6 != null)
-            ("Tailnet domain '${e.domain}' is declared by host '${e.source.name}' which has no tailscale address configured"
-              + "(see `address` in modules/hosts/schema.nix).");
-              lib.optionals (v4 != null) [
-                {
-                  name = e.domain;
-                  type = "A";
-                  value = v4;
-                }
-              ]
-              ++ lib.optionals (v6 != null) [
-                {
-                  name = e.domain;
-                  type = "AAAA";
-                  value = v6;
-                }
-              ])
-          tailnetEntries;
-
-      directEntries = mkDirectEntries reverseProxy;
-
-      mkRecordValue = ip:
-        if ip.kind == "clear"
-        then ip.value
-        else config.vaultix.placeholder.${ip.name};
-
-      directRecords = lib.concatMap (e:
-        lib.optionals (e.v4 != null) [
-          {
-            name = e.domain;
-            type = "A";
-            value = mkRecordValue e.v4;
-          }
-        ]
-        ++ lib.optionals (e.v6 != null) [
-          {
-            name = e.domain;
-            type = "AAAA";
-            value = mkRecordValue e.v6;
-          }
-        ])
-      directEntries;
-
-      directSecretIps = lib.unique (lib.concatMap (e:
-        lib.optional (e.v4 != null && e.v4.kind == "secret") e.v4
-        ++ lib.optional (e.v6 != null && e.v6.kind == "secret") e.v6)
-      directEntries);
-    in {
-      services.headscale = {
-        enable = true;
-        address = "127.0.0.1";
-        port = cfg.port;
-        settings = {
-          server_url = "https://${cfg.domain}";
-          database.type = "sqlite";
-          tls_cert_path = null; # Use webserver for TLS instead.
-          tls_key_path = null;
-          prefixes = {
-            v4 = "100.64.0.0/10";
-            v6 = "fd7a:115c:a1e0::/48";
-            allocation = "random";
-          };
-          derp = {
-            server = lib.optionalAttrs (cfg.derp.enable) {
-              enabled = true;
-              stun_listen_addr = "0.0.0.0:${toString cfg.derp.port}";
-              verify_clients = true;
-              region_id = 999;
-              region_code = "headscale";
-              region_name = "Headscale Embedded DERP";
-              ipv4 = lib.mkIf (hostAddr.ipv4.clearText != null) hostAddr.ipv4.clearText;
-              ipv6 = lib.mkIf (hostAddr.ipv6.clearText != null) hostAddr.ipv6.clearText;
-            };
-            paths = [config.vaultix.templates.headscale-derp-map.path];
-          };
-          dns = {
-            magic_dns = cfg.dns.enable;
-            override_local_dns = cfg.dns.enable;
-            base_domain = cfg.dns.domain;
-            nameservers.global = [
-              # IPv4
-              "119.29.29.29" # DNSPod
-              "223.5.5.5" # AliDNS
-              # IPv6
-              "2400:3200::1" # AliDNS
-              "2606:4700:4700::1111" # Cloudflare
-            ];
-            extra_records_path = dnsRecordsFilePath;
-          };
-        };
-      };
-
-      networking.firewall.allowedUDPPorts = lib.optional (cfg.derp.enable) cfg.derp.port;
-
-      vaultix.secrets = lib.mkMerge (
-        (map (h: {${h.address.ipv4.secret.name}.file = h.address.ipv4.secret.file;}) hostsWithIpv4Secret)
-        ++ (map (h: {${h.address.ipv6.secret.name}.file = h.address.ipv6.secret.file;}) hostsWithIpv6Secret)
-        ++ (map (s: {${s.name}.file = s.file;}) directSecretIps)
-      );
-      vaultix.templates.headscale-derp-map = {
-        content = derpMapYaml;
-        owner = config.services.headscale.user;
-        group = config.services.headscale.group;
-        mode = "0400";
-      };
-      vaultix.templates.headscale-direct-records = {
-        content = builtins.toJSON directRecords;
-        owner = config.services.headscale.user;
-        group = config.services.headscale.group;
-        mode = "0400";
-      };
-
-      systemd.services.headscale = let
-        nixConfig = config.services.headscale.configFile;
-        hsCfg = config.services.headscale;
-
-        nixDnsRecords =
-          pkgs.writeText "headscale-dns-records.json"
-          (builtins.toJSON dnsRecords);
-
-        mergeHeadscaleState = pkgs.writeShellScript "merge-headscale-state" ''
-          set -euo pipefail
-
-          # Merge main configuration
-          if [ -f "${configFilePath}" ]; then
-            ${lib.getExe pkgs.yq-go} eval-all '. as $item ireduce ({}; . * $item)' \
-              "${configFilePath}" \
-              "${nixConfig}" \
-              > "${configFilePath}.tmp" \
-              && mv "${configFilePath}.tmp" "${configFilePath}"
-          else
-            cp "${nixConfig}" "${configFilePath}"
-            chmod 0640 "${configFilePath}"
-          fi
-
-          # Merge DNS extra records
-          if [ ! -f "${dnsRecordsFilePath}" ]; then
-            cp "${nixDnsRecords}" "${dnsRecordsFilePath}"
-            chmod 0640 "${dnsRecordsFilePath}"
-          fi
-
-          ${lib.getExe pkgs.yq-go} eval-all '. as $item ireduce ([]; . + $item) | unique_by(.name + "|" + .type)' \
-            "${nixDnsRecords}" \
-            "${config.vaultix.templates.headscale-direct-records.path}" \
-            "${dnsRecordsFilePath}" \
-            > "${dnsRecordsFilePath}.tmp" \
-            && mv "${dnsRecordsFilePath}.tmp" "${dnsRecordsFilePath}"
-        '';
-      in {
-        serviceConfig = {
-          ExecStartPre = [mergeHeadscaleState];
-          EnvironmentFile =
-            lib.mkIf (hostAddr.ipv4.secret.name != null || hostAddr.ipv6.secret.name != null)
-            config.vaultix.templates.headscale-env.path;
-        };
-
-        script = lib.mkForce ''
-          ${lib.optionalString (hsCfg.settings.database.postgres.password_file != null) ''
-            export HEADSCALE_DATABASE_POSTGRES_PASS="$(head -n1 ${lib.escapeShellArg hsCfg.settings.database.postgres.password_file})"
-          ''}
-          exec ${lib.getExe hsCfg.package} serve --config ${configFilePath}
-        '';
-      };
-
-      vaultix.templates.headscale-env =
-        lib.mkIf (hostAddr.ipv4.secret.name != null || hostAddr.ipv6.secret.name != null)
-        {
-          content = lib.concatLines (
-            (
-              lib.optional
-              (hostAddr.ipv4.secret.name != null)
-              "HEADSCALE_DERP_SERVER_IPV4=${config.vaultix.placeholder.${hostAddr.ipv4.secret.name}}"
-            )
-            ++ (
-              lib.optional
-              (hostAddr.ipv6.secret.name != null)
-              "HEADSCALE_DERP_SERVER_IPV6=${config.vaultix.placeholder.${hostAddr.ipv6.secret.name}}"
-            )
-          );
-          owner = config.services.headscale.user;
-          group = config.services.headscale.group;
-          mode = "0400";
-        };
-    };
-  };
-}