name the control server explicitly, and log out before moving between them

Two gaps found by tracing the assembled command rather than assuming it, and the
first meant option 3 did not work at all on a machine like this one.

`tailscale up` with no --login-server keeps whatever ControlURL is already
stored. So on a node already pointed at a self-hosted server — which this host is
— choosing "the easy route" left it exactly where it was. No error, no message,
and a summary line claiming it had connected. The URL is now passed explicitly in
both cases, TS_DEFAULT_CONTROL_URL for Tailscale's own service.

And a node logged in to one coordination server cannot simply be pointed at
another; it has to be logged out first. That is now detected by comparing the
stored URL with the target, and offered rather than done quietly — the tailnet
drops while it happens, and the run says so, because on a machine reached over
the tailnet that is the session you are reading this in. Declining leaves the
node where it is and records that.

Verified all three paths with tailscale stubbed: switching logs out then connects
to controlplane.tailscale.com, declining leaves it on offscale, and reconnecting
to the SAME server offers no logout at all.

Also noted while tracing: lan_cidr correctly finds nothing on this host, since a
/32 with host routes has no subnet to advertise. That means the homelab
subnet-router prompt is the one path here that has not been exercised on real
hardware.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-12 20:29:26 +00:00
co-authored by Claude Opus 5
parent 22a661131d
commit 06492c3297
2 changed files with 46 additions and 5 deletions
@@ -169,6 +169,22 @@ tailscale_control_url() {
tailscale_install() { curl -fsSL https://tailscale.com/install.sh | sh; }
# Tailscale's own coordination server, spelled out.
#
# Passed explicitly even when it is the default, because `tailscale up` with no
# --login-server keeps whatever ControlURL is already stored. On a node already
# pointed at a self-hosted server, choosing "the easy route" would otherwise
# leave it exactly where it was — no error, no message, wrong answer.
TS_DEFAULT_CONTROL_URL="https://controlplane.tailscale.com"
# Moving a node between coordination servers is not something `up` will do while
# it is logged in to one. Logging out first is the documented way, and doing it
# unasked would be worse than saying so.
tailscale_needs_logout() {
local current="$1" target="$2"
[[ -n "$current" && -n "$target" && "$current" != "$target" ]]
}
# Routing has to be on before this machine can forward anyone else's packets,
# whether as an exit node or as a subnet router. Written as a drop-in so it is
# visible as this script's doing.