Skip to content

Instantly share code, notes, and snippets.

@samelie
Last active May 3, 2026 14:54
Show Gist options
  • Select an option

  • Save samelie/bddcbdc9038833615011a9fb75f159a4 to your computer and use it in GitHub Desktop.

Select an option

Save samelie/bddcbdc9038833615011a9fb75f159a4 to your computer and use it in GitHub Desktop.
Headscale SSH client setup - connect laptop to home machine via Tailscale
#!/bin/bash
# Headscale SSH Client Setup
# Standalone script - run on any laptop to connect to home machine
#
# Usage:
# curl -sL https://gist.githubusercontent.com/.../headscale-ssh-client-setup.sh | bash
# # or
# ./headscale-ssh-client-setup.sh
#
# Prerequisites:
# - Tailscale app installed and connected to hs.add.dog
# - Home machine (mac-mini) has Remote Login enabled
set -euo pipefail
#######################
# CONFIGURATION
#######################
HOME_TAILSCALE_IP="100.64.0.3"
HOME_HOSTNAME="mac-mini"
HOME_MAGIC_DNS="mac-mini.tail.add.dog"
SSH_USER="selie"
HEADSCALE_URL="hs.add.dog"
#######################
# COLORS
#######################
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
ok() { echo -e "${GREEN}${NC} $1"; }
warn() { echo -e "${YELLOW}${NC} $1"; }
fail() { echo -e "${RED}${NC} $1"; }
#######################
# MAIN
#######################
echo "╔════════════════════════════════════════════╗"
echo "║ Headscale SSH Client Setup ║"
echo "║ Target: $HOME_HOSTNAME @ $HOME_TAILSCALE_IP"
echo "╚════════════════════════════════════════════╝"
echo ""
# 1. Check Tailscale
echo "[1/6] Tailscale status"
if ! command -v tailscale &>/dev/null; then
fail "Tailscale not installed"
echo " Install: https://tailscale.com/download"
exit 1
fi
if tailscale status &>/dev/null; then
LOCAL_IP=$(tailscale ip -4 2>/dev/null || echo "unknown")
ok "Connected as $LOCAL_IP"
else
fail "Tailscale not connected"
echo " Run: tailscale up --login-server https://$HEADSCALE_URL"
exit 1
fi
# 2. SSH directory setup
echo ""
echo "[2/6] SSH directory"
mkdir -p ~/.ssh/sockets
chmod 700 ~/.ssh ~/.ssh/sockets 2>/dev/null || true
ok "~/.ssh/sockets ready"
# 3. SSH key
echo ""
echo "[3/6] SSH key"
if [[ -f ~/.ssh/id_ed25519 ]]; then
ok "Key exists: ~/.ssh/id_ed25519"
else
echo " Generating SSH key..."
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N "" -C "$(whoami)@$(hostname)"
ok "Key generated"
fi
# 4. SSH config
echo ""
echo "[4/6] SSH config"
SSH_CONFIG_BLOCK="
# === Headscale Remote SSH ===
Host home
HostName $HOME_TAILSCALE_IP
User $SSH_USER
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
ServerAliveInterval 30
ServerAliveCountMax 3
Compression yes
ForwardAgent yes
UseKeychain yes
AddKeysToAgent yes
IdentityFile ~/.ssh/id_ed25519
# === End Headscale Remote SSH ==="
if grep -q "# === Headscale Remote SSH ===" ~/.ssh/config 2>/dev/null; then
ok "SSH config already configured"
else
touch ~/.ssh/config
chmod 600 ~/.ssh/config
echo "$SSH_CONFIG_BLOCK" >> ~/.ssh/config
ok "Added 'home' host to ~/.ssh/config"
fi
# 5. Copy key to home machine
echo ""
echo "[5/6] SSH key authorization"
# Test if key already authorized
if ssh -o ConnectTimeout=5 -o BatchMode=yes -o StrictHostKeyChecking=accept-new home "exit 0" 2>/dev/null; then
ok "Key already authorized on home machine"
else
warn "Key not yet authorized"
echo ""
echo " Copying SSH key to home machine..."
echo " You'll be prompted for password (one time only):"
echo ""
if ssh-copy-id -i ~/.ssh/id_ed25519 "$SSH_USER@$HOME_TAILSCALE_IP"; then
ok "Key copied successfully"
else
fail "Failed to copy key"
echo " Manual fix: copy this to home's ~/.ssh/authorized_keys:"
cat ~/.ssh/id_ed25519.pub
exit 1
fi
fi
# 6. Verify connection
echo ""
echo "[6/6] Connection test"
# Tailscale ping
PING_OUT=$(tailscale ping -c 1 "$HOME_TAILSCALE_IP" 2>&1 | head -1)
if echo "$PING_OUT" | grep -q "pong"; then
if echo "$PING_OUT" | grep -q "DERP"; then
warn "Relayed via DERP (works, slightly higher latency)"
else
ok "Direct WireGuard connection"
fi
else
fail "Tailscale ping failed"
fi
# SSH test
if ssh -o ConnectTimeout=10 home "echo 'SSH OK'" &>/dev/null; then
ok "SSH connection successful"
# Get remote info
REMOTE_HOST=$(ssh home "hostname" 2>/dev/null || echo "unknown")
REMOTE_OS=$(ssh home "sw_vers -productVersion 2>/dev/null || uname -r" 2>/dev/null || echo "unknown")
echo " Remote: $REMOTE_HOST (macOS $REMOTE_OS)"
else
fail "SSH connection failed"
echo " Debug: ssh -vvv home"
exit 1
fi
echo ""
echo "╔════════════════════════════════════════════╗"
echo "║ Setup Complete! ║"
echo "╚════════════════════════════════════════════╝"
echo ""
echo "Usage:"
echo " ssh home # Terminal"
echo " code --remote ssh-remote+home /path # VS Code"
echo " scp file.txt home:~/ # Copy files"
echo ""
echo "Troubleshooting:"
echo " tailscale status # Check peers"
echo " tailscale ping home # Test connection type"
echo " ssh -vvv home # Verbose SSH debug"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Headscale Admin Reference (run on server or via kubectl):"
echo ""
echo " # List users (v0.28+ uses ID not name)"
echo " headscale users list"
echo ""
echo " # Create pre-auth key (--user takes ID, not name)"
echo " headscale preauthkeys create --user 1 --expiration 1h"
echo ""
echo " # Register new device (on device, use pre-auth key)"
echo " tailscale up --login-server https://hs.add.dog --authkey <KEY>"
echo ""
echo " # List nodes"
echo " headscale nodes list"
echo ""
echo " # Rename node"
echo " headscale nodes rename --identifier <ID> <new-name>"
echo ""
echo " # Node expiry: 0001-01-01 or -62135596800 = never expires"
echo " # ephemeral: false/null = permanent node"
echo ""
echo " # Via kubectl:"
echo " kubectl exec -n headscale deploy/headscale -- headscale nodes list"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment