From 5d16fb00c0dbbd7a4addb67401e13c0ce5d90483 Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Fri, 13 Feb 2026 04:35:47 -0800 Subject: [PATCH] nixos: tailscale auto-connect via agenix auth key --- nixos/imalison.nix | 19 +++++++++++ nixos/secrets/secrets.nix | 4 +++ nixos/secrets/tailscale-authkey.age | 43 ++++++++++++++++++++++++ nixos/tailscale.nix | 51 ++++++++++++++++++++++++++++- 4 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 nixos/secrets/tailscale-authkey.age diff --git a/nixos/imalison.nix b/nixos/imalison.nix index e94758a5..40bea132 100644 --- a/nixos/imalison.nix +++ b/nixos/imalison.nix @@ -149,6 +149,25 @@ }; }; + systemd.user.services.tailscale-systray = { + Unit = { + Description = "Tailscale systray"; + After = [ "graphical-session.target" "tray.target" ]; + PartOf = [ "graphical-session.target" ]; + Requires = [ "tray.target" ]; + }; + + Service = { + ExecStart = "${pkgs.tailscale}/bin/tailscale systray"; + Restart = "on-failure"; + RestartSec = 3; + }; + + Install = { + WantedBy = [ "graphical-session.target" ]; + }; + }; + xdg.desktopEntries.google-chrome-devtools = { name = "Google Chrome (DevTools)"; genericName = "Web Browser"; diff --git a/nixos/secrets/secrets.nix b/nixos/secrets/secrets.nix index 07e12bd7..31c54fd7 100644 --- a/nixos/secrets/secrets.nix +++ b/nixos/secrets/secrets.nix @@ -25,4 +25,8 @@ in "org-api-ssh-key.age".publicKeys = keys.hostKeys ++ keys.kanivanKeys ++ keys.railbird-sf; "google-assistant-integration-service-key.age".publicKeys = keys.hostKeys ++ keys.kanivanKeys; "zwave-js.json.age".publicKeys = keys.hostKeys ++ keys.kanivanKeys; + + # Optional Tailscale pre-auth key for unattended enrollment. + # This is safe to commit encrypted; default plaintext is "DISABLED". + "tailscale-authkey.age".publicKeys = keys.kanivanKeys; } diff --git a/nixos/secrets/tailscale-authkey.age b/nixos/secrets/tailscale-authkey.age new file mode 100644 index 00000000..6e0f9f02 --- /dev/null +++ b/nixos/secrets/tailscale-authkey.age @@ -0,0 +1,43 @@ +age-encryption.org/v1 +-> ssh-rsa gwJx0Q +FvsNlNlVhNMmvbbOlPTC49Db7OMVIxCrRCamo5A1jSZQ3IWf6XbyErhzSLQY3Gfw +zEwt8HegamDqsZu28y4V6g2ibLCAK7cGMHQXnfBBQbKK0KriJZdGCCaQo9wSZBAf +A05WHB0NHuLVEHXWhQc5fAdKz/pwTRD9NOXQCqZ76IDha/jcL9UwjFmb6zOWaM65 +FDLPiNBgvGpAhEWcWpQ833lfk/2ixlOwZ6NeXker7U72RZWHaDAWullnLzqnLcoa +Kz5mD9N6BnAS1DpCyDUYrQkyPAkcXM5CPs57efN5/u+wyyyCa2tlXiPbVSQ9OEiE +spDiBTzqak978FiP5d3FMA +-> ssh-ed25519 YFIoHA x6bVPZ5yMz6r22ZNLchRsEa9LqiYd3qG2GTSSOgL9QY +PIIV7mv82vo0iVF+FbCEe0Wqfx93GBP3qi/MPXjyddI +-> ssh-ed25519 KQfiow lllzjgCHkHWBzynEUHSQNiUYltbRxw2juRmzOcswr3I +Rz9yf8YadqxSWoCmVcByKjF0Jve1pRCC6EEYhM8B7fs +-> ssh-ed25519 kScIxg k7UXiHGM4vS8CBTXGi5GVWIbK/kevYuus5nhB5H4ChI +yJmfqVIxkfeqUNx+yBfoBnQ1liXTGlOTEObmDL4/B0M +-> ssh-ed25519 HzX1zw EmVR4oAFYSdjxQjPIAV1/MGs/p75bYgDLgtzAnbvCl8 +yUZcTll16d4oRwx87+o2twEB5jbkkMr7SCXIkVHQkMw +-> ssh-ed25519 KQfiow bVHXOFAcHmDQnJTOkvAa2yqB1B2Q+Th6/8+/hAQzym0 +HXlTQupGi2HGwL1YYBWdyVIeB6kHzfuPI6EmlL4n4hk +-> ssh-ed25519 1o2X0w yJ1ZYo+Ivml5SUg8x3FC9Z5ScoemWbxPXYeH1A77vT8 +PIL0dOiZJ/MZJtuHdqRtixxGOYhWmQ9L0gnZ7blCM1I +-> ssh-ed25519 KQ5iUA STHZY7izxKR7bhmRWoLvqJo2KhqAFguy1w2ybF3LvBo +zX/mc5bPR1P7PWSzdD6HzQMeiGU2iyRfl46GIdztv2s +-> ssh-ed25519 AKGkDw 2Tsz3GP4MoVSfqyapthWeXAdOjYiJm/n497m12sKOUM +1z8W39Vp39gkHeFG4IrWSqnyDfTk/faGOqXVR0cgYeU +-> ssh-ed25519 0eS5+A Ys6bSdBmt8tFmb+0w3aOvhD7AskFXPZvey3mlR6yh1k +K7A+9MR8E4pi3oE5/BaS75NG8jQitT4SgghUBsTKOWs +-> ssh-ed25519 9/4Prw a6Hn8su2hMt0M1n361VwSehJb5kpoAmuqSdbtL3nZE4 +G8CVhYjq1BwT1k6Fg+dWKy5iwJSZH1XPWq42Ut6DGwQ +-> ssh-ed25519 gAk3+Q Ojgh8mIG055OCOgmzy+ONM5hAwepuSW6Y6NQZ/lKmWg +R6CFZdFNWvGLeDRjruMUtU/VkASUrgBRckS3UKTQJBk +-> ssh-ed25519 X6eGtQ TC7xl6oPV3OfcRtUAh1cVZFwlXUPpi1AdeucGv/U5RQ +ItNKgWhglMLdWXsZuFc3wBzaHIWDWH1P8M5WGTxOZRE +-> ssh-ed25519 0ma8Cw uYMGharDkTjlsd47H6664Z+c6w4B4QyxYx/WCG5vVyQ +jvhGw3qcTR2OaqMoMLGhu9ZbjFk1oVBLCmPSByUdzxM +-> ssh-ed25519 Tp0Z1Q bGhWGaTFhZF7L5ZKrkvLTPsb0Y0FDnSl/3YUzVTEqXY +2JBDa7pUzUWTvjErVKKs2AUO36UTvJ0bVcp8dcjVrLk +-> ssh-ed25519 ePNWZQ wF/LEqfLfanzl0V4FVZTv6b0t/l1nPnfxCSHuFFBMwA +ask3AqAmkTp02Y+ZxwT1qTo8nqXlqdFVmsKRKmjfZ0c +-> ssh-ed25519 hILzzA x5K6ekDUTem831ZVB1232b21D9qlrs5wbDPTpvO+1jQ +qTwvmdBz82DHpx53P/OrAP0sK3R325pN64+umM18qM8 +--- Py2A4BqLJSNtJ3C7ZmuyV1jpxo6aLirCj2z2egPQdQ4 +Ւ|ASh[N?c^kһT +"F혋*u \ No newline at end of file diff --git a/nixos/tailscale.nix b/nixos/tailscale.nix index 72e71af2..2cd5a418 100644 --- a/nixos/tailscale.nix +++ b/nixos/tailscale.nix @@ -6,5 +6,54 @@ makeEnable config "myModules.tailscale" true { # Handy even if you only enable the service and run `tailscale up` manually. environment.systemPackages = [ pkgs.tailscale ]; -} + # Optional: unattended enrollment using a pre-auth key stored in agenix. + # + # Plaintext content "DISABLED" means "do nothing". + age.secrets.tailscale-authkey = { + file = ./secrets/tailscale-authkey.age; + owner = "root"; + group = "root"; + mode = "0400"; + }; + + systemd.services.tailscale-autoconnect = { + description = "Auto-connect Tailscale (optional, via agenix auth key)"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" "tailscaled.service" "agenix.service" ]; + wants = [ "network-online.target" "tailscaled.service" ]; + + unitConfig = { + ConditionPathExists = config.age.secrets.tailscale-authkey.path; + }; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + + script = '' + set -euo pipefail + + key_file='${config.age.secrets.tailscale-authkey.path}' + if [ ! -s "$key_file" ]; then + exit 0 + fi + if [ "$(cat "$key_file")" = "DISABLED" ]; then + exit 0 + fi + + state="$(${pkgs.tailscale}/bin/tailscale status --json 2>/dev/null | ${pkgs.jq}/bin/jq -r '.BackendState // empty' || true)" + if [ "$state" = "Running" ]; then + exit 0 + fi + + # First-time (or post-logout) login. + ${pkgs.tailscale}/bin/tailscale up \\ + --auth-key "file:$key_file" \\ + --accept-dns=true \\ + --operator=imalison \\ + --timeout=60s + ''; + }; +}