Skip to content

Instantly share code, notes, and snippets.

@JoshZA
Created May 1, 2025 06:23
Show Gist options
  • Save JoshZA/5eeba59af2ad6d6e72df510e78a861c9 to your computer and use it in GitHub Desktop.
Save JoshZA/5eeba59af2ad6d6e72df510e78a861c9 to your computer and use it in GitHub Desktop.
A quick script to provision headscale + tailscale exit node on an ubuntu noble server
#!/bin/bash
# Exit on error
set -e
# Function to check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Check for sudo privileges
if ! sudo -n true 2>/dev/null; then
echo "This script requires sudo privileges. Please run with sudo or as root."
exit 1
fi
# Check for curl
if ! command_exists curl; then
echo "curl is not installed. Installing curl..."
sudo apt update
sudo apt install -y curl
fi
# Install Tailscale if not installed
if ! command_exists tailscale; then
echo "Installing Tailscale..."
sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/noble.noarmor.gpg | sudo tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null
curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/noble.tailscale-keyring.list | sudo tee /etc/apt/sources.list.d/tailscale.list
sudo apt update
sudo apt install -y tailscale
fi
# Install Headscale if not installed
if ! command_exists headscale; then
echo "Installing Headscale..."
HEADSCALE_VERSION="0.25.0"
HEADSCALE_ARCH="amd64"
wget --output-document=headscale.deb \
"https://github.com/juanfont/headscale/releases/download/v${HEADSCALE_VERSION}/headscale_${HEADSCALE_VERSION}_linux_${HEADSCALE_ARCH}.deb"
sudo dpkg -i headscale.deb
rm headscale.deb
fi
# Configure Headscale if config doesn't exist
if [ ! -f /etc/headscale/config.yaml ]; then
echo "Configuring Headscale..."
sudo mkdir -p /etc/headscale
cat << EOF | sudo tee /etc/headscale/config.yaml
server_url: http://127.0.0.1:8080
listen_addr: 0.0.0.0:8080
metrics_addr: 127.0.0.1:9090
private_key_path: /var/lib/headscale/private.key
db_type: sqlite
db_path: /var/lib/headscale/db.sqlite
EOF
fi
# Start and enable Headscale service if not already
if ! systemctl is-enabled headscale >/dev/null 2>&1 || ! systemctl is-active headscale >/dev/null 2>&1; then
echo "Starting Headscale service..."
sudo systemctl enable --now headscale
fi
# Create Headscale user if not exists
if ! sudo headscale users list | grep -q mytailnet; then
echo "Creating Headscale user 'mytailnet'..."
sudo headscale users create mytailnet
fi
# Generate a pre-auth key if not already authenticated
SERVER_URL=$(sudo grep -oP '(?<=server_url: ).*' /etc/headscale/config.yaml)
if [ -z "$SERVER_URL" ]; then
echo "Failed to extract server_url from /etc/headscale/config.yaml. Please check the configuration."
exit 1
fi
# Start and enable Tailscale service if not already
if ! systemctl is-enabled tailscaled >/dev/null 2>&1 || ! systemctl is-active tailscaled >/dev/null 2>&1; then
echo "Starting Tailscale service..."
sudo systemctl enable --now tailscaled
fi
# Check if Tailscale is authenticated
if ! tailscale ip -4 >/dev/null 2>&1; then
echo "Generating pre-auth key..."
PRE_AUTH_KEY=$(sudo headscale preauthkeys create -u mytailnet -e 24h --output text)
if [ -z "$PRE_AUTH_KEY" ]; then
echo "Failed to generate pre-auth key. Check Headscale configuration."
exit 1
fi
echo "Pre-auth key: $PRE_AUTH_KEY"
echo "Authenticating Tailscale client to Headscale at $SERVER_URL..."
if ! sudo tailscale up --login-server "$SERVER_URL" --authkey "$PRE_AUTH_KEY" --advertise-exit-node; then
echo "Tailscale authentication failed. Checking Headscale status..."
if ! sudo systemctl is-active headscale >/dev/null 2>&1; then
echo "Headscale service is not running. Restarting..."
sudo systemctl restart headscale
sleep 5
fi
if ! sudo netstat -tuln | grep -q :8080; then
echo "Headscale is not listening on port 8080. Check /etc/headscale/config.yaml and firewall settings."
exit 1
fi
echo "Retrying Tailscale authentication..."
sudo tailscale up --login-server "$SERVER_URL" --authkey "$PRE_AUTH_KEY" --advertise-exit-node
fi
else
echo "Tailscale already authenticated. Ensuring exit node advertisement..."
sudo tailscale up --login-server "$SERVER_URL" --advertise-exit-node
fi
# Enable exit node routes in Headscale if not already enabled
echo "Checking exit node routes in Headscale..."
NODE_ID=$(sudo headscale nodes list | grep $(hostname) | awk '{print $1}')
if [ -n "$NODE_ID" ]; then
ROUTE_ID=$(sudo headscale routes list | grep "$NODE_ID" | grep "0.0.0.0/0" | awk '{print $1}')
if [ -n "$ROUTE_ID" ]; then
ROUTE_STATUS=$(sudo headscale routes list | grep "$ROUTE_ID" | awk '{print $5}')
if [ "$ROUTE_STATUS" == "false" ]; then
echo "Enabling exit node route..."
sudo headscale routes enable -r "$ROUTE_ID"
else
echo "Exit node route already enabled."
fi
else
echo "Warning: Exit node route not found. Check Headscale routes manually."
fi
else
echo "Warning: Node not found in Headscale. Check node registration."
fi
# Verify setup
echo "Verifying setup..."
tailscale status
sudo headscale routes list
echo "Setup complete! This machine is configured as a Tailscale exit node with Headscale."
echo "Check the Headscale admin CLI or UI for further management."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment