Add Tailscale support to NixOS configurations

This commit is contained in:
2026-06-11 12:51:09 -03:00
parent 691b71a5cd
commit 3dfaeb111a
5 changed files with 132 additions and 3 deletions
@@ -50,6 +50,9 @@
};
sops.secrets."users/server/hashedPassword".neededForUsers = true;
sops.secrets."tailscale/auth-key" = {
mode = "0400";
};
security.sudo.wheelNeedsPassword = true;
@@ -63,6 +66,11 @@
networking = {
hostName = "nix-server";
networkManager.enable = true;
tailscale = {
enable = true;
authKeyFile = config.sops.secrets."tailscale/auth-key".path;
subnetRouter.enable = true;
};
};
caching.attic = {
+8
View File
@@ -38,6 +38,9 @@
group = "users";
mode = "0400";
};
sops.secrets."tailscale/auth-key" = {
mode = "0400";
};
chiasson.system.librepods.enable = true;
chiasson.system.palera1n.enable = true;
@@ -106,6 +109,11 @@
networking = {
hostName = "t2mbp";
networkManager.enable = true;
tailscale = {
enable = true;
authKeyFile = config.sops.secrets."tailscale/auth-key".path;
acceptRoutes = true;
};
};
};
+1
View File
@@ -5,6 +5,7 @@
self.nixosModules.systemLocalization
self.nixosModules.systemFonts
self.nixosModules.systemNetworking
self.nixosModules.systemTailscale
self.nixosModules.systemLocalsend
self.nixosModules.systemChromiumHevcVaapi
self.nixosModules.systemMonitorInput
+110
View File
@@ -0,0 +1,110 @@
{ ... }: {
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
'';
})
]);
};
}