nix-da/hosts/raptus/headscale.nix

195 lines
4.6 KiB
Nix

{
config,
lib,
pkgs,
options,
...
}:
let
inherit (lib) mkIf;
metricsEnable = false;
mkAcl = src: dst: {
action = "accept";
inherit src dst;
};
mkSshAcl = src: dst: users: {
action = "accept";
inherit src dst users;
};
domain = "hs.ccnlc.eu";
in
mkIf config.services.headscale.enable {
environment.systemPackages = [ config.services.headscale.package ];
networking.firewall.allowedTCPPorts = [
3478 # DERP
];
services = {
headscale = {
address = "127.0.0.1";
port = 8521;
settings = {
server_url = "https://${domain}";
tls_cert_path = null;
tls_key_path = null;
ip_prefixes = [
"100.64.0.0/10"
"fd7a:115c:a1e0::/48"
];
ephemeral_node_inactivity_timeout = "30m";
node_update_check_interval = "10s";
metrics_listen_addr = mkIf metricsEnable "127.0.0.1:8086";
# logging
log = {
format = "text";
level = "info";
};
logtail.enabled = false;
dns_config = {
override_local_dns = true;
magic_dns = true;
# FIX: shan being down takes down entire tailnet
# 1 -> move dns to dedicated device
# 2 -> find out if fallback dns's are supported by headscale
nameservers = [
"100.64.0.4"
];
extra_records =
let
mkRecords = map (sub: {
name = "${sub}.ccnlc.eu";
type = "A";
value = "100.64.0.4";
});
in
[
{
name = "ccnlc.eu";
type = "A";
value = "100.64.0.4";
}
{
name = "git.ccnlc.eu";
type = "A";
value = "100.64.0.3";
}
]
# Tailscale doesn't seem to support wildcard A/AAAA records
# - https://github.com/juanfont/headscale/issues/2159#issuecomment-2393406444
++ mkRecords [
"immich"
"adguard"
"nextcloud"
"kitchenowl"
"navidrome"
"subsonic"
"nextcloud"
"paperless"
"truenas"
"fritz"
];
};
derp = {
# Reference: https://github.com/juanfont/headscale/issues/1326#issuecomment-1505487881
server = {
enabled = true;
stun_listen_addr = "0.0.0.0:3478";
# Region code and name are displayed in the Tailscale UI to identify a DERP region
region_code = "headscale";
region_name = "Headscale Embedded DERP";
region_id = 999;
};
urls = [ ];
paths = [ ];
auto_update_enabled = false;
update_frequency = "6h";
};
};
};
nginx.virtualHosts.${domain} = {
forceSSL = true;
enableACME = true;
quic = true;
http3 = true;
locations = {
"/" = {
proxyPass = "http://localhost:${toString config.services.headscale.port}";
proxyWebsockets = true;
};
"/metrics" = mkIf metricsEnable {
proxyPass = "http://${toString config.services.headscale.settings.metrics_listen_addr}/metrics";
};
};
extraConfig = ''
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
'';
};
};
systemd.services = {
tailscaled.after = [ "headscale.service" ];
headscale = {
environment = {
# required to use ssh
HEADSCALE_EXPERIMENTAL_FEATURE_SSH = "1";
};
};
};
services.headscale.settings.acl_policy_path = pkgs.writeTextFile {
name = "headscale-acl.hujson";
text = builtins.toJSON {
acls = [
(mkAcl [ "tag:client" ] [
"tag:client:*"
"tag:server:*"
]) # client -> {client, server}
(mkAcl [
"tag:client"
"tag:server"
] [ "tag:backup:${toString options.modules.server.rsync-daemon.port.default}" ])
];
ssh = [
(mkSshAcl [ "tag:client" ] [
"tag:server"
"tag:client"
] [ "ny" ]) # client -> {client, server}
];
tags = [
"tag:client"
"tag:server"
"tag:backup"
];
tagOwners =
let
users = [ "ny" ];
tags = map (name: "tag:${name}") [
"server"
"client"
"backup"
];
in
lib.genAttrs tags (_: users);
autoApprovers = {
exitNode = [ "*" ];
};
};
};
}