Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 34 additions & 10 deletions src/scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -504,8 +504,22 @@ install_tailscale() {
;;
esac

# Linux/WSL: ensure daemon is running
# 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
Comment on lines +511 to +513
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.
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.
echo -e " ${GREEN}✅ Passwordless sudo for tailscale configured${NC}"
Comment on lines +507 to +515
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.
fi

# Enable systemd service if available (survives reboots on native Linux)
if command -v systemctl &>/dev/null && systemctl is-system-running &>/dev/null 2>&1; then
sudo systemctl enable tailscaled 2>/dev/null || true
fi

if ! pgrep -x tailscaled &>/dev/null; then
echo -e " ${YELLOW}Starting tailscaled daemon...${NC}"
# WSL2 doesn't have systemd — always try direct spawn first
Expand Down Expand Up @@ -542,16 +556,26 @@ install_tailscale() {
return
fi

# Need auth — start tailscale up (shows URL interactively)
if [ -t 0 ]; then
echo -e " ${YELLOW}Authenticating Tailscale...${NC}"
sudo tailscale up --ssh 2>&1
local ts_ip=$(tailscale ip -4 2>/dev/null || echo "pending")
if [ "$ts_ip" != "pending" ]; then
echo -e " ${GREEN}✅ Tailscale connected (${ts_ip})${NC}"
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 ""
Comment on lines +559 to +567
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.

# 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.

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
Comment on lines +569 to 579
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.
fi
}
Expand Down
Loading