Files
headscale/nix/testkit.nix
T
Kristoffer Dalby 200c2c01e8 nix: add a NixOS test kit for Tailscale clients
nixosModules.testkit makes a node a control server every client joins without
trust setup; testkit-peer joins it with hs-join.
2026-09-28 21:42:19 +02:00

79 lines
2.4 KiB
Nix

# Makes a NixOS test node a headscale control server that any Tailscale client
# joins without trust setup: plain-HTTP control, plus the same router on a
# self-signed TLS :443 whose DERP region is InsecureForTests. That is the one
# shape tailscaled, tsnet, webpki-only tailscale-rs and the Android app all
# accept. Contract: nix/README.md.
self:
{
config,
lib,
pkgs,
...
}:
let
hs-authkey = pkgs.writeShellApplication {
name = "hs-authkey";
runtimeInputs = [
config.services.headscale.package
pkgs.jq
];
text = ''
user=''${1:?usage: hs-authkey USER [headscale preauthkeys create flags]}
shift
# Safe straight after start_all(): the CLI retries the socket until this.
HEADSCALE_CLI_TIMEOUT=''${HEADSCALE_CLI_TIMEOUT:-60s} headscale health >/dev/null
headscale users list --name "$user" -o json | jq -e 'length > 0' >/dev/null ||
headscale users create "$user" >/dev/null
headscale preauthkeys create --user "$user" --reusable --expiration 24h "$@"
'';
};
in
{
key = "headscale-testkit";
_file = ./testkit.nix;
imports = [ self.nixosModules.headscale ];
services.headscale = {
enable = true;
package = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.headscale;
address = "[::]";
port = 80;
settings = {
server_url = "http://${config.networking.hostName}";
dns.base_domain = lib.mkDefault "tailnet";
dns.override_local_dns = lib.mkDefault false;
# Nothing stored allows all, and `headscale policy set` works live.
policy.mode = lib.mkDefault "database";
derp = {
urls = [ ];
server = {
enabled = true;
region_id = 999;
region_code = "headscale";
region_name = "headscale test kit";
stun_listen_addr = "[::]:3478";
};
};
};
};
# DERP rides this listener, and so does noise: Go clients redial only :443
# for a hostname URL after a recent dial.
systemd.services.headscale = {
environment.HEADSCALE_DEBUG_INSECURE_TLS_LISTEN_ADDR = "[::]:443";
# The module grants this only for a privileged main port.
serviceConfig.AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
serviceConfig.CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
};
networking.firewall = {
allowedTCPPorts = [
80
443
];
allowedUDPPorts = [ 3478 ];
};
environment.systemPackages = [ hs-authkey ];
}