Commit 6da2f50

HPCesia <me@hpcesia.com>
2026-06-18 03:53:50
Add Headscale service
1 parent 0929d8a
Changed files (2)
modules
hosts
hyacine
services
services
modules/hosts/hyacine/services/services.nix
@@ -1,10 +1,21 @@
 {den, ...}: {
   den.hosts.hyacine.settings = {
+    services.headscale = {
+      domain = "gate.trin.one";
+      port = 3465;
+      derp.enable = true;
+      dns = {
+        enable = true;
+        domain = "net.trin.one";
+      };
+    };
   };
 
   den.aspects.hyacine = {
     includes = [
       den.aspects.services.webserver
+
+      den.aspects.services.headscale
     ];
   };
 }
modules/services/headscale.nix
@@ -0,0 +1,141 @@
+{
+  den,
+  lib,
+  ...
+}: {
+  den.aspects.services.headscale = {
+    settings = {
+      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,
+      ...
+    }: let
+      cfg = host.settings.services.headscale;
+      hostAddr = host.address;
+
+      headscale-wrapped =
+        pkgs.runCommandLocal "headscale-wrapped" {
+          nativeBuildInputs = [pkgs.makeWrapper];
+        } ''
+          mkdir -p $out/bin
+          makeWrapper ${lib.getExe config.services.headscale.package} $out/bin/headscale \
+            --add-flags "--config ${config.services.headscale.configFile}"
+        '';
+    in {
+      environment.systemPackages = [headscale-wrapped];
+      services.headscale = {
+        enable = true;
+        address =
+          if (host.hasAspect den.aspects.services.webserver)
+          then "127.0.0.1"
+          else abort "Not implemented for outside host webserver";
+        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;
+          };
+          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
+            ];
+          };
+        };
+      };
+
+      networking.firewall.allowedUDPPorts = lib.optional (cfg.derp.enable) 3478;
+
+      systemd.services.headscale = {
+        serviceConfig.EnvironmentFile =
+          lib.mkIf (hostAddr.ipv4.secret.name != null || hostAddr.ipv6.secret.name != null)
+          config.vaultix.templates.headscale-env.path;
+      };
+      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_IPV4=${config.vaultix.placeholder.${hostAddr.ipv6.secret.name}}"
+            )
+          );
+          owner = config.services.headscale.user;
+          group = config.services.headscale.group;
+          mode = "0400";
+        };
+    };
+  };
+}