diff --git a/migration.sh b/migration.sh new file mode 100755 index 0000000..50e49b6 --- /dev/null +++ b/migration.sh @@ -0,0 +1,111 @@ +#!/bin/bash -eu +# ============================================================================= +# migration.sh — WireGuard Go → FastAPI migration +# Run as Linux user (not root). Asks for sudo once. +# ============================================================================= +if [ "$EUID" -eq 0 ]; then + echo "[X] Run as your Linux user, not root: bash migration.sh" + exit 1 +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +OPERATOR_DIR="$HOME/operator-docker" +BACKUP_DIR="$HOME/.wg-migration" +LOG="$BACKUP_DIR/migration.log" +REPO_URL="git@git.simplifiedprivacy.com:zenaku/operator-docker.git" +TOTAL_STEPS=4 + +# ─── Borrow UI from operator-docker if available, else minimal fallback ─────── +if [ -f "$OPERATOR_DIR/lib/ui.sh" ]; then + source "$OPERATOR_DIR/lib/ui.sh" + source "$OPERATOR_DIR/lib/log.sh" + source "$OPERATOR_DIR/lib/steps.sh" +else + RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; RESET='\033[0m' + ok() { echo -e "${GREEN}[OK]${RESET} $1"; [ -f "$LOG" ] && echo "[OK] $1" >> "$LOG"; } + info() { echo -e "${CYAN}[i]${RESET} $1"; [ -f "$LOG" ] && echo "[i] $1" >> "$LOG"; } + warn() { echo -e "${YELLOW}[!]${RESET} $1"; [ -f "$LOG" ] && echo "[!] $1" >> "$LOG"; } + fail() { echo -e "${RED}[X]${RESET} $1"; [ -f "$LOG" ] && echo "[X] $1" >> "$LOG"; exit 1; } + print_header() { echo ""; echo " $1 $2"; echo ""; } + print_section() { echo -e "\n${CYAN}─── $1 ───${RESET}"; } + log_ok() { ok "$1"; } + log_info() { info "$1"; } + log_warn() { warn "$1"; } + log_error() { fail "$1"; } +fi + +mkdir -p "$BACKUP_DIR" +: > "$LOG" 2>/dev/null || true + +print_header "WireGuard Migration" "v1.0" + +# ─── SUDO once ──────────────────────────────────────────────────────────────── +info "Requesting sudo credentials (used throughout migration)..." +sudo -v || fail "sudo required. Check your permissions." +ok "Sudo credentials cached." + +# ─── STEP 1: SSH + Clone ────────────────────────────────────────────────────── +print_section "Step 1/$TOTAL_STEPS — Verify SSH & Clone operator-docker" + +[ -f "$HOME/.ssh/id_ed25519" ] || fail "SSH key not found. Generate one or run 00-backup.sh first." + +SSH_TEST=$(GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10' \ + ssh -T git@git.simplifiedprivacy.com 2>&1 || true) + +if echo "$SSH_TEST" | grep -qiE "Welcome|successfully authenticated"; then + ok "SSH access authorized." +else + warn "SSH not authorized. Share this key with Simplified Privacy:" + echo "" + cat "$HOME/.ssh/id_ed25519.pub" + echo "" + fail "Once authorized, run again: bash migration.sh" +fi + +if [ -d "$OPERATOR_DIR" ]; then + warn "operator-docker already exists — skipping clone." +else + info "Cloning operator-docker..." + GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no' \ + git clone "$REPO_URL" "$OPERATOR_DIR" || fail "Could not clone repository." + ok "Cloned to $OPERATOR_DIR" +fi + +# Borrow UI from freshly cloned repo +if [ -f "$OPERATOR_DIR/lib/ui.sh" ]; then + source "$OPERATOR_DIR/lib/ui.sh" + source "$OPERATOR_DIR/lib/log.sh" + source "$OPERATOR_DIR/lib/steps.sh" +fi + +# ─── STEP 2: Backup & cleanup (needs root) ──────────────────────────────────── +print_section "Step 2/$TOTAL_STEPS — Backup & cleanup" + +[ -f "$BACKUP_DIR/private_key" ] && ok "Backup already exists — skipping step 2." || { + info "Running backup (sudo required)..." + sudo OPERATOR_DIR="$OPERATOR_DIR" bash "$SCRIPT_DIR/steps/00-backup.sh" || fail "Backup failed." +} + +# ─── STEP 3: Install operator-docker ───────────────────────────────────────── +print_section "Step 3/$TOTAL_STEPS — Install operator-docker" + +[ -f "$OPERATOR_DIR/.env" ] && ok ".env exists — skipping install." || { + info "Running installer..." + cd "$OPERATOR_DIR" + sudo systemctl start nginx 2>/dev/null || true + echo "$HOME" > /tmp/.migration_home + sudo MIGRATION_MODE=1 bash install.sh || { rm -f /tmp/.migration_home; fail "Installer failed."; } + rm -f /tmp/.migration_home + ok "operator-docker installed." +} + +# ─── STEP 4: Migrate peers & identity ──────────────────────────────────────── +print_section "Step 4/$TOTAL_STEPS — Migrate WireGuard identity & peers" + +bash "$SCRIPT_DIR/steps/02-peers.sh" || fail "Peer migration failed." + +echo "" +echo "=========================================" +echo " Migration complete" +echo " Log: cat $LOG" +echo "=========================================" diff --git a/rollback.sh b/rollback.sh new file mode 100755 index 0000000..c7abdee --- /dev/null +++ b/rollback.sh @@ -0,0 +1,107 @@ +#!/bin/bash -eu +# ============================================================================= +# rollback.sh — Revert to Go legacy WireGuard +# Run as Linux user (not root). +# ============================================================================= +if [ "$EUID" -eq 0 ]; then + echo "[X] Run as your Linux user, not root: bash rollback.sh" + exit 1 +fi + +BACKUP_DIR="$HOME/.wg-migration" +OPERATOR_DIR="$HOME/operator-docker" +LOG="$BACKUP_DIR/migration.log" + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; RESET='\033[0m' +ok() { echo -e "${GREEN}[OK]${RESET} $1"; echo "[OK-ROLLBACK] $1" >> "$LOG" 2>/dev/null || true; } +warn() { echo -e "${YELLOW}[!]${RESET} $1"; echo "[!-ROLLBACK] $1" >> "$LOG" 2>/dev/null || true; } +fail() { echo -e "${RED}[X]${RESET} $1"; echo "[X-ROLLBACK] $1" >> "$LOG" 2>/dev/null || true; exit 1; } +sep() { echo -e "\n${CYAN}────────────────────────────────────${RESET}"; } + +[ -f "$BACKUP_DIR/legacy_compose_dir" ] || fail "No backup found at $BACKUP_DIR. Cannot rollback." + +LEGACY_COMPOSE_DIR=$(cat "$BACKUP_DIR/legacy_compose_dir") +[ -d "$LEGACY_COMPOSE_DIR" ] || fail "Legacy directory not found: $LEGACY_COMPOSE_DIR" + +echo "" +echo "=========================================" +echo " Rollback — Restoring Go legacy" +echo " $(date '+%Y-%m-%d %H:%M:%S')" +echo "=========================================" + +sudo -v || fail "sudo required." + +# ─── Stop FastAPI stack ─────────────────────────────────────────────────────── +sep +warn "Stopping operator-docker..." +if [ -f "$OPERATOR_DIR/docker-compose.yml" ]; then + cd "$OPERATOR_DIR" + docker compose down -v --rmi all --remove-orphans &>/dev/null || true + ok "operator-docker stopped." +else + warn "operator-docker not found — skipping." +fi + +# ─── Remove certbot certs for FastAPI subdomains ────────────────────────────── +sep +warn "Removing FastAPI SSL certificates..." +if [ -f "$OPERATOR_DIR/.env" ]; then + set -a; source "$OPERATOR_DIR/.env"; set +a + for subdomain in "${API_SUBDOMAIN:-}" "${MARZBAN_SUBDOMAIN:-}" "${VPN_SUBDOMAIN:-}" "${HYSTERIA_SUBDOMAIN:-}"; do + [ -z "$subdomain" ] && continue + CERT="${subdomain}.${BASE_DOMAIN}" + sudo certbot delete --cert-name "$CERT" --non-interactive &>/dev/null && ok "Deleted: $CERT" || warn "Not found: $CERT" + done +else + warn ".env not found — skipping cert deletion" +fi + +# ─── Clean nginx ────────────────────────────────────────────────────────────── +sep +warn "Cleaning nginx..." +sudo rm -f /etc/nginx/sites-enabled/operator /etc/nginx/sites-available/operator /etc/nginx/mtls-ca.cert.pem + +# Restore backed-up nginx configs +if [ -d "$BACKUP_DIR/nginx/sites-available" ]; then + sudo cp -r "$BACKUP_DIR/nginx/sites-available/." /etc/nginx/sites-available/ 2>/dev/null || true + sudo cp -r "$BACKUP_DIR/nginx/sites-enabled/." /etc/nginx/sites-enabled/ 2>/dev/null || true + ok "Nginx configs restored from backup." +fi + +sudo nginx -t && sudo systemctl reload nginx 2>/dev/null && ok "Nginx reloaded." || warn "Nginx reload failed." + +# ─── Restore SSL certs ──────────────────────────────────────────────────────── +sep +warn "Restoring legacy SSL certificates..." +if [ -d "$BACKUP_DIR/letsencrypt" ]; then + sudo cp -r "$BACKUP_DIR/letsencrypt/." /etc/letsencrypt/ 2>/dev/null || true + ok "Certificates restored." +else + warn "No certificate backup found." +fi + +# ─── Remove operator-docker directory ───────────────────────────────────────── +sep +warn "Removing operator-docker directory..." +sudo rm -rf "$OPERATOR_DIR" +ok "operator-docker removed." + +# ─── Start Go legacy ────────────────────────────────────────────────────────── +sep +info() { echo -e "${CYAN}[i]${RESET} $1"; } +info "Starting Go legacy stack..." +sudo modprobe ip6table_filter 2>/dev/null || true +cd "$LEGACY_COMPOSE_DIR" +docker compose up -d 2>/dev/null || true +sleep 5 + +LEGACY_UP=$(docker ps --format '{{.Names}}' | grep -i wireguard | head -1 || true) +[ -n "$LEGACY_UP" ] && ok "Legacy WireGuard running: $LEGACY_UP" || \ + warn "Legacy may not have started. Check: docker ps" + +echo "" +echo "=========================================" +echo " Rollback complete" +echo " Legacy stack: $LEGACY_COMPOSE_DIR" +echo " Log: cat $LOG" +echo "=========================================" diff --git a/steps/00-backup.sh b/steps/00-backup.sh new file mode 100644 index 0000000..253bd90 --- /dev/null +++ b/steps/00-backup.sh @@ -0,0 +1,261 @@ +#!/bin/bash -eu +# ============================================================================= +# 00-migrate-root.sh — Run as ROOT +# 1. Full backup (keys, peers, nginx, certs, ufw, env) +# 2. Shuts down legacy stack +# 3. Cleans system for fresh install +# ============================================================================= +if [ "$EUID" -ne 0 ]; then + echo "[X] Run as root: sudo bash 00-migrate-root.sh" + exit 1 +fi + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; RESET='\033[0m' + +LINUX_USER="${SUDO_USER:-}" +if [ -z "$LINUX_USER" ] || [ "$LINUX_USER" = "root" ]; then + LINUX_USER=$(getent passwd | awk -F: '$3 >= 1000 && $3 < 65534 {print $1}' | head -1 || true) +fi +[ -z "$LINUX_USER" ] && { echo -e "${RED}[X]${RESET} Could not detect Linux user."; exit 1; } +LINUX_HOME=$(getent passwd "$LINUX_USER" | cut -d: -f6) + +BACKUP_DIR="$LINUX_HOME/.wg-migration" +LOG="$BACKUP_DIR/migration.log" + +mkdir -p "$BACKUP_DIR" +chmod 700 "$BACKUP_DIR" +chown "$LINUX_USER":"$LINUX_USER" "$BACKUP_DIR" +: > "$LOG" +chown "$LINUX_USER":"$LINUX_USER" "$LOG" + +ok() { echo -e "${GREEN}[OK]${RESET} $1"; echo "[OK] $1" >> "$LOG"; } +info() { echo -e "${CYAN}[i]${RESET} $1"; echo "[i] $1" >> "$LOG"; } +warn() { echo -e "${YELLOW}[!]${RESET} $1"; echo "[!] $1" >> "$LOG"; } +fail() { echo -e "${RED}[X]${RESET} $1"; echo "[X] $1" >> "$LOG"; exit 1; } +sep() { echo -e "\n${CYAN}------------------------------------${RESET}"; } + +_save() { + if [ -e "$1" ]; then + cp -r "$1" "$BACKUP_DIR/$2" 2>/dev/null || true + chown -R "$LINUX_USER":"$LINUX_USER" "$BACKUP_DIR/$2" 2>/dev/null || true + ok "Saved: $1 → $2" + else + warn "Not found (skipped): $1" + fi +} + +echo "" +echo "=========================================" +echo " 00 - Backup, cleanup and preparation" +echo " $(date '+%Y-%m-%d %H:%M:%S')" +echo " User : $LINUX_USER" +echo " Backup : $BACKUP_DIR" +echo "=========================================" +echo "[START-00] $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG" + +# ─── DOCKER ─────────────────────────────────────────────────────────────────── +sep +info "Checking Docker..." +command -v docker &>/dev/null || fail "Docker not installed." +ok "Docker: $(docker --version)" + +# ─── DETECT LEGACY WIREGUARD ────────────────────────────────────────────────── +sep +info "Detecting legacy WireGuard container..." +LEGACY_WG=$(docker ps --format '{{.Names}} {{.Image}}' | \ + grep -i "linuxserver/wireguard\|lscr.io/linuxserver/wireguard" | \ + awk '{print $1}' | head -1 || true) +if [ -z "$LEGACY_WG" ]; then + LEGACY_WG=$(docker ps --format '{{.Names}}' | \ + grep -iE "^wireguard$|^wg$|^sp-wireguard$" | head -1 || true) +fi +[ -z "$LEGACY_WG" ] && fail "No legacy WireGuard container found. Check: docker ps" +ok "Legacy WireGuard: $LEGACY_WG" + +# ─── DETECT LEGACY STACK ────────────────────────────────────────────────────── +sep +info "Detecting legacy stack location..." +LEGACY_COMPOSE_DIR="" + +# Search in known paths first +for DIR in "$LINUX_HOME/sp-env-wireguard" \ + "$LINUX_HOME/sp-env-wireguard-ipv6" \ + "$LINUX_HOME/wireguard" \ + /root/sp-env-wireguard \ + /root/sp-env-wireguard-ipv6 \ + /opt/sp-env-wireguard \ + /srv/wireguard; do + if [ -f "$DIR/docker-compose.yml" ] && grep -q "wireguard" "$DIR/docker-compose.yml" 2>/dev/null; then + LEGACY_COMPOSE_DIR="$DIR" + break + fi +done + +# Fallback: search entire home directory +if [ -z "$LEGACY_COMPOSE_DIR" ]; then + LEGACY_COMPOSE_DIR=$(find "$LINUX_HOME" -maxdepth 6 -name "docker-compose.yml" 2>/dev/null | \ + while read -r f; do + grep -q "wireguard" "$f" 2>/dev/null && dirname "$f" && break + done | head -1) +fi + +[ -z "$LEGACY_COMPOSE_DIR" ] && fail "Legacy docker-compose.yml not found." +ok "Legacy stack: $LEGACY_COMPOSE_DIR" +echo "$LEGACY_COMPOSE_DIR" > "$BACKUP_DIR/legacy_compose_dir" +chown "$LINUX_USER":"$LINUX_USER" "$BACKUP_DIR/legacy_compose_dir" + +# ─── BACKUP WIREGUARD KEYS AND PEERS ────────────────────────────────────────── +sep +info "Backing up WireGuard keys and peers..." + +WG_CONF_CONTENT=$(docker exec "$LEGACY_WG" cat /config/wg_confs/wg0.conf 2>/dev/null || true) +[ -z "$WG_CONF_CONTENT" ] && fail "Could not read wg0.conf from container $LEGACY_WG" + +LEGACY_PRIVATE_KEY=$(echo "$WG_CONF_CONTENT" | grep "^PrivateKey" | awk '{print $3}' | tr -d '[:space:]') +[ -z "$LEGACY_PRIVATE_KEY" ] && fail "PrivateKey not found in wg0.conf" +echo "$LEGACY_PRIVATE_KEY" > "$BACKUP_DIR/private_key" +chmod 600 "$BACKUP_DIR/private_key" +chown "$LINUX_USER":"$LINUX_USER" "$BACKUP_DIR/private_key" +ok "Private key saved." + +LEGACY_PUBLIC_KEY=$(docker exec "$LEGACY_WG" cat /config/server/publickey-server 2>/dev/null | tr -d '[:space:]' || true) +if [ -z "$LEGACY_PUBLIC_KEY" ]; then + warn "publickey-server not found — deriving from private key..." + LEGACY_PUBLIC_KEY=$(echo "$LEGACY_PRIVATE_KEY" | wg pubkey 2>/dev/null || true) +fi +[ -z "$LEGACY_PUBLIC_KEY" ] && fail "Could not obtain public key." +echo "$LEGACY_PUBLIC_KEY" > "$BACKUP_DIR/public_key" +chmod 600 "$BACKUP_DIR/public_key" +chown "$LINUX_USER":"$LINUX_USER" "$BACKUP_DIR/public_key" +ok "Public key: $LEGACY_PUBLIC_KEY" + +PEER_CONF=$(docker exec "$LEGACY_WG" wg showconf wg0 2>/dev/null || echo "$WG_CONF_CONTENT") +echo "$PEER_CONF" > "$BACKUP_DIR/wg0-legacy.conf" +cp "$BACKUP_DIR/wg0-legacy.conf" "$BACKUP_DIR/wg0-legacy.conf.original" +chmod 600 "$BACKUP_DIR/wg0-legacy.conf" "$BACKUP_DIR/wg0-legacy.conf.original" +chown "$LINUX_USER":"$LINUX_USER" "$BACKUP_DIR/wg0-legacy.conf" "$BACKUP_DIR/wg0-legacy.conf.original" +PEER_COUNT=$(grep -c "^\[Peer\]" "$BACKUP_DIR/wg0-legacy.conf" || echo 0) +ok "$PEER_COUNT peers saved." + +# ─── BACKUP NGINX ───────────────────────────────────────────────────────────── +sep +info "Backing up Nginx..." +mkdir -p "$BACKUP_DIR/nginx" +mkdir -p "$BACKUP_DIR/nginx/sites-available" "$BACKUP_DIR/nginx/sites-enabled" +cp -r /etc/nginx/sites-available/. "$BACKUP_DIR/nginx/sites-available/" 2>/dev/null || true +cp -r /etc/nginx/sites-enabled/. "$BACKUP_DIR/nginx/sites-enabled/" 2>/dev/null || true +_save /etc/nginx/nginx.conf nginx/nginx.conf +chown -R "$LINUX_USER":"$LINUX_USER" "$BACKUP_DIR/nginx/" +ok "Nginx configs backed up." + +# ─── BACKUP SSL CERTIFICATES ────────────────────────────────────────────────── +sep +info "Backing up SSL certificates..." +mkdir -p "$BACKUP_DIR/letsencrypt" +if [ -d /etc/letsencrypt ]; then + cp -r /etc/letsencrypt/. "$BACKUP_DIR/letsencrypt/" + chown -R "$LINUX_USER":"$LINUX_USER" "$BACKUP_DIR/letsencrypt/" + ok "Certificates saved." +else + warn "/etc/letsencrypt not found." +fi + +# ─── BACKUP UFW ─────────────────────────────────────────────────────────────── +sep +info "Backing up UFW rules..." +ufw status verbose > "$BACKUP_DIR/ufw-snapshot.txt" 2>/dev/null || true +chown "$LINUX_USER":"$LINUX_USER" "$BACKUP_DIR/ufw-snapshot.txt" +ok "UFW snapshot saved." + +# ─── BACKUP LEGACY ENV ──────────────────────────────────────────────────────── +sep +info "Backing up legacy .env..." +_save "$LEGACY_COMPOSE_DIR/.env" legacy.env + +# ─── DETECT NETWORK ─────────────────────────────────────────────────────────── +IPV6_ADDRS=$(ip -6 addr show | grep -oP 'inet6 \K[0-9a-fA-F:]+' | grep -v '^::1$' || true) +if [ -n "$IPV6_ADDRS" ]; then + echo "ipv6=true" > "$BACKUP_DIR/network_info" + ok "IPv6 detected." +else + echo "ipv6=false" > "$BACKUP_DIR/network_info" + info "IPv4 only." +fi +chown "$LINUX_USER":"$LINUX_USER" "$BACKUP_DIR/network_info" + +# ─── SSH KEY ────────────────────────────────────────────────────────────────── +sep +info "Adding $LINUX_USER to docker group..." +usermod -aG docker "$LINUX_USER" 2>/dev/null || true +ok "$LINUX_USER added to docker group. Changes take effect on next login." + +info "Checking SSH key for private repo..." +mkdir -p "$LINUX_HOME/.ssh" +chmod 700 "$LINUX_HOME/.ssh" +chown "$LINUX_USER":"$LINUX_USER" "$LINUX_HOME/.ssh" +if [ ! -f "$LINUX_HOME/.ssh/id_ed25519" ]; then + sudo -u "$LINUX_USER" ssh-keygen -t ed25519 -f "$LINUX_HOME/.ssh/id_ed25519" -N "" -q + ok "SSH key generated." +else + ok "SSH key already exists." +fi +SSH_PUBKEY=$(cat "$LINUX_HOME/.ssh/id_ed25519.pub") +echo "$SSH_PUBKEY" > "$BACKUP_DIR/ssh_pubkey" +chown "$LINUX_USER":"$LINUX_USER" "$BACKUP_DIR/ssh_pubkey" + +# ─── SHUT DOWN LEGACY STACK ─────────────────────────────────────────────────── +sep +warn "Shutting down legacy stack..." +cd "$LEGACY_COMPOSE_DIR" +docker compose down --remove-orphans 2>/dev/null || true + +for CNAME in $(docker ps -a --format '{{.Names}} {{.Image}}' | \ + grep -iE "linuxserver/wireguard|lscr.io/linuxserver/wireguard|wireguard-api|sp-wireguard-api" | \ + awk '{print $1}' || true); do + docker stop "$CNAME" 2>/dev/null || true + docker rm "$CNAME" 2>/dev/null || true + warn "Removed: $CNAME" +done +ok "Legacy stack stopped." + +# ─── CLEAN NGINX ────────────────────────────────────────────────────────────── +sep +info "Cleaning Nginx for fresh install..." +for SITE in /etc/nginx/sites-enabled/*; do + rm -f "$SITE" && warn "Removed from sites-enabled: $(basename $SITE)" +done +for SITE in /etc/nginx/sites-available/*; do + NAME=$(basename "$SITE") + [ "$NAME" = "default" ] && continue + rm -f "$SITE" && warn "Removed from sites-available: $NAME" +done +rm -f /etc/nginx/sites-enabled/certbot-temp /etc/nginx/sites-available/certbot-temp 2>/dev/null || true +systemctl stop nginx 2>/dev/null || true +ok "Nginx cleaned." + +# ─── VERIFY PORT 51820 FREE ─────────────────────────────────────────────────── +sep +info "Checking port 51820/UDP..." +sleep 3 +ss -ulnp 2>/dev/null | grep -q ":51820" && fail "Port 51820 still in use." || ok "Port 51820/UDP free." + +echo "[END-00] $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG" + +echo "" +echo "=========================================" +echo " Backup complete — System ready" +echo "=========================================" +echo "" +echo " Backup dir : $BACKUP_DIR" +echo " Legacy stack : $LEGACY_COMPOSE_DIR" +echo " Public key : $LEGACY_PUBLIC_KEY" +echo " Peers saved : $PEER_COUNT" +echo "" +echo " SSH key for Simplified Privacy:" +echo " $SSH_PUBKEY" +echo "" +echo " Once SP authorizes access, run:" +echo " bash 01-migrate-user.sh" +echo "" +echo " Log: cat $LOG" +echo "=========================================" diff --git a/steps/02-peers.sh b/steps/02-peers.sh new file mode 100644 index 0000000..7e9ea91 --- /dev/null +++ b/steps/02-peers.sh @@ -0,0 +1,277 @@ +#!/bin/bash -eu +# ============================================================================= +# 02-migrate-peers.sh — Run as Linux user after 01 +# 1. Shuts down legacy WireGuard +# 2. Injects legacy private key into new wg0.conf +# 3. Starts new WireGuard +# 4. Verifies public key is identical +# 5. Migrates peers via API +# 6. Destroys private key +# Auto-rollback: restores legacy if anything fails +# ============================================================================= +if [ "$EUID" -eq 0 ]; then + echo "[X] Run as your Linux user, not root" + exit 1 +fi + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; RESET='\033[0m' + +BACKUP_DIR="$HOME/.wg-migration" +LOG="$BACKUP_DIR/migration.log" +OPERATOR_DIR="$HOME/operator-docker" +WG_CONF="$OPERATOR_DIR/services/wireguard/config/wg_confs/wg0.conf" +WG_CONF_BAK="" + +ok() { echo -e "${GREEN}[OK]${RESET} $1"; echo "[OK] $1" >> "$LOG"; } +info() { echo -e "${CYAN}[i]${RESET} $1"; echo "[i] $1" >> "$LOG"; } +warn() { echo -e "${YELLOW}[!]${RESET} $1"; echo "[!] $1" >> "$LOG"; } +fail() { echo -e "${RED}[X]${RESET} $1"; echo "[X] $1" >> "$LOG"; exit 1; } +sep() { echo -e "\n${CYAN}------------------------------------${RESET}"; } + +rollback_02() { + echo "" + warn "Error detected — starting rollback..." + echo "[ROLLBACK-02] $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG" + + [ -n "$WG_CONF_BAK" ] && [ -f "$WG_CONF_BAK" ] && \ + cp "$WG_CONF_BAK" "$WG_CONF" && warn "wg0.conf restored." + + cd "$OPERATOR_DIR" 2>/dev/null || true + docker compose stop wireguard 2>/dev/null || true + + LEGACY_COMPOSE_DIR=$(cat "$BACKUP_DIR/legacy_compose_dir" 2>/dev/null || true) + if [ -n "$LEGACY_COMPOSE_DIR" ] && [ -d "$LEGACY_COMPOSE_DIR" ]; then + sudo modprobe ip6table_filter 2>/dev/null || true + cd "$LEGACY_COMPOSE_DIR" + docker compose up -d 2>/dev/null || true + sleep 5 + LEGACY_UP=$(docker ps --format '{{.Names}}' | grep -i wireguard | head -1 || true) + [ -n "$LEGACY_UP" ] && ok "Legacy restored: $LEGACY_UP" || \ + warn "Legacy failed to start. Run manually: cd $LEGACY_COMPOSE_DIR && docker compose up -d" + fi + warn "Rollback complete. Check: cat $LOG" +} +trap rollback_02 ERR + +echo "" +echo "=========================================" +echo " 02 - Identity and peer migration" +echo " $(date '+%Y-%m-%d %H:%M:%S')" +echo "=========================================" +echo "[START-02] $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG" + +# ─── PREREQUISITES ──────────────────────────────────────────────────────────── +sep +info "Checking prerequisites..." + +if ! docker info &>/dev/null; then + if groups | grep -q docker; then + fail "Docker socket not accessible. Try logging out and back in." + else + warn "No Docker access — re-launching with docker group..." + exec newgrp docker "$0" "$@" + fi +fi + +[ -f "$BACKUP_DIR/private_key" ] || fail "No private key backup found." +[ -f "$BACKUP_DIR/wg0-legacy.conf" ] || fail "No peer backup found." +[ -f "$BACKUP_DIR/public_key" ] || fail "No public key backup found." +[ -f "$BACKUP_DIR/legacy_compose_dir" ] || fail "No legacy snapshot found." +ok "Backups found." + +[ -d "$OPERATOR_DIR" ] || fail "operator-docker not found. Run 01 first." +[ -f "$WG_CONF" ] || fail "wg0.conf not found: $WG_CONF" +ok "operator-docker found." + +for CONTAINER in operator-api mysql; do + STATUS=$(docker inspect -f '{{.State.Running}}' "$CONTAINER" 2>/dev/null || echo "false") + [ "$STATUS" = "true" ] || fail "Container '$CONTAINER' is not running." + ok "Running: $CONTAINER" +done + +# ─── LOAD CREDENTIALS ───────────────────────────────────────────────────────── +sep +info "Loading credentials..." +set -a; source "$OPERATOR_DIR/.env"; set +a + +[ -z "${API_TOKEN:-}" ] && fail "API_TOKEN not found in .env" +[ -z "${BASE_DOMAIN:-}" ] && fail "BASE_DOMAIN not found in .env" +[ -z "${API_SUBDOMAIN:-}" ] && fail "API_SUBDOMAIN not found in .env" + +echo "$BASE_DOMAIN" | grep -qE '[/;|&<> ]|^[0-9]+-' && \ + fail "Invalid BASE_DOMAIN in .env: '$BASE_DOMAIN'" + +API_URL="https://${API_SUBDOMAIN}.${BASE_DOMAIN}" +ok "API URL: $API_URL" + +# ─── STOP LEGACY WIREGUARD ──────────────────────────────────────────────────── +sep +info "Stopping legacy WireGuard..." +LEGACY_COMPOSE_DIR=$(cat "$BACKUP_DIR/legacy_compose_dir") +[ -d "$LEGACY_COMPOSE_DIR" ] || fail "Legacy directory not found: $LEGACY_COMPOSE_DIR" + +cd "$LEGACY_COMPOSE_DIR" +docker compose stop wireguard 2>/dev/null || true +sleep 3 +STILL_UP=$(docker compose ps --services --filter "status=running" 2>/dev/null | grep -i wireguard || true) +[ -n "$STILL_UP" ] && fail "Legacy WireGuard is still running." +ok "Legacy WireGuard stopped." + +# ─── BACKUP NEW CONF ────────────────────────────────────────────────────────── +sep +info "Backing up new wg0.conf..." +WG_CONF_BAK="$WG_CONF.bak.$(date +%s)" +cp "$WG_CONF" "$WG_CONF_BAK" +ok "Backup: $WG_CONF_BAK" + +# ─── INJECT PRIVATE KEY ─────────────────────────────────────────────────────── +sep +info "Injecting legacy private key..." +LEGACY_PRIVATE_KEY=$(cat "$BACKUP_DIR/private_key" | tr -d '[:space:]') +LEGACY_PUBLIC_KEY=$(cat "$BACKUP_DIR/public_key" | tr -d '[:space:]') +[ -z "$LEGACY_PRIVATE_KEY" ] && fail "Private key is empty." +[ -z "$LEGACY_PUBLIC_KEY" ] && fail "Public key is empty." + +sed -i "s|^PrivateKey = .*|PrivateKey = $LEGACY_PRIVATE_KEY|" "$WG_CONF" +ok "Private key written to wg0.conf." + +WG_SERVER_KEY="$OPERATOR_DIR/services/wireguard/config/server/privatekey-server" +WG_SERVER_PUB="$OPERATOR_DIR/services/wireguard/config/server/publickey-server" +mkdir -p "$(dirname "$WG_SERVER_KEY")" +echo "$LEGACY_PRIVATE_KEY" > "$WG_SERVER_KEY" +echo "$LEGACY_PUBLIC_KEY" > "$WG_SERVER_PUB" +ok "Private key written to server keyfile." + +# ─── START NEW WIREGUARD ────────────────────────────────────────────────────── +sep +info "Starting new WireGuard with legacy identity..." +echo "[WIREGUARD-START] $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG" + +cd "$OPERATOR_DIR" +sudo modprobe ip6table_filter 2>/dev/null || true +docker compose up -d --force-recreate wireguard +info "Waiting for startup..." +sleep 8 + +STATUS=$(docker inspect -f '{{.State.Running}}' wireguard 2>/dev/null || echo "false") +[ "$STATUS" = "true" ] || fail "New WireGuard failed to start. Check: docker logs wireguard" +ok "New WireGuard running." + +# ─── VERIFY PUBLIC KEY ──────────────────────────────────────────────────────── +sep +info "Verifying server identity..." + +NEW_PUBLIC_KEY=$(docker exec wireguard cat /config/server/publickey-server 2>/dev/null | \ + tr -d '[:space:]' || true) +if [ -z "$NEW_PUBLIC_KEY" ]; then + NEW_PUBLIC_KEY=$(docker exec wireguard wg show wg0 2>/dev/null | \ + grep "public key" | awk '{print $NF}' | tr -d '[:space:]' || true) +fi +[ -z "$NEW_PUBLIC_KEY" ] && fail "Could not retrieve public key from new WireGuard." + +if [ "$NEW_PUBLIC_KEY" != "$LEGACY_PUBLIC_KEY" ]; then + warn "Public key MISMATCH!" + warn " Expected : $LEGACY_PUBLIC_KEY" + warn " Got : $NEW_PUBLIC_KEY" + fail "Server identity was not preserved." +fi + +ok "Public key verified: $NEW_PUBLIC_KEY" +ok "Existing users will reconnect automatically." + +# ─── VERIFY API ─────────────────────────────────────────────────────────────── +sep +info "Verifying API..." +HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 15 \ + -H "Authorization: Bearer $API_TOKEN" \ + "$API_URL/wireguard/peers" 2>/dev/null || echo "000") +[ "$HTTP_CODE" = "200" ] || fail "API not responding (HTTP $HTTP_CODE)." +ok "API responding." + +# ─── MIGRATE PEERS ──────────────────────────────────────────────────────────── +sep +info "Migrating peers to new API..." + +LEGACY_CONF=$(cat "$BACKUP_DIR/wg0-legacy.conf") +MIGRATED=0 +FAILED=0 +CURRENT_PUBKEY="" +CURRENT_IPS="" + +migrate_peer() { + local PUBKEY="$1" + local IPS="$2" + + IPS_JSON=$(echo "$IPS" | tr ',' '\n' | \ + sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//' | \ + awk 'NF {print "\"" $0 "\""}' | paste -sd ',' | \ + sed 's/^/[/' | sed 's/$/]/') + + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 15 \ + -X POST "$API_URL/wireguard/peers" \ + -H "Authorization: Bearer $API_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"public_key\": \"$PUBKEY\", \"allowed_ips\": $IPS_JSON}" 2>/dev/null || echo "000") + + if [ "$HTTP_CODE" = "200" ]; then + ok "Peer migrated: ${PUBKEY:0:20}... → $IPS" + MIGRATED=$((MIGRATED + 1)) + else + warn "Peer failed: ${PUBKEY:0:20}... (HTTP $HTTP_CODE)" + FAILED=$((FAILED + 1)) + fi +} + +while IFS= read -r line; do + if [[ "$line" == "[Peer]" ]]; then + if [ -n "$CURRENT_PUBKEY" ] && [ -n "$CURRENT_IPS" ]; then + migrate_peer "$CURRENT_PUBKEY" "$CURRENT_IPS" + fi + CURRENT_PUBKEY="" + CURRENT_IPS="" + elif [[ "$line" =~ ^PublicKey\ =\ (.+)$ ]]; then + CURRENT_PUBKEY="${BASH_REMATCH[1]}" + elif [[ "$line" =~ ^AllowedIPs\ =\ (.+)$ ]]; then + CURRENT_IPS="${BASH_REMATCH[1]}" + fi +done <<< "$LEGACY_CONF" + +[ -n "$CURRENT_PUBKEY" ] && [ -n "$CURRENT_IPS" ] && migrate_peer "$CURRENT_PUBKEY" "$CURRENT_IPS" + +# ─── VERIFY PEERS ───────────────────────────────────────────────────────────── +sep +info "Verifying peers in WireGuard..." +REGISTERED=$(docker exec wireguard wg show wg0 | grep -c "^ peer:" 2>/dev/null || echo 0) +ok "$REGISTERED peers active in wg0." + +# ─── DESTROY PRIVATE KEY ────────────────────────────────────────────────────── +sep +info "Destroying private key..." +command -v shred &>/dev/null && \ + shred -u "$BACKUP_DIR/private_key" 2>/dev/null || \ + rm -f "$BACKUP_DIR/private_key" +ok "Private key deleted from disk." + +trap - ERR +echo "[END-02] $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG" + +echo "" +echo "=========================================" +echo " Migration complete" +echo "=========================================" +echo "" +echo " Public key : $NEW_PUBLIC_KEY" +echo " API : $API_URL" +echo " Peers migrated : $MIGRATED" +if [ "$FAILED" -gt 0 ]; then + echo -e " ${RED}Peers failed : $FAILED${RESET}" + echo " Check: cat $LOG" +else + echo " Errors : none" +fi +echo "" +echo " Existing users reconnect automatically" +echo " without new configuration." +echo "" +echo " Log: cat $LOG" +echo "========================================="