Update preauthkey and join script
This commit is contained in:
+60
-5
@@ -13,7 +13,16 @@ let
|
|||||||
myKey = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL4qWNZnYU5JaqR9c2qh6oFZktzVNBOx42PfDaGofCL6 cnclack@pm.me macbook" ];
|
myKey = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL4qWNZnYU5JaqR9c2qh6oFZktzVNBOx42PfDaGofCL6 cnclack@pm.me macbook" ];
|
||||||
|
|
||||||
# Headscale preauth key. Paste/replace here.
|
# Headscale preauth key. Paste/replace here.
|
||||||
headscaleAuthKey = "hskey-auth-zZdWiKTwDfTT-tgRKiAHyXabykoARDDyNfYihgVrJQ6y6zK7lwV0FW6xwHTdyWoqa_ZpCf80vgAJC";
|
#
|
||||||
|
# This MUST be a *reusable* key with a long expiry (1 year is good), created
|
||||||
|
# with:
|
||||||
|
# headscale preauthkeys create --user <user-id> --reusable --expiration 8760h
|
||||||
|
#
|
||||||
|
# A single-use key only ever enrolls one node, which would mean a fresh key
|
||||||
|
# plus a rebuild for every kiosk that joins the fleet. A reusable key lets any
|
||||||
|
# number of kiosks enroll unattended, and the timer below retries the join so
|
||||||
|
# a node that misses its window at boot heals on its own.
|
||||||
|
headscaleAuthKey = "hskey-auth-UfU_eS_sdOu6-CjrqrVdDJFRFsPnaTtK5sFuCiVlffJAS7uQ1wX1_65MNk205WGsF4clOiBD3ZOV-";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
############################################################
|
############################################################
|
||||||
@@ -115,7 +124,9 @@ in
|
|||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
RemainAfterExit = true;
|
# Deliberately no RemainAfterExit: the unit has to fall back to inactive
|
||||||
|
# so the timer below can trigger it again. The script is idempotent, so
|
||||||
|
# re-running it on an already-joined kiosk is a cheap no-op.
|
||||||
};
|
};
|
||||||
path = [ pkgs.tailscale pkgs.coreutils pkgs.gnused pkgs.gnugrep ];
|
path = [ pkgs.tailscale pkgs.coreutils pkgs.gnused pkgs.gnugrep ];
|
||||||
script = ''
|
script = ''
|
||||||
@@ -144,7 +155,17 @@ in
|
|||||||
| cut -c1-63)"
|
| cut -c1-63)"
|
||||||
[ -n "$serial" ] || serial="radiantwave-unknown"
|
[ -n "$serial" ] || serial="radiantwave-unknown"
|
||||||
|
|
||||||
echo "radiantwave-tailscale-auth: joining as '$serial'"
|
# Already joined? Bail out early. This is what makes the periodic
|
||||||
|
# re-invocation from the timer below cheap and safe to run forever.
|
||||||
|
# The JSON is pretty-printed with a space, so allow optional whitespace.
|
||||||
|
state="$(tailscale status --json 2>/dev/null \
|
||||||
|
| sed -n 's/.*"BackendState": *"\([A-Za-z]*\)".*/\1/p')"
|
||||||
|
if [ "$state" = "Running" ]; then
|
||||||
|
echo "radiantwave-tailscale-auth: already joined as '$serial'"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "radiantwave-tailscale-auth: joining as '$serial' (state: ''${state:-unknown})"
|
||||||
for _ in $(seq 1 30); do
|
for _ in $(seq 1 30); do
|
||||||
if tailscale up \
|
if tailscale up \
|
||||||
--auth-key=${pkgs.lib.escapeShellArg headscaleAuthKey} \
|
--auth-key=${pkgs.lib.escapeShellArg headscaleAuthKey} \
|
||||||
@@ -158,11 +179,45 @@ in
|
|||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "radiantwave-tailscale-auth: could not join tailnet after retries" >&2
|
# Best-effort by design: do NOT exit non-zero here.
|
||||||
exit 1
|
#
|
||||||
|
# This unit is part of the new generation that switch-to-configuration
|
||||||
|
# starts, and *any* failing unit among those aborts the switch with
|
||||||
|
# status 4/NOPERMISSION. While the preauth key was expired that is exactly
|
||||||
|
# what happened: this unit failed, which failed `switch-to-configuration`,
|
||||||
|
# which failed nixos-upgrade.service -- so the tailscale key silently
|
||||||
|
# stopped *all* system upgrades on the fleet every 10 minutes.
|
||||||
|
#
|
||||||
|
# The retry timer below is what actually recovers the join, so a failed
|
||||||
|
# attempt here must never take down the system switch with it.
|
||||||
|
echo "radiantwave-tailscale-auth: could not join tailnet after retries;" >&2
|
||||||
|
echo "radiantwave-tailscale-auth: leaving the join to the retry timer" >&2
|
||||||
|
exit 0
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# The join only needs to happen once per node, but it fails for reasons
|
||||||
|
# outside our control: the network may not be usable yet at boot, tailscaled
|
||||||
|
# may be restarted later, or the preauth key may be rotated out from under
|
||||||
|
# the fleet. A timer re-runs the (idempotent) join so a kiosk recovers on its
|
||||||
|
# own instead of needing a manual `systemctl restart`.
|
||||||
|
#
|
||||||
|
# This replaces the old behavior where the one-shot unit failed once at boot
|
||||||
|
# and stayed failed forever (Restart=no, NRestarts=0).
|
||||||
|
systemd.timers.radiantwave-tailscale-auth = {
|
||||||
|
description = "Periodically ensure the kiosk is joined to the tailnet";
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
|
timerConfig = {
|
||||||
|
# The join is attempted at boot via multi-user.target above; this is the
|
||||||
|
# first retry in case that one lost the race with the network.
|
||||||
|
OnBootSec = "5min";
|
||||||
|
# Measured from when the unit last went inactive, so a slow join doesn't
|
||||||
|
# immediately retrigger.
|
||||||
|
OnUnitInactiveSec = "10min";
|
||||||
|
Unit = "radiantwave-tailscale-auth.service";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
system.autoUpgrade = {
|
system.autoUpgrade = {
|
||||||
enable = true;
|
enable = true;
|
||||||
flake = upgradeFlakeRef;
|
flake = upgradeFlakeRef;
|
||||||
|
|||||||
Reference in New Issue
Block a user