Skip to content

Fix #565: Tailscale — passwordless sudo, persistent auth, no more tower visits#631

Merged
joelteply merged 1 commit intomainfrom
fix/install-tailscale-sudo
Mar 30, 2026
Merged

Fix #565: Tailscale — passwordless sudo, persistent auth, no more tower visits#631
joelteply merged 1 commit intomainfrom
fix/install-tailscale-sudo

Conversation

@joelteply
Copy link
Copy Markdown
Contributor

After install.sh runs ONCE, Tailscale auto-reconnects forever. Passwordless sudo, systemd enable, clear browser auth prompt with no timeout.

…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.
Copilot AI review requested due to automatic review settings March 30, 2026 15:06
@joelteply joelteply merged commit d0f1f8c into main Mar 30, 2026
4 checks passed
@joelteply joelteply deleted the fix/install-tailscale-sudo branch March 30, 2026 15:06
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 up invocation to include --accept-routes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/scripts/install.sh
Comment on lines +569 to 579
# 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
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread src/scripts/install.sh

# 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
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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

Copilot uses AI. Check for mistakes.
Comment thread src/scripts/install.sh
Comment on lines +507 to +515
# 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}"
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread src/scripts/install.sh
Comment on lines +511 to +513
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
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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

Copilot uses AI. Check for mistakes.
Comment thread src/scripts/install.sh
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
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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

Copilot uses AI. Check for mistakes.
Comment thread src/scripts/install.sh
Comment on lines +559 to +567
# 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 ""
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants