diff --git a/scripts/setup/machine-setup/lib/tailscale.sh b/scripts/setup/machine-setup/lib/tailscale.sh index 0f5172b9..fdc0bbdd 100644 --- a/scripts/setup/machine-setup/lib/tailscale.sh +++ b/scripts/setup/machine-setup/lib/tailscale.sh @@ -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. diff --git a/scripts/setup/machine-setup/machine-setup.sh b/scripts/setup/machine-setup/machine-setup.sh index c79b4953..1d10494f 100755 --- a/scripts/setup/machine-setup/machine-setup.sh +++ b/scripts/setup/machine-setup/machine-setup.sh @@ -530,11 +530,34 @@ if ! skip; then install_exit_node_tuning fi + # Where this node is being pointed, named explicitly in both cases — see + # TS_DEFAULT_CONTROL_URL for why the default is not left implicit. + if [[ "$TS_PLANE" == "self-hosted" ]]; then + TS_TARGET_URL="$TS_LOGIN_SERVER" + else + TS_TARGET_URL="$TS_DEFAULT_CONTROL_URL" + fi + + # A node logged in to one coordination server cannot simply be pointed at + # another; it has to be logged out first. Said out loud rather than done + # quietly, because it drops the tailnet for a moment. + TS_URL_NOW="$(tailscale_control_url)" + if [[ "$TS_STATE" == "Running" ]] && tailscale_needs_logout "$TS_URL_NOW" "$TS_TARGET_URL"; then + echo "" + warn "this node is on ${TS_URL_NOW}, and moving it to ${TS_TARGET_URL} means logging out first" + echo " The tailnet drops while that happens. If you are connected over" + echo " it right now, this session goes with it." + if confirm "Log out and move it?" n; then + tailscale logout || true + else + warn "left where it is" + SUMMARY+=("Tailscale: left on ${TS_URL_NOW}") + TS_CONNECT=false + fi + fi + echo "" - TS_ARGS=(up --timeout=60s) - # Only a self-hosted server needs pointing at. Option 3 is the default - # coordination server, which means passing no --login-server at all. - [[ "$TS_PLANE" == "self-hosted" ]] && TS_ARGS+=(--login-server "$TS_LOGIN_SERVER") + TS_ARGS=(up --timeout=60s --login-server "$TS_TARGET_URL") # Never passed empty. `--authkey ""` silently falls back to the interactive # flow and blocks forever, which is exactly how the original hung. [[ -n "$TS_AUTHKEY" ]] && TS_ARGS+=(--authkey "$TS_AUTHKEY") @@ -547,7 +570,9 @@ if ! skip; then fi echo "" - if tailscale "${TS_ARGS[@]}"; then + if [[ "$TS_CONNECT" == false ]]; then + : + elif tailscale "${TS_ARGS[@]}"; then TS_IP="$(tailscale_ip)" ok "connected as ${TS_IP} on $(hostname)" SUMMARY+=("Tailscale: ${TS_IP}${TS_SSH:+, Tailscale SSH}${TS_ROUTES:+, subnet router}${TS_EXIT:+, exit node}")