Commit 29956b8

HPCesia <me@hpcesia.com>
2026-10-02 08:13:44
tailscale: use peer relay instead of derp
Assisted-by: crush:deepseek-v4.1-flash
1 parent b813cd5
Changed files (8)
modules
hosts
cyrene
services
hyacine
services
services
modules/hosts/cyrene/services/default.nix
@@ -41,10 +41,6 @@
       services.headscale = {
         domain = "gate.trin.one";
         port = 3465;
-        derp = {
-          enable = true;
-          addPublicServers = true;
-        };
         dns = {
           enable = true;
           domain = "net.trin.one";
@@ -75,6 +71,7 @@
     services.headplane
     services.headscale
     services.ntfy-sh
+    services.tailscale-relay
     services.vaultwarden
     services.wakapi
   ];
modules/hosts/hyacine/services/default.nix
@@ -1,11 +1,6 @@
 {den, ...}: {
   den.hosts.hyacine = {
     settings = {
-      services.tailscale-derp = {
-        domain = "derp-hyacine.trin.one";
-        port = 8010;
-      };
-
       services.xray = {
         realitySiteFileAged = ./xray-reality-site.age;
         port = 7890;
@@ -21,7 +16,7 @@
   den.aspects.hyacine.includes = with den.aspects; [
     services.caddy
 
-    services.tailscale-derp
+    services.tailscale-relay
 
     services.caddy.reality
     services.xray
modules/services/headscale/_lib.nix
@@ -1,38 +1,6 @@
 {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
@@ -176,6 +144,4 @@ in {
 
   configFilePath = "/var/lib/headscale/config.yaml";
   dnsRecordsFilePath = "/var/lib/headscale/dns-records.json";
-
-  inherit mkDerpRegionId mkDerpRegionBlock;
 }
modules/services/headscale/default.nix
@@ -5,7 +5,6 @@
 }: {
   den.aspects.services.headscale = {
     includes = [
-      den.aspects.services.headscale.derp
       den.aspects.services.headscale.dns
       den.aspects.services.headscale.policy
     ];
@@ -19,14 +18,6 @@
         type = lib.types.port;
         default = 8080;
       };
-      derp = {
-        enable = lib.mkEnableOption "Enable Headscale's builtin DERP server";
-        addPublicServers = lib.mkEnableOption "Add public Tailscale DERP servers to DERP map";
-        port = lib.mkOption {
-          type = lib.types.port;
-          default = 3478;
-        };
-      };
       dns = {
         enable = lib.mkEnableOption "Enable Headscale's Magic DNS";
         domain = lib.mkOption {
@@ -70,7 +61,6 @@
         ;
 
       cfg = host.settings.services.headscale;
-      hostAddr = host.address;
 
       tailnetDomain = den.aspects.services.caddy.tailnetDomain;
 
@@ -126,7 +116,6 @@
             v6 = "fd7a:115c:a1e0::/48";
             allocation = "random";
           };
-          derp.paths = [config.vaultix.templates.headscale-derp-map.path];
         };
       };
 
@@ -135,9 +124,6 @@
       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 ''
modules/services/headscale/derp.nix
@@ -1,129 +0,0 @@
-{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;
-      };
-
-      services.headscale.settings.derp.urls = lib.mkIf (!cfg.derp.addPublicServers) [];
-
-      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/mihomo/tproxy.nix
@@ -50,8 +50,8 @@
           }
           set bypass-udp-ports {
             type inet_service
-            # 3478: STUN; 41641: WireGuard endpoints of Tailscale peers.
-            elements = { 53, 67, 68, 123, 3478, 41641 }
+            # 3478: STUN; 40000: peer relay; 41641: WireGuard endpoints of Tailscale peers.
+            elements = { 53, 67, 68, 123, 3478, 40000, 41641 }
           }
           set bypass-udp-sports {
             type inet_service
modules/services/tailscale-derp.nix
@@ -1,36 +0,0 @@
-{lib, ...}: {
-  den.aspects.services.tailscale-derp = {
-    settings.host = {
-      domain = lib.mkOption {
-        type = lib.types.str;
-      };
-      port = lib.mkOption {
-        type = lib.types.port;
-        default = 8010;
-      };
-    };
-
-    reverseProxy = {host, ...}: let
-      cfg = host.settings.services.tailscale-derp;
-    in {
-      ${cfg.domain} = {
-        port = cfg.port;
-        prefer = "direct";
-      };
-    };
-
-    nixos = {host, ...}: let
-      cfg = host.settings.services.tailscale-derp;
-    in {
-      services.tailscale.derper = {
-        enable = true;
-
-        inherit (cfg) domain port;
-
-        configureNginx = false;
-        openFirewall = true;
-        verifyClients = true;
-      };
-    };
-  };
-}
modules/services/tailscale-relay.nix
@@ -0,0 +1,26 @@
+{lib, ...}: {
+  den.aspects.services.tailscale-relay = {
+    settings.host.port = lib.mkOption {
+      type = lib.types.port;
+      default = 40000;
+      description = "UDP port that accepts peer relay connections from tailnet members.";
+    };
+
+    tailscaleGrant = _: [
+      {
+        src = ["autogroup:member"];
+        app."tailscale.com/cap/relay" = [];
+      }
+    ];
+
+    nixos = {host, ...}: let
+      cfg = host.settings.services.tailscale-relay;
+    in {
+      services.tailscale.extraSetFlags = [
+        "--relay-server-port=${toString cfg.port}"
+      ];
+
+      networking.firewall.allowedUDPPorts = [cfg.port];
+    };
+  };
+}