nixos/tailscale: tailscaled-autoconnect use Type=notify, wait for Running

Previously the `Starting` state was missed, allowing the service to
complete before the interface was ready, causing services that bind to
Tailscale IPs to fail to start.

Now waits for Tailscale to report `Running` and notifies systemd
accordingly.

Switch the unit to Type=notify to timeout if there is no connection.

Remove `NeedsMachineAuth` gating since it requires client approval in
the console.
This commit is contained in:
Scott Edlund
2025-09-15 17:55:41 +09:00
parent c23193b943
commit b67bf1607c
+36 -10
View File
@@ -84,6 +84,8 @@ in
description = ''
A file containing the auth key.
Tailscale will be automatically started if provided.
Services that bind to Tailscale IPs should order using {option}`systemd.services.<name>.after` `tailscaled-autoconnect.service`.
'';
};
@@ -116,7 +118,7 @@ in
extraUpFlags = mkOption {
description = ''
Extra flags to pass to {command}`tailscale up`. Only applied if `authKeyFile` is specified.";
Extra flags to pass to {command}`tailscale up`. Only applied if {option}`services.tailscale.authKeyFile` is specified.
'';
type = types.listOf types.str;
default = [ ];
@@ -183,12 +185,15 @@ in
wants = [ "tailscaled.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
Type = "notify";
};
# https://github.com/tailscale/tailscale/blob/v1.72.1/ipn/backend.go#L24-L32
path = [
cfg.package
pkgs.jq
];
enableStrictShellChecks = true;
script =
let
statusCommand = "${lib.getExe cfg.package} status --json --peers=false | ${lib.getExe pkgs.jq} -r '.BackendState'";
paramToString = v: if (builtins.isBool v) then (lib.boolToString v) else (toString v);
params = lib.pipe cfg.authKeyParameters [
(lib.filterAttrs (_: v: v != null))
@@ -197,14 +202,35 @@ in
(params: if params != "" then "?${params}" else "")
];
in
# bash
''
while [[ "$(${statusCommand})" == "NoState" ]]; do
sleep 0.5
getState() {
tailscale status --json --peers=false | jq -r '.BackendState'
}
lastState=""
while state="$(getState)"; do
if [[ "$state" != "$lastState" ]]; then
# https://github.com/tailscale/tailscale/blob/v1.72.1/ipn/backend.go#L24-L32
case "$state" in
NeedsLogin)
echo "Server needs authentication, sending auth key"
tailscale up --auth-key "$(cat ${cfg.authKeyFile})${params}" ${escapeShellArgs cfg.extraUpFlags}
;;
Running)
echo "Tailscale is running"
systemd-notify --ready
exit 0
;;
*)
echo "Waiting for Tailscale State = Running or systemd timeout"
;;
esac
fi
echo "State = $state"
lastState="$state"
sleep .5
done
status=$(${statusCommand})
if [[ "$status" == "NeedsLogin" || "$status" == "NeedsMachineAuth" ]]; then
${lib.getExe cfg.package} up --auth-key "$(cat ${cfg.authKeyFile})${params}" ${escapeShellArgs cfg.extraUpFlags}
fi
'';
};