Created
May 1, 2025 06:23
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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