summaryrefslogtreecommitdiff
path: root/snippets/hyperstack/wg1-setup.sh
diff options
context:
space:
mode:
Diffstat (limited to 'snippets/hyperstack/wg1-setup.sh')
-rwxr-xr-xsnippets/hyperstack/wg1-setup.sh285
1 files changed, 189 insertions, 96 deletions
diff --git a/snippets/hyperstack/wg1-setup.sh b/snippets/hyperstack/wg1-setup.sh
index d057fb8..49d716a 100755
--- a/snippets/hyperstack/wg1-setup.sh
+++ b/snippets/hyperstack/wg1-setup.sh
@@ -1,56 +1,76 @@
#!/bin/bash
#
-# wg1-setup.sh - Set up WireGuard wg1 tunnel between earth and hyperstack VM
+# wg1-setup.sh - Set up WireGuard wg1 tunnel between earth and a hyperstack VM
#
# USAGE:
-# ./wg1-setup.sh <VM_PUBLIC_IP>
-# Example: ./wg1-setup.sh 185.216.20.163
+# ./wg1-setup.sh <VM_PUBLIC_IP> [SERVER_WG_IP] [WG_HOSTNAME]
+#
+# VM_PUBLIC_IP Public IP of the hyperstack VM (required)
+# SERVER_WG_IP WireGuard IP to assign to this VM's tunnel interface (default: 192.168.3.1)
+# Use 192.168.3.3 for hyperstack2 when hyperstack1 is already set up.
+# WG_HOSTNAME Hostname mapped to SERVER_WG_IP in /etc/hosts (default: <vmhostname>.wg1)
+#
+# EXAMPLES:
+# ./wg1-setup.sh 185.216.20.163 # VM1 (hyperstack1, 192.168.3.1)
+# ./wg1-setup.sh 185.216.20.200 192.168.3.3 hyperstack2.wg1 # VM2 added to existing tunnel
#
# NETWORK DESIGN:
# Subnet: 192.168.3.0/24 (separate from wg0's 192.168.2.0/24)
# Port: 56710/udp
#
-# +----------------+ +------------------+
-# | earth (client) | | hyperstack (VM) |
-# | 192.168.3.2 |<--- WireGuard ---> | 192.168.3.1 |
-# +----------------+ tunnel +------------------+
-# | Ollama :11434 |
-# +------------------+
+# +----------------+ +------------------+
+# | earth (client) | | hyperstack1 (VM) |
+# | 192.168.3.2 |<--- WireGuard ---> | 192.168.3.1 |
+# +----------------+ tunnel +------------------+
+# | | vLLM :11434 |
+# | +------------------+
+# | +------------------+
+# +--------- WireGuard ----------> | hyperstack2 (VM) |
+# | 192.168.3.3 |
+# +------------------+
+# | vLLM :11434 |
+# +------------------+
#
# WHAT THIS SCRIPT DOES:
-# On hyperstack VM (via SSH):
+#
+# For the FIRST VM (SERVER_WG_IP = 192.168.3.1, default):
+# Generates fresh key-pairs and REPLACES /etc/wireguard/wg1.conf on earth with
+# a single-peer config pointing to this VM.
+#
+# For ADDITIONAL VMs (any other SERVER_WG_IP, e.g. 192.168.3.3):
+# Generates new server-side keys and ADDS or UPDATES just the new [Peer] block
+# in the existing /etc/wireguard/wg1.conf, preserving the [Interface] section
+# (client key-pair) and any other peers already present.
+# The existing client public key from wg1.conf is extracted and used in the new
+# VM's server config so it can encrypt traffic to earth.
+#
+# On every hyperstack VM (via SSH):
# - Installs WireGuard if not present
-# - Creates /etc/wireguard/wg1.conf
-# - Opens UFW ports: 56710/udp (WireGuard), 11434/tcp from 192.168.3.0/24 (Ollama)
-# - Configures Ollama to listen on 0.0.0.0:11434
+# - Creates /etc/wireguard/wg1.conf with SERVER_WG_IP as the tunnel address
+# - Opens UFW ports: 56710/udp (WireGuard), 11434/tcp from 192.168.3.0/24
# - Starts wg-quick@wg1
#
# On earth (locally):
# - Installs WireGuard if not present (dnf)
-# - Creates /etc/wireguard/wg1.conf
-# - Starts wg-quick@wg1
+# - Creates or updates /etc/wireguard/wg1.conf (see above)
+# - Adds SERVER_WG_IP <-> WG_HOSTNAME mapping to /etc/hosts
+# - Restarts wg-quick@wg1
#
# PREREQUISITES:
# - SSH access to ubuntu@<VM_IP> with key-based auth
# - UDP port 56710 open in cloud provider's firewall/security group
#
# RE-RUNNING:
-# When the VM IP changes, simply re-run this script with the new IP.
+# When a VM IP changes, simply re-run this script with the new IP.
# It will regenerate keys and update configs on both sides.
#
-# USING OLLAMA REMOTELY:
-# export OLLAMA_HOST=http://192.168.3.1:11434
-# ollama run qwen2.5-coder:14b-instruct
-# # Or with aider:
-# aider --model ollama/qwen2.5-coder:14b-instruct
-#
set -euo pipefail
-# Configuration constants
+# Fixed network constants that must match hyperstack-vm*.toml [network] section.
WG_INTERFACE="wg1"
WG_PORT="56710"
-SERVER_WG_IP="192.168.3.1"
+DEFAULT_SERVER_WG_IP="192.168.3.1"
CLIENT_WG_IP="192.168.3.2"
SUBNET_MASK="24"
SSH_USER="ubuntu"
@@ -61,22 +81,12 @@ GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
-print_warning() {
- echo -e "${YELLOW}$1${NC}"
-}
-
-print_success() {
- echo -e "${GREEN}$1${NC}"
-}
-
-print_error() {
- echo -e "${RED}$1${NC}"
-}
+print_warning() { echo -e "${YELLOW}$1${NC}"; }
+print_success() { echo -e "${GREEN}$1${NC}"; }
+print_error() { echo -e "${RED}$1${NC}"; }
# Retry wrapper for SSH/SCP commands that may fail due to transient
# connection resets (e.g. sshd restart from unattended-upgrades).
-# Usage: retry_ssh ssh user@host "command"
-# retry_ssh scp file user@host:/path
retry_ssh() {
local max_attempts=5
local attempt=1
@@ -96,50 +106,137 @@ retry_ssh() {
done
}
+# Updates or adds a [Peer] block in the existing /etc/wireguard/wg1.conf.
+# Preserves the [Interface] section and any other peers; only the block for
+# SERVER_WG_IP (matched by AllowedIPs) is replaced.
+# Uses python3 for safe regex-based TOML-like block manipulation.
+update_peer_in_client_config() {
+ local server_ip="$1"
+ local server_pubkey="$2"
+ local vm_ip="$3"
+ local tmpfile conf_copy
+ tmpfile=$(mktemp)
+ conf_copy=$(mktemp)
+
+ # /etc/wireguard/wg1.conf is root-owned; read it via sudo into a user-readable temp copy.
+ if ! sudo cat /etc/wireguard/wg1.conf > "$conf_copy" 2>/dev/null; then
+ print_error "Cannot read /etc/wireguard/wg1.conf. Run wg1-setup.sh for VM1 (192.168.3.1) first."
+ rm -f "$tmpfile" "$conf_copy"
+ return 1
+ fi
+
+ python3 - "$server_ip" "$server_pubkey" "$vm_ip" "$WG_PORT" "$conf_copy" "$tmpfile" << 'PYEOF'
+import sys, re
+
+server_ip, server_pubkey, vm_ip, wg_port, conf_copy, tmpfile = sys.argv[1:]
+
+with open(conf_copy) as f:
+ content = f.read()
+
+if not content.strip():
+ print("ERROR: wg1.conf is empty. Run wg1-setup.sh for VM1 (192.168.3.1) first.", file=sys.stderr)
+ sys.exit(1)
+
+# Split into sections: [Interface] block + any [Peer] blocks.
+# Each section starts with a [ header; split on newline-[ boundaries.
+parts = re.split(r'(?=\n\[)', content)
+
+# Remove any existing [Peer] block whose AllowedIPs matches server_ip/32.
+kept = [p for p in parts if not (re.search(r'^\[Peer\]', p.lstrip()) and f'AllowedIPs = {server_ip}/32' in p)]
+
+new_peer = f"""
+[Peer]
+# hyperstack VM ({server_ip})
+PublicKey = {server_pubkey}
+Endpoint = {vm_ip}:{wg_port}
+AllowedIPs = {server_ip}/32
+PersistentKeepalive = 25"""
+
+result = ''.join(kept).rstrip('\n') + '\n' + new_peer + '\n'
+
+with open(tmpfile, 'w') as f:
+ f.write(result)
+print('peer-updated-ok')
+PYEOF
+
+ local rc=$?
+ rm -f "$conf_copy"
+ if [[ $rc -eq 0 ]]; then
+ sudo cp "${tmpfile}" /etc/wireguard/wg1.conf
+ sudo chmod 600 /etc/wireguard/wg1.conf
+ fi
+ rm -f "${tmpfile}"
+ return $rc
+}
+
# Validate arguments
-if [[ $# -ne 1 ]]; then
- echo "Usage: $0 <VM_PUBLIC_IP>"
- echo "Example: $0 185.216.20.163"
+if [[ $# -lt 1 ]]; then
+ echo "Usage: $0 <VM_PUBLIC_IP> [SERVER_WG_IP] [WG_HOSTNAME]"
+ echo "Example (VM1): $0 185.216.20.163"
+ echo "Example (VM2): $0 185.216.20.200 192.168.3.3 hyperstack2.wg1"
exit 1
fi
VM_IP="$1"
+SERVER_WG_IP="${2:-${DEFAULT_SERVER_WG_IP}}"
+# Default WG_HOSTNAME: replace 192.168.3. prefix with 'hyperstack' and append .wg1,
+# or fall back to server IP if the address doesn't match the expected pattern.
+WG_HOSTNAME="${3:-$(echo "$SERVER_WG_IP" | sed 's/^192\.168\.3\.\(.*\)/hyperstack\1.wg1/' || echo "${SERVER_WG_IP}.wg1")}"
+
+# Determine mode: first VM replaces the entire client config; additional VMs add a peer.
+IS_FIRST_VM=false
+[[ "$SERVER_WG_IP" == "$DEFAULT_SERVER_WG_IP" ]] && IS_FIRST_VM=true
echo "=============================================="
print_warning "IMPORTANT: Ensure UDP port ${WG_PORT} is open on the VM!"
print_warning "This must be configured in your cloud provider's"
print_warning "firewall/security group settings."
+if [[ "$IS_FIRST_VM" == "false" ]]; then
+ print_warning "Mode: ADD PEER — ${SERVER_WG_IP} (${WG_HOSTNAME}) will be added to existing wg1.conf."
+ print_warning "Ensure the first VM (192.168.3.1) has already been set up."
+fi
echo "=============================================="
echo ""
-read -p "Press Enter to continue (or Ctrl+C to abort)..."
+read -rp "Press Enter to continue (or Ctrl+C to abort)..."
echo ""
# Create temporary directory for key generation
TMPDIR=$(mktemp -d)
-trap "rm -rf $TMPDIR" EXIT
+trap 'rm -rf $TMPDIR' EXIT
echo "=== Generating WireGuard keys locally ==="
-# Generate server (hyperstack) keys
+# Generate server (hyperstack VM) keys — always fresh for each VM.
wg genkey > "$TMPDIR/server-privatekey"
wg pubkey < "$TMPDIR/server-privatekey" > "$TMPDIR/server-publickey"
SERVER_PRIVATE_KEY=$(cat "$TMPDIR/server-privatekey")
-SERVER_PUBLIC_KEY=$(cat "$TMPDIR/server-publickey")
-
-# Generate client (earth) keys
-wg genkey > "$TMPDIR/client-privatekey"
-wg pubkey < "$TMPDIR/client-privatekey" > "$TMPDIR/client-publickey"
-CLIENT_PRIVATE_KEY=$(cat "$TMPDIR/client-privatekey")
-CLIENT_PUBLIC_KEY=$(cat "$TMPDIR/client-publickey")
-
-print_success "Keys generated successfully"
+SERVER_PUBLIC_KEY=$(cat "$TMPDIR/server-publickey")
+
+if [[ "$IS_FIRST_VM" == "true" ]]; then
+ # First VM: generate fresh client keys; the entire wg1.conf will be replaced.
+ wg genkey > "$TMPDIR/client-privatekey"
+ wg pubkey < "$TMPDIR/client-privatekey" > "$TMPDIR/client-publickey"
+ CLIENT_PRIVATE_KEY=$(cat "$TMPDIR/client-privatekey")
+ CLIENT_PUBLIC_KEY=$(cat "$TMPDIR/client-publickey")
+ print_success "Keys generated (first VM — full config will be replaced)"
+else
+ # Additional VM: reuse the existing client keys from /etc/wireguard/wg1.conf so that
+ # the first VM's server config (which already stores the client public key) keeps working.
+ CLIENT_PRIVATE_KEY=$(sudo cat /etc/wireguard/wg1.conf | grep -m1 'PrivateKey' | awk '{print $3}')
+ if [[ -z "$CLIENT_PRIVATE_KEY" ]]; then
+ print_error "Cannot extract client private key from /etc/wireguard/wg1.conf."
+ print_error "Run this script for VM1 (192.168.3.1) first."
+ exit 1
+ fi
+ CLIENT_PUBLIC_KEY=$(echo "$CLIENT_PRIVATE_KEY" | wg pubkey)
+ print_success "Keys generated (additional VM — client keys reused from existing wg1.conf)"
+fi
echo ""
-echo "=== Creating server (hyperstack) configuration ==="
+echo "=== Creating server (hyperstack VM ${SERVER_WG_IP}) configuration ==="
-# Create server wg1.conf
cat > "$TMPDIR/server-wg1.conf" << EOF
-# WireGuard wg1 configuration for hyperstack VM
+# WireGuard wg1 configuration for hyperstack VM (${SERVER_WG_IP})
# Server side of earth <-> hyperstack tunnel
# Generated by wg1-setup.sh on $(date)
@@ -154,13 +251,13 @@ PublicKey = ${CLIENT_PUBLIC_KEY}
AllowedIPs = ${CLIENT_WG_IP}/32
EOF
-print_success "Server config created"
+print_success "Server config created (server IP: ${SERVER_WG_IP})"
-echo ""
-echo "=== Creating client (earth) configuration ==="
+if [[ "$IS_FIRST_VM" == "true" ]]; then
+ echo ""
+ echo "=== Creating client (earth) configuration ==="
-# Create client wg1.conf
-cat > "$TMPDIR/client-wg1.conf" << EOF
+ cat > "$TMPDIR/client-wg1.conf" << EOF
# WireGuard wg1 configuration for earth
# Client side of earth <-> hyperstack tunnel
# Generated by wg1-setup.sh on $(date)
@@ -170,49 +267,43 @@ Address = ${CLIENT_WG_IP}/${SUBNET_MASK}
PrivateKey = ${CLIENT_PRIVATE_KEY}
[Peer]
-# hyperstack VM (server)
+# hyperstack VM (${SERVER_WG_IP})
PublicKey = ${SERVER_PUBLIC_KEY}
Endpoint = ${VM_IP}:${WG_PORT}
AllowedIPs = ${SERVER_WG_IP}/32
PersistentKeepalive = 25
EOF
-print_success "Client config created"
+ print_success "Client config created"
+fi
echo ""
-echo "=== Setting up hyperstack VM (${VM_IP}) ==="
+echo "=== Setting up hyperstack VM (${VM_IP}, tunnel IP ${SERVER_WG_IP}) ==="
-# Wait for SSH to become available (handles transient connection resets
-# from sshd restarts due to unattended-upgrades or package installs)
echo "Testing SSH connection..."
retry_ssh ssh -o ConnectTimeout=10 -o BatchMode=yes "${SSH_USER}@${VM_IP}" "echo 'SSH OK'"
print_success "SSH connection OK"
-# Install WireGuard on server if not present
echo "Installing WireGuard on hyperstack..."
retry_ssh ssh "${SSH_USER}@${VM_IP}" "which wg >/dev/null 2>&1 || (sudo apt update && sudo apt install -y wireguard)"
print_success "WireGuard installed"
-# Copy server config to hyperstack
echo "Copying wg1.conf to hyperstack..."
retry_ssh scp "$TMPDIR/server-wg1.conf" "${SSH_USER}@${VM_IP}:/tmp/wg1.conf"
retry_ssh ssh "${SSH_USER}@${VM_IP}" "sudo mv /tmp/wg1.conf /etc/wireguard/wg1.conf && sudo chmod 600 /etc/wireguard/wg1.conf"
print_success "Server config installed"
-# Configure firewall on hyperstack
echo "Configuring firewall (ufw) on hyperstack..."
retry_ssh ssh "${SSH_USER}@${VM_IP}" bash -s << 'REMOTE_SCRIPT'
sudo ufw allow ssh comment 'Allow SSH' 2>/dev/null || true
sudo ufw --force enable >/dev/null 2>&1 || true
sudo ufw allow 56710/udp comment 'WireGuard wg1' 2>/dev/null || true
-sudo ufw allow from 192.168.3.0/24 to any port 11434 proto tcp comment 'Ollama via wg1' 2>/dev/null || true
+sudo ufw allow from 192.168.3.0/24 to any port 11434 proto tcp comment 'Ollama/vLLM via wg1' 2>/dev/null || true
echo "Firewall rules added"
REMOTE_SCRIPT
print_success "Firewall configured"
-# Ensure Ollama listens on all interfaces (only if override not already set
-# by ollama_setup_script, which also configures OLLAMA_MODELS and other env vars)
-echo "Configuring Ollama to listen on 0.0.0.0..."
+echo "Configuring Ollama to listen on 0.0.0.0 (if installed)..."
retry_ssh ssh "${SSH_USER}@${VM_IP}" bash -s << 'REMOTE_SCRIPT'
if [ -f /etc/systemd/system/ollama.service.d/override.conf ] && \
grep -q 'OLLAMA_HOST' /etc/systemd/system/ollama.service.d/override.conf; then
@@ -224,12 +315,11 @@ else
Environment="OLLAMA_HOST=0.0.0.0:11434"
OVERRIDE
sudo systemctl daemon-reload
- sudo systemctl restart ollama 2>/dev/null || echo "Note: Ollama service not running or not installed"
+ sudo systemctl restart ollama 2>/dev/null || echo "Note: Ollama not running or not installed"
fi
REMOTE_SCRIPT
print_success "Ollama configured"
-# Start wg1 on hyperstack
echo "Starting wg1 on hyperstack..."
retry_ssh ssh "${SSH_USER}@${VM_IP}" "sudo systemctl start wg-quick@wg1 2>/dev/null || sudo wg-quick up wg1"
print_success "wg1 started on hyperstack"
@@ -237,35 +327,43 @@ print_success "wg1 started on hyperstack"
echo ""
echo "=== Setting up earth (local) ==="
-# Check if WireGuard is installed locally
if ! which wg >/dev/null 2>&1; then
echo "Installing WireGuard locally..."
sudo dnf install -y wireguard-tools
fi
print_success "WireGuard installed locally"
-# Install client config locally
-echo "Installing wg1.conf locally..."
-sudo cp "$TMPDIR/client-wg1.conf" /etc/wireguard/wg1.conf
-sudo chmod 600 /etc/wireguard/wg1.conf
-print_success "Client config installed"
+if [[ "$IS_FIRST_VM" == "true" ]]; then
+ echo "Installing fresh wg1.conf locally (first VM — replaces any existing config)..."
+ sudo cp "$TMPDIR/client-wg1.conf" /etc/wireguard/wg1.conf
+ sudo chmod 600 /etc/wireguard/wg1.conf
+ print_success "Client config installed"
+else
+ echo "Adding peer ${SERVER_WG_IP} to existing wg1.conf (additional VM)..."
+ update_peer_in_client_config "$SERVER_WG_IP" "$SERVER_PUBLIC_KEY" "$VM_IP"
+ print_success "Peer added to client config"
+fi
+
+# Update /etc/hosts so that WG_HOSTNAME resolves to the VM's WireGuard IP.
+# hyperstack.rb uses this hostname in test URLs and informational output.
+echo "Updating /etc/hosts: ${SERVER_WG_IP} ${WG_HOSTNAME}..."
+sudo sed -i "/ ${WG_HOSTNAME}$/d" /etc/hosts # Remove stale entry if present
+echo "${SERVER_WG_IP} ${WG_HOSTNAME}" | sudo tee -a /etc/hosts > /dev/null
+print_success "/etc/hosts updated"
-# Stop existing wg1 if running, then start fresh
-echo "Starting wg1 locally..."
-sudo systemctl stop wg-quick@wg1 2>/dev/null || true
+echo "Restarting wg1 locally..."
+sudo systemctl stop wg-quick@wg1 2>/dev/null || true
sudo systemctl start wg-quick@wg1
-print_success "wg1 started locally"
+print_success "wg1 restarted locally"
echo ""
echo "=============================================="
print_success "Setup complete!"
echo "=============================================="
echo ""
-echo "WireGuard wg1 tunnel is now active."
-echo ""
-echo "Tunnel IPs:"
-echo " hyperstack (server): ${SERVER_WG_IP}"
-echo " earth (client): ${CLIENT_WG_IP}"
+echo "WireGuard wg1 tunnel peer active:"
+echo " hyperstack VM (server): ${SERVER_WG_IP} (${WG_HOSTNAME})"
+echo " earth (client): ${CLIENT_WG_IP}"
echo ""
echo "=== Verification commands ==="
echo ""
@@ -278,8 +376,8 @@ echo ""
echo "# Verify default route is UNCHANGED:"
echo "ip route | grep default"
echo ""
-echo "# Test Ollama access:"
-echo "curl http://${SERVER_WG_IP}:11434/api/tags"
+echo "# Test vLLM access:"
+echo "curl http://${WG_HOSTNAME}:11434/v1/models"
echo ""
echo "=== Manual start/stop commands ==="
echo ""
@@ -291,8 +389,3 @@ echo "sudo systemctl start wg-quick@wg1"
echo ""
echo "# Restart on hyperstack (if VM rebooted):"
echo "ssh ${SSH_USER}@${VM_IP} 'sudo systemctl start wg-quick@wg1'"
-echo ""
-echo "=== Use Ollama remotely ==="
-echo ""
-echo "export OLLAMA_HOST=http://${SERVER_WG_IP}:11434"
-echo "curl http://${SERVER_WG_IP}:11434/v1/models"