diff --git a/hosts/orion/default.nix b/hosts/orion/default.nix index 5a0e632..15f838c 100644 --- a/hosts/orion/default.nix +++ b/hosts/orion/default.nix @@ -4,6 +4,9 @@ inputs, ... }: +let + inherit (lib) concatLists unique; +in { imports = [ inputs.srvos.nixosModules.server @@ -94,7 +97,6 @@ mkvtoolnix-cli nfs-utils p7zip - podman-compose pwgen ripgrep rsync @@ -117,6 +119,51 @@ ataraxia.services.gitea.enable = true; ataraxia.services.syncyomi.enable = true; ataraxia.services.vaultwarden.enable = true; + ataraxia.services.headscale.enable = true; + ataraxia.services.headscale.extraDns = unique ( + concatLists ( + map + (name: [ + { + inherit name; + type = "A"; + value = "100.64.0.1"; + } + { + inherit name; + type = "AAAA"; + value = "fd7a:115c:a1e0::1"; + } + ]) + [ + "api.ataraxiadev.com" + "cache.ataraxiadev.com" + "cal.ataraxiadev.com" + "code.ataraxiadev.com" + "docs.ataraxiadev.com" + "element.ataraxiadev.com" + "files.ataraxiadev.com" + "home.ataraxiadev.com" + "jackett.ataraxiadev.com" + "jellyfin.ataraxiadev.com" + "kavita.ataraxiadev.com" + "ldap.ataraxiadev.com" + "lib.ataraxiadev.com" + "matrix.ataraxiadev.com" + "medusa.ataraxiadev.com" + "pdf.ataraxiadev.com" + "qbit.ataraxiadev.com" + "radarr.ataraxiadev.com" + "restic.ataraxiadev.com" + "s3.ataraxiadev.com" + "sonarr.ataraxiadev.com" + "tools.ataraxiadev.com" + "turn.ataraxiadev.com" + "vw.ataraxiadev.com" + "wiki.ataraxiadev.com" + ] + ) + ); ataraxia.virtualisation.guests = { omv = { diff --git a/modules/nixos/services/headscale.nix b/modules/nixos/services/headscale.nix new file mode 100644 index 0000000..6b6baf9 --- /dev/null +++ b/modules/nixos/services/headscale.nix @@ -0,0 +1,158 @@ +{ + config, + lib, + pkgs, + secretsDir, + ... +}: +let + inherit (lib) + mkEnableOption + mkIf + mkOption + recursiveUpdate + ; + inherit (lib.types) + bool + enum + listOf + str + submodule + ; + + cfg = config.ataraxia.services.headscale; + nginx = config.ataraxia.services.nginx; + domain = "wg.ataraxiadev.com"; + + dnsEntry = submodule { + options = { + name = mkOption { + type = str; + }; + type = mkOption { + type = enum [ + "A" + "AAAA" + ]; + }; + value = mkOption { + type = str; + }; + }; + }; +in +{ + options.ataraxia.services.headscale = { + enable = mkEnableOption "Enable headscale service"; + sopsDir = mkOption { + type = str; + default = config.networking.hostName; + description = '' + Name for sops secrets directory. Defaults to hostname. + ''; + }; + nginxHost = mkOption { + type = bool; + default = config.ataraxia.services.nginx.enable; + description = "Enable nginx vHost integration"; + }; + extraDns = mkOption { + type = listOf dnsEntry; + description = '' + Extra dns records for headscale. + ''; + }; + }; + + config = mkIf cfg.enable { + services.headscale = { + enable = true; + address = "0.0.0.0"; + port = 8005; + settings = { + server_url = "https://${domain}"; + ip_prefixes = [ + "fd7a:115c:a1e0::/64" + "100.64.0.0/16" + ]; + dns = { + override_local_dns = true; + base_domain = "tailnet.ataraxiadev.com"; + nameservers.global = [ "127.0.0.1" ]; + extra_records = cfg.extraDns; + }; + oidc = { + only_start_if_oidc_is_available = true; + issuer = "https://auth.ataraxiadev.com/application/o/headscale/"; + client_id = "n6UBhK8PahexLPb7GkU1xzoFLcYxQX0HWDytpUoi"; + client_secret_path = config.sops.secrets.headscale-oidc.path; + scope = [ + "openid" + "profile" + "email" + "groups" + ]; + allowed_groups = [ "headscale" ]; + }; + grpc_listen_addr = "127.0.0.1:50443"; + grpc_allow_insecure = true; + disable_check_updates = true; + ephemeral_node_inactivity_timeout = "4h"; + }; + }; + + services.nginx.virtualHosts = mkIf cfg.nginxHost { + ${domain} = recursiveUpdate nginx.defaultSettings { + locations."/headscale." = { + extraConfig = '' + grpc_pass grpc://${config.services.headscale.settings.grpc_listen_addr}; + ''; + priority = 1; + }; + locations."/metrics" = { + proxyPass = "http://127.0.0.1:${toString config.services.headscale.port}"; + extraConfig = '' + allow 100.64.0.0/16; + allow 10.10.10.0/24; + deny all; + ''; + priority = 2; + }; + locations."/" = { + proxyPass = "http://127.0.0.1:${toString config.services.headscale.port}"; + proxyWebsockets = true; + priority = 3; + }; + }; + }; + + sops.secrets.headscale-oidc = { + sopsFile = secretsDir + /${cfg.sopsDir}/headscale.yaml; + owner = "headscale"; + restartUnits = [ "headscale.service" ]; + }; + systemd.services.headscale = { + serviceConfig.TimeoutStopSec = 15; + serviceConfig.ExecStartPre = + let + waitAuthnetikReady = pkgs.writeShellScript "waitAuthnetikReady" '' + # Check until authentik is alive + max_retry=100 + counter=0 + until ${lib.getExe pkgs.curl} -fsSL http://auth.ataraxiadev.com/-/health/ready/ + do + echo "Waiting for the authentik..." + sleep 3 + [[ counter -eq $max_retry ]] && echo "Could not connect to authentik!" && exit 1 + echo "Trying again. Try #$counter" + ((counter++)) + done + echo "Authentik is alive!" + ''; + in + waitAuthnetikReady; + }; + + persist.state.directories = [ "/var/lib/headscale" ]; + }; +} diff --git a/secrets/orion/headscale.yaml b/secrets/orion/headscale.yaml new file mode 100644 index 0000000..1a31e36 --- /dev/null +++ b/secrets/orion/headscale.yaml @@ -0,0 +1,27 @@ +headscale-oidc: ENC[AES256_GCM,data:lu1c/XSD7/fV1MuwAETDV1PCn3C7zr0UKK0u4/5Z2AoQXHLsUES3Yvu7B9kStFd3M+GoOq6Y0xYVGLS9x5TcEVFDKSsdRRgGYlu2C/x+NUOlP0cEKKq222NYIZ6iA9emP6A2ZVy1ZpM1UE65vJHk1NHHbS4zYiiJMskOacwW1bs=,iv:o9/TG+9/MU6mchYtj6navG97eJhP/4kUlWcx/xjhvK0=,tag:l2xQhGn1vkcBZvBZevpTOg==,type:str] +headscale-oidc-env: ENC[AES256_GCM,data:LX26VJfqImj5hHGSczey4okdPsNdxsIQ4OD3kRhwRt4P2MAdlVWiBQl47Jj5lk1Nm/yZejf4GXARLoQf3TK1ie4aDaWJx8Yhl8aSpy1s3h/1lcM7OCNb9WhUB+ZmikXaA6sOui4sQfGEtf0ydeIE0CwH04WL+Qomu+WxFzUVSzPW3baR2AKSqKiLGLGB0mZrRmdbhSdxCJN85j2i/Q==,iv:9b4pMMLj9huMg2RnrU10xqjRoA3NCWUKn4rc956Gm+s=,tag:+XN0KzJqWvTS/8ufGooNfg==,type:str] +sops: + shamir_threshold: 1 + age: + - recipient: age13phpsegg6vu7a34ydtfa9s904dfpgzqhzru7epnky7glezk0xvkst9qh6h + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhdWVNQVRkYUwvVkRnUlJV + ZFI3K1NsTzBFWDZXSU5Md0NQRTlsUTlCTHhZCjFObFNMSENXN2NIZlQ1ZHBEZ3Fu + R0JhVXdsRThUS3ZDWXV2OTFkODdQUGMKLS0tIEZiZ1ZEWTBaMnlIc2lTWGQxSW1W + bjNGQnFnOFVwbHdsVkpuNHJlRVc2c1kK/IQzoSi17GU8D6LP+4ccxq+Ip3lary62 + 0dkRYgMOf+jR21VA+1jhyFFYkwzZl7ajnM+pXYKf+/togQv6MnML/g== + -----END AGE ENCRYPTED FILE----- + - recipient: age1m5msm7rgqye2q9zesgedg0emga4ntehlr629786lrxs3rhk0squq0ly9je + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSZFdCQmltbnduUklHdTJB + OGR6WllGaS8xUUhNT0t6cU1JVHF0V0tsSlF3CmlSYlNDZEJvRzdCT3hrRGFPeGdZ + eFRCVUFtbFJBUnNPREd2RzR1eUYyOFkKLS0tIGZBWGJURE5Cb2lreWdPYjJWL0FV + VzBXZTNORldzcGo2KzVhTmQ5dERMMjAKMEV+wMtClLbgur/Qx/xLaQNjjqNtm1sf + 5z+Hi/D0aqgA09k5iAxGdfez0rFJAX32w6vJouMTfYzCLqC6iaaomQ== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2024-01-24T20:09:29Z" + mac: ENC[AES256_GCM,data:akcHfxJrGSPINI28sQdxcz4s6P9Va+GAvF0TC7adgf2mgVtqkZdaZPJZ/BaVlxccWf3tFgBMKwLVHcfmxMi93KnxFxOuA3DWYnjmBfHzxHFq+jWke7BHzRhPvVsKOKKHdfkXPCZnqyHLwRPp0jUyrANw9m9Ub2JTomfHy3j2+FA=,iv:784bnpb7v0z3KewsnH+RXYkdml+o2sj/qvR7qqn/om0=,tag:L1c/p8GcUlT+4sLyr0T5fA==,type:str] + unencrypted_suffix: _unencrypted + version: 3.8.1