Commit 1a71345
Changed files (4)
.secrets
modules
hosts
hyacine
services
services
.secrets/cache/cyrene/01d5bc63ddba392c9fa55ccb759d10c5ed6d354b55e2ff614d1b68e811b72614
@@ -0,0 +1,8 @@
+age-encryption.org/v1
+-> ssh-ed25519 7JvPpg eFoDP61kD600XcJ9JFHqDE6lx8Pa4jLOdQ9j80clr1o
+qMKUfnXpIL1160EkoQl9AZZakKtTlqBGax/2VuJ4YPQ
+-> cO8()M-grease L,mZiP_ $
+VEGF63WU+3T/WvYPBKR0moovG/dYCdmkHNYMuD0Jg776sfqWNx4ncozyZDPEvlqP
+H+RBNA
+--- IhimD/hkZqs1CYHB8e8vKkn+6KBl9y0RlBAJhgXjsiI
+`/��h��sto�o(��|���(:Bw�:ӘQ^�@�e���-�pY
\ No newline at end of file
modules/hosts/hyacine/services/default.nix
@@ -0,0 +1,16 @@
+{den, ...}: {
+ den.hosts.hyacine = {
+ settings = {
+ services.tailscale-derp = {
+ domain = "derp-hyacine.trin.one";
+ port = 8010;
+ };
+ };
+ };
+
+ den.aspects.hyacine.includes = with den.aspects; [
+ services.caddy
+
+ services.tailscale-derp
+ ];
+}
modules/services/headscale.nix
@@ -63,6 +63,102 @@
tailnetDomain = den.aspects.services.caddy.tailnetDomain;
+ 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 =
@@ -130,15 +226,18 @@
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;
+ 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;
@@ -159,6 +258,17 @@
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";
+ };
+
systemd.services.headscale = let
nixConfig = config.services.headscale.configFile;
hsCfg = config.services.headscale;
@@ -222,7 +332,7 @@
++ (
lib.optional
(hostAddr.ipv6.secret.name != null)
- "HEADSCALE_DERP_SERVER_IPV4=${config.vaultix.placeholder.${hostAddr.ipv6.secret.name}}"
+ "HEADSCALE_DERP_SERVER_IPV6=${config.vaultix.placeholder.${hostAddr.ipv6.secret.name}}"
)
);
owner = config.services.headscale.user;
modules/services/tailscale-derp.nix
@@ -0,0 +1,35 @@
+{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;
+ };
+ };
+
+ 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;
+ };
+ };
+ };
+}