159 lines
4.0 KiB
Nix
Raw Normal View History

2025-07-10 18:37:38 +03:00
{
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" ];
};
}