Fix #565: Tailscale — passwordless sudo, persistent auth, no more tower visits#631
Fix #565: Tailscale — passwordless sudo, persistent auth, no more tower visits#631
Conversation
…lear login flow Three fixes for grid resilience: 1. Passwordless sudo for tailscale during install (auto-reconnect on boot) 2. systemctl enable tailscaled (survives reboots) 3. Clear login prompt — no timeout, waits for browser auth, explains it's one-time After install.sh runs ONCE with auth, Tailscale auto-reconnects forever. No more physically visiting the tower.
There was a problem hiding this comment.
Pull request overview
Updates the Linux/WSL Tailscale install flow so nodes can authenticate once and then reliably come back online after reboots, aiming to improve “grid” resilience and reduce manual intervention.
Changes:
- Adds Linux/WSL setup for passwordless sudo for Tailscale commands and attempts to
systemctl enable tailscaled. - Improves Tailscale authentication UX messaging and switches to a blocking “login required” flow.
- Adjusts
tailscale upinvocation to include--accept-routes.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Run tailscale up — it will print the auth URL and wait for you to click it. | ||
| # No timeout. This must complete for the grid to work. | ||
| sudo tailscale up --ssh --accept-routes 2>&1 | ||
|
|
||
| local ts_ip=$(tailscale ip -4 2>/dev/null || echo "pending") | ||
| if [ "$ts_ip" != "pending" ] && [ -n "$ts_ip" ]; then | ||
| echo -e " ${GREEN}✅ Tailscale connected! IP: ${ts_ip}${NC}" | ||
| echo -e " ${GREEN} This node is now part of the mesh. It will auto-reconnect on reboot.${NC}" | ||
| else | ||
| echo -e " ${YELLOW}⚠️ Tailscale needs auth. Run: sudo tailscale up --ssh${NC}" | ||
| echo -e " ${RED}❌ Tailscale auth may have failed. Run manually: sudo tailscale up --ssh${NC}" | ||
| fi |
There was a problem hiding this comment.
sudo tailscale up ... is run under set -e; if it exits non-zero (user aborts, auth fails, already up with conflicting prefs), the whole install script will terminate before reaching the follow-up status/error messaging. Wrap it in an explicit error-handling block (capture exit code) so the script can print a clear next-step message instead of exiting abruptly.
|
|
||
| # Run tailscale up — it will print the auth URL and wait for you to click it. | ||
| # No timeout. This must complete for the grid to work. | ||
| sudo tailscale up --ssh --accept-routes 2>&1 |
There was a problem hiding this comment.
Passing --accept-routes changes network behavior by automatically accepting subnet routes advertised by the tailnet. This can unexpectedly route traffic and is a security-sensitive default; consider making it opt-in/configurable rather than enabling unconditionally during install.
| sudo tailscale up --ssh --accept-routes 2>&1 | |
| # By default we do NOT auto-accept subnet routes; this is security-sensitive. | |
| # To enable route acceptance, set CONTINUUM_TAILSCALE_ACCEPT_ROUTES=true (or 1). | |
| local tailscale_args="--ssh" | |
| if [ "${CONTINUUM_TAILSCALE_ACCEPT_ROUTES:-}" = "1" ] || [ "${CONTINUUM_TAILSCALE_ACCEPT_ROUTES:-}" = "true" ]; then | |
| echo -e " ${YELLOW} Note: --accept-routes enabled via CONTINUUM_TAILSCALE_ACCEPT_ROUTES${NC}" | |
| tailscale_args="$tailscale_args --accept-routes" | |
| else | |
| echo -e " ${YELLOW} Note: subnet routes will NOT be auto-accepted.${NC}" | |
| echo -e " ${YELLOW} To enable, set CONTINUUM_TAILSCALE_ACCEPT_ROUTES=true before running install.${NC}" | |
| fi | |
| sudo tailscale up $tailscale_args 2>&1 |
| # Linux/WSL: set up passwordless sudo for tailscale so it auto-starts on boot | ||
| # without prompting. This is critical for grid resilience — nodes must reconnect | ||
| # to the mesh automatically after reboots. | ||
| if [ "$PLATFORM" = "linux" ] || [ "$PLATFORM" = "wsl" ]; then | ||
| if [ ! -f /etc/sudoers.d/tailscale ]; then | ||
| echo -e " Setting up passwordless sudo for tailscale (grid auto-reconnect)..." | ||
| echo "$USER ALL=(ALL) NOPASSWD: /usr/bin/tailscale, /usr/bin/tailscaled, /usr/sbin/tailscaled" | sudo tee /etc/sudoers.d/tailscale > /dev/null | ||
| sudo chmod 440 /etc/sudoers.d/tailscale | ||
| echo -e " ${GREEN}✅ Passwordless sudo for tailscale configured${NC}" |
There was a problem hiding this comment.
Creating /etc/sudoers.d/tailscale with NOPASSWD for the full tailscale client effectively grants passwordless root for a very powerful admin surface (e.g., changing routing/DNS, bringing interfaces up/down). Consider making this opt-in (env flag / prompt), and/or narrowing the sudoers rule to the minimal commands/args actually required for boot/reconnect, or prefer the systemd service path instead of sudoers escalation.
| if [ ! -f /etc/sudoers.d/tailscale ]; then | ||
| echo -e " Setting up passwordless sudo for tailscale (grid auto-reconnect)..." | ||
| echo "$USER ALL=(ALL) NOPASSWD: /usr/bin/tailscale, /usr/bin/tailscaled, /usr/sbin/tailscaled" | sudo tee /etc/sudoers.d/tailscale > /dev/null |
There was a problem hiding this comment.
The sudoers entry uses $USER, which can be root (e.g., if someone runs the script from a root shell where SUDO_USER is empty). That would create a rule for the wrong principal. Use a derived non-root target user (e.g., ${SUDO_USER:-$(logname)}) and/or explicitly refuse to proceed if the target resolves to root.
| if [ ! -f /etc/sudoers.d/tailscale ]; then | |
| echo -e " Setting up passwordless sudo for tailscale (grid auto-reconnect)..." | |
| echo "$USER ALL=(ALL) NOPASSWD: /usr/bin/tailscale, /usr/bin/tailscaled, /usr/sbin/tailscaled" | sudo tee /etc/sudoers.d/tailscale > /dev/null | |
| # Determine the non-root user for sudoers entry: prefer SUDO_USER, fallback to logname. | |
| local ts_sudo_user="${SUDO_USER:-$(logname 2>/dev/null || true)}" | |
| if [ -z "$ts_sudo_user" ] || [ "$ts_sudo_user" = "root" ]; then | |
| echo -e " ${YELLOW}Skipping sudoers setup for tailscale: could not determine a non-root user.${NC}" | |
| elif [ ! -f /etc/sudoers.d/tailscale ]; then | |
| echo -e " Setting up passwordless sudo for tailscale (grid auto-reconnect)..." | |
| echo "$ts_sudo_user ALL=(ALL) NOPASSWD: /usr/bin/tailscale, /usr/bin/tailscaled, /usr/sbin/tailscaled" | sudo tee /etc/sudoers.d/tailscale > /dev/null |
| if [ ! -f /etc/sudoers.d/tailscale ]; then | ||
| echo -e " Setting up passwordless sudo for tailscale (grid auto-reconnect)..." | ||
| echo "$USER ALL=(ALL) NOPASSWD: /usr/bin/tailscale, /usr/bin/tailscaled, /usr/sbin/tailscaled" | sudo tee /etc/sudoers.d/tailscale > /dev/null | ||
| sudo chmod 440 /etc/sudoers.d/tailscale |
There was a problem hiding this comment.
After writing a new file under /etc/sudoers.d, it should be validated (e.g., via visudo -cf /etc/sudoers.d/tailscale) before returning. A syntax error can break sudo for the machine, and the current code doesn’t perform any validation.
| sudo chmod 440 /etc/sudoers.d/tailscale | |
| sudo chmod 440 /etc/sudoers.d/tailscale | |
| # Validate the new sudoers drop-in to avoid breaking sudo if there's a syntax error | |
| if ! sudo visudo -cf /etc/sudoers.d/tailscale; then | |
| echo -e " ${RED}❌ Failed to validate /etc/sudoers.d/tailscale with visudo${NC}" | |
| echo -e " ${YELLOW}Removing invalid sudoers file and leaving sudo configuration unchanged.${NC}" | |
| sudo rm -f /etc/sudoers.d/tailscale | |
| return 1 | |
| fi |
| # Need auth — tailscale up prints a login URL that must be opened in a browser. | ||
| # This BLOCKS until you authenticate. That's intentional — the grid won't work without it. | ||
| echo -e "" | ||
| echo -e " ${YELLOW}═══════════════════════════════════════════════════════════${NC}" | ||
| echo -e " ${YELLOW} TAILSCALE LOGIN REQUIRED${NC}" | ||
| echo -e " ${YELLOW} A URL will appear below. Open it in your browser to authenticate.${NC}" | ||
| echo -e " ${YELLOW} This is a ONE-TIME step — after this, Tailscale auto-reconnects forever.${NC}" | ||
| echo -e " ${YELLOW}═══════════════════════════════════════════════════════════${NC}" | ||
| echo -e "" |
There was a problem hiding this comment.
The previous implementation only attempted interactive auth when stdin was a TTY. The new flow always runs tailscale up and intentionally blocks, which will hang non-interactive runs (e.g., automation/DEPS_ONLY usage) and may cause CI timeouts. Please restore a non-interactive path (print instructions and return non-zero / skip) when [ -t 0 ] is false.
After install.sh runs ONCE, Tailscale auto-reconnects forever. Passwordless sudo, systemd enable, clear browser auth prompt with no timeout.