111 lines
3.9 KiB
Nix
111 lines
3.9 KiB
Nix
{ ... }: {
|
|
flake.nixosModules.systemTailscale =
|
|
{ config, lib, ... }:
|
|
let
|
|
cfg = config.chiasson.system.networking.tailscale;
|
|
tsCfg = config.services.tailscale;
|
|
in
|
|
{
|
|
options.chiasson.system.networking.tailscale = {
|
|
enable = lib.mkEnableOption ''
|
|
Tailscale mesh VPN. Joins this host to your tailnet for encrypted access
|
|
from anywhere without opening inbound ports on your router.
|
|
'';
|
|
|
|
authKeyFile = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
example = "/run/secrets/tailscale/auth-key";
|
|
description = ''
|
|
Reusable Tailscale auth key (sops path). Create one at
|
|
https://login.tailscale.com/admin/settings/keys — enable Reusable and
|
|
Pre-approved. Store under `tailscale/auth-key` in secrets/secrets.yaml.
|
|
'';
|
|
};
|
|
|
|
openFirewall = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = "Allow Tailscale UDP 41641 through the host firewall.";
|
|
};
|
|
|
|
acceptRoutes = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = ''
|
|
Accept subnet routes advertised by other tailnet nodes (e.g. home LAN
|
|
via nix-server). Enable on portable clients like t2mbp.
|
|
'';
|
|
};
|
|
|
|
subnetRouter = {
|
|
enable = lib.mkEnableOption ''
|
|
Advertise the home LAN through this node so other tailnet devices can
|
|
reach local IPs (SSH, Attic, Gitea, Jellyfin, etc.). Enable on an
|
|
always-on home host (nix-server). Approve the route in the Tailscale
|
|
admin console after first connect.
|
|
'';
|
|
|
|
routes = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ "192.168.2.0/24" ];
|
|
example = [ "192.168.2.0/24" ];
|
|
description = "Subnets to advertise to the tailnet.";
|
|
};
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable (lib.mkMerge [
|
|
{
|
|
services.tailscale = {
|
|
enable = true;
|
|
openFirewall = cfg.openFirewall;
|
|
authKeyFile = cfg.authKeyFile;
|
|
};
|
|
|
|
networking.firewall.trustedInterfaces = lib.mkAfter [ "tailscale0" ];
|
|
}
|
|
(lib.mkIf cfg.acceptRoutes {
|
|
services.tailscale.useRoutingFeatures = "client";
|
|
services.tailscale.extraUpFlags = [ "--accept-routes" ];
|
|
})
|
|
(lib.mkIf cfg.subnetRouter.enable {
|
|
services.tailscale.useRoutingFeatures = "both";
|
|
services.tailscale.extraUpFlags =
|
|
map (route: "--advertise-routes=${route}") cfg.subnetRouter.routes;
|
|
})
|
|
(lib.mkIf (cfg.authKeyFile != null) {
|
|
# Upstream uses `$(cat $authKeyFile)` which breaks when sops leaves a trailing newline.
|
|
systemd.services.tailscaled-autoconnect.script = lib.mkOverride 50 ''
|
|
getState() {
|
|
tailscale status --json --peers=false | jq -r '.BackendState'
|
|
}
|
|
|
|
lastState=""
|
|
while state="$(getState)"; do
|
|
if [[ "$state" != "$lastState" ]]; then
|
|
case "$state" in
|
|
NeedsLogin|NeedsMachineAuth|Stopped)
|
|
echo "Server needs authentication, sending auth key"
|
|
tailscale up --auth-key "$(tr -d '\n\r' < ${cfg.authKeyFile})" ${lib.escapeShellArgs tsCfg.extraUpFlags}
|
|
;;
|
|
Running)
|
|
echo "Tailscale is running"
|
|
systemd-notify --ready
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Waiting for Tailscale State = Running or systemd timeout"
|
|
;;
|
|
esac
|
|
echo "State = $state"
|
|
fi
|
|
lastState="$state"
|
|
sleep .5
|
|
done
|
|
'';
|
|
})
|
|
]);
|
|
};
|
|
}
|