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
'';
})
]);
};
}
+5 -3
View File
@@ -10,6 +10,8 @@ cachix:
caching:
attic:
token: ENC[AES256_GCM,data:8omssG3GwCFIegfz+8IAGGhFGj01RB3dqqHeFpmZOzMJUshIDvSRuTTpGFhUBC7Xue8h09hAhpirIHmqzyG3I+e2Se/VZZoByXmpyIKesl3+NqOXDkJvgImqhvFVkTiSe5p/vSN3slWDylfkThQ0hZYw5mB9J13M5965iUnWcRbg+1fYFdTuSgrHY8Rxt4da0287A0YGnsN63k7j32XOJndxsRoOLoo+IQ+X+hiPOJkfGYxY0MglnxaxhPwH8SP1V+p78N75Z2npOtMdEikdHmj/NmKbqUXN2P0+IXthxV17WePCulZVsKC1Jw+clgbyAvHcQeVG/yyrcb1CRRQpszHtq1Pz7DHvfAG+gxyPNyP7D6oQNT8foX4C6CwuHgYQtM1x0D6oAL+lppQWJ0kEV/GDlJSXQnp/aBbVAqDmqS0TCx40nVmQ0PvMcjtsiZJigkRJRNLCg6n+qmhc5Rh9RhslPN5JXU0orWs9QYAoLXzdDDGP/R9tlEhwQBxwGrFAp016iilqPavMdI8txrWWdvezQuAh//eeW5LQSa6t363VCjX8phnXeJltOgXYlyuKnCCmv0a6XwhmT0PA+32/F0BxTf9lcZConpurlvOHdznaVeUXcFOEwKouDC7smPIZZqcRU8OIbWs7YXqMgatgb/bJVtB0P0Avsj9t9Uz8Dv8xBV+90U5qwM7HV16FIERorDquzgKFcvtb8/QfjTINoswpHZKNCbmQPxfJYPheJFwMQGFn+b+ecv+Z7qng9JEujJSNtEPv2CIuVmSxZJaU5g2CMu3rFGIA3qF81Bf1Ri8n+KYWgOKpQt11nClouv2XePO8JKI6fslF411zJ8zD4E/6Qg95UWhLh0RG2cXzYSXXvrpXDlIe9spc4OLuj4tFtXkiZZvfM5MgRTtoh93soypUpEbswTji2UprC3OPikjIIW49YysGVsH2100/67HbtinRoazM1M+DjaD2pMryx7kW/oVpyaW61wiqtHk9nq8vqROLWBhQxzGSh9157z/46AT+8PN89gFh5uNdFuhFz8e8/HIV3HtIrzrtR+flJfHJ1ZT5dhTDicSMiC/DhG/hupX4GHGX6zlaMgBqB8bKxxvs+v0iHfSkDIkuenZ+nTD72DP5yuIQVIwGV16CZA6rusjb1zLn6QYpvQtCuqlih+epsGNEYP6B3rvNMc/N7JcwY4YMTK+C46EC9mXhpfPn0a5OdD4kQ6s=,iv:+g9W5MzgtLppD1K3dZ/tCuaMxaa194W3Lf23/jUmDvk=,tag:5uVwIOkB2+MRHPGlKGQtGg==,type:str]
tailscale:
auth-key: ENC[AES256_GCM,data:ffBLc/LIm67P/DUSvDUek/qWHLfQbmSE3jQL1/TLHrZEEGk+HqEO4Prlj5bGGAU37tiHaD7Ksj1wqX3U+Q==,iv:Uw1tnuxMPZVwqvrRkZZusJM/a7QeoggR047+CKx9fnY=,tag:kS1IixpXYm1QVBIAmmgQ6Q==,type:str]
sops:
age:
- enc: |
@@ -75,7 +77,7 @@ sops:
6POXxpxjGhlWJaV47jqeN+7mQY2oTHE/x4raoX/KA2ouXL29K8QpmA==
-----END AGE ENCRYPTED FILE-----
recipient: age1pewusvlcgzcnk0kpskgc9qr6jlq8s2yzwnqrnh84p7v5z0kj3qhsya8h2x
lastmodified: "2026-03-24T00:15:02Z"
mac: ENC[AES256_GCM,data:dYTwO5DtkKinTKfBXGuvXRFxl8yavxXMKTw27M5/GcK/kkstHBG119IRk9B9KC6s6IHTY81U3MeUxE9XwdBiE7q4m15+ZO2vmdBVhN8wAh+82P9BP0HSaxLkjWLeKWBfULyLX/YXmQVsr09/NUEVSZcugJ6m40Ta+X9AQgO+cyA=,iv:FmsznsKTuIr61s3Zn0QZKSKvb/e2AljEB1ijKE52RKk=,tag:rHF2Xi4iP9VF33rxpBr5pg==,type:str]
lastmodified: "2026-06-10T03:00:15Z"
mac: ENC[AES256_GCM,data:F1DU7Sj9lsTOHWlyuY4L4N+urrBXv2GLKhlzohMA9iF6/bphRABa9we3WZX9MFNIc2Te+2SCh3f6Rv5eHwWSsZVdD4iwN/6HTabdNPTEOZyk/OVsap/F374MLu8ys3OVZ5vzg2PYCGmhlf4PQMrjvaz+CV3t8UezB6E4U0N3RuU=,iv:wT2ieUON5VInJhDtZFLENzyaty5TqcisLmYePnCHGM4=,tag:z9CYpCWa/XdSqPKMG3M41A==,type:str]
unencrypted_suffix: _unencrypted
version: 3.12.1
version: 3.13.1