From 2181e87a0fad91ab2920f7a799dc0a85d709e5d3 Mon Sep 17 00:00:00 2001 From: claude-hdb Date: Sun, 12 Jul 2026 15:47:38 +0000 Subject: [PATCH] fix(bootstrap): converge the tailnet hostname on an already-joined box MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `bootstrap` skips `tailscale up` when the box has already joined — which is what lets a re-run work without spending a pre-auth key. But skipping it also skipped `--hostname`, so the TAILNET name never converged: a box that joined under the wrong name (e.g. `--hostname` omitted, defaulting to the ROLE) stayed misnamed permanently, and re-running rig — the documented repair path — could not fix it. rig is convergent by contract; this was the one field that wasn't. The already-joined path now compares the current tailnet hostname against `--hostname` and reconciles it with `tailscale set --hostname` (no re-auth, no key). A failure warns rather than dying: a cosmetic name is not worth failing a bootstrap over. Safe by construction: Tailscale ACLs cannot bind a rule's dst to a hostname (it must be a tag, an IP, or a `hosts` alias — which is precisely why acl.hujson pins coolify-box to an IP), so a rename cannot silently void a grant. It also will not clobber a deliberate rename — a machine renamed in the admin console keeps that name and is no longer overridden by the device hostname. Found in the same session as the sshd first-wins bug (#6): an operator ran `bootstrap control-plane` on the prod box without `--hostname`, and no re-run could undo the resulting name. Co-Authored-By: Claude Opus 4.8 --- README.md | 8 ++++++++ commands/bootstrap.sh | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/README.md b/README.md index d3eb3ed..ef7b859 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,14 @@ enables periodic unattended upgrades); writes an sshd hardening drop-in **verifies it took effect** via `sshd -T`; sets the system hostname; installs tailscale and joins your tailnet. +**`--hostname` converges both names.** On a box that has already joined, +`bootstrap` skips `tailscale up` (so a re-run needs no pre-auth key) — but it +still reconciles the **tailnet** hostname via `tailscale set --hostname`. Without +that, a box which joined under the wrong name — say `--hostname` was omitted, so +it defaulted to the *role* — stayed misnamed forever, and re-running rig, the +documented repair, could not fix it. A machine you deliberately renamed in the +admin console keeps that name; rig will not fight it. + > **Why the drop-in is `00-rig.conf` and not `99-`.** `sshd_config` is > **first-wins** — *"for each keyword, the first obtained value will be used"* > (`sshd_config(5)`) — and `Include` expands its glob in lexical order. Cloud diff --git a/commands/bootstrap.sh b/commands/bootstrap.sh index a76f4e5..22e171d 100755 --- a/commands/bootstrap.sh +++ b/commands/bootstrap.sh @@ -170,6 +170,26 @@ if ! command -v tailscale >/dev/null 2>&1; then fi if tailscale status >/dev/null 2>&1; then log "tailnet already joined; skipping tailscale up (no pre-auth key needed)" + # ...but skipping `tailscale up` also skipped --hostname, so the TAILNET name + # never converged: a box that joined under the wrong name (e.g. --hostname + # omitted, so it defaulted to the ROLE) stayed misnamed forever, and re-running + # rig — the documented repair — could not fix it. rig is convergent by + # contract; this was the one field that wasn't. `tailscale set` converges it + # without a re-auth or a pre-auth key. + # + # Safe by construction here: Tailscale ACLs cannot bind a rule's dst to a + # hostname (it must be a tag, an IP, or a `hosts` alias — which is exactly why + # acl.hujson pins coolify-box to an IP), so a rename cannot silently void a + # grant. It also will NOT clobber a deliberate rename: a machine renamed in the + # admin console keeps that name, and the device hostname no longer overrides it. + current_ts_name="$(tailscale status --peers=false 2>/dev/null | awk 'NR==1 {print $2}')" + if [ -n "$current_ts_name" ] && [ "$current_ts_name" != "$TS_HOSTNAME" ]; then + log "tailnet hostname is '${current_ts_name}', want '${TS_HOSTNAME}' — converging" + tailscale set --hostname="$TS_HOSTNAME" \ + || warn "tailscale set --hostname failed; rename '${current_ts_name}' -> '${TS_HOSTNAME}' in the admin console" + else + log "tailnet hostname already ${TS_HOSTNAME}" + fi else # env override, else prompt; never touches disk if [ -z "${TS_AUTHKEY:-}" ]; then -- 2.45.2