diff --git a/migration.sh b/migration.sh index 34bcdf4..e69de29 100755 --- a/migration.sh +++ b/migration.sh @@ -1,116 +0,0 @@ -#!/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)" -sudo chown -R "$USER:$USER" "$SCRIPT_DIR" 2>/dev/null || true -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"; } - 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() { echo -e "${RED}[X]${RESET} $1"; [ -f "$LOG" ] && echo "[X] $1" >> "$LOG"; } -fi - -# fail() always defined after if/else/fi — never inside a block -fail() { log_error "$1"; exit 1; } - -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: Backup (go-legacy stays running) ──────────────────────────────── -print_section "Step 1/$TOTAL_STEPS — Backup WireGuard identity & peers" - -[ -f "$BACKUP_DIR/private_key" ] && ok "Backup already exists — skipping step 1." || { - info "Running backup (sudo required)..." - sudo bash "$SCRIPT_DIR/steps/00-backup.sh" || fail "Backup failed." -} - -# ─── STEP 2: SSH + Clone operator-docker ───────────────────────────────────── -print_section "Step 2/$TOTAL_STEPS — Verify SSH & Clone operator-docker" - -[ -f "$HOME/.ssh/id_ed25519" ] || fail "SSH key not found. Run step 1 first (00-backup.sh generates it)." - -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 - -# Re-source UI from freshly cloned repo (better version) -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 -# Re-define fail() after re-source so it always exists -fail() { log_error "$1"; exit 1; } - -# ─── STEP 3: Install operator-docker (preflight runs inside as step 3) ─────── -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 (shuts down go-legacy here) ──────────── -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/steps/02-peers.sh b/steps/02-peers.sh index 3ecd8e9..e69de29 100755 --- a/steps/02-peers.sh +++ b/steps/02-peers.sh @@ -1,298 +0,0 @@ -#!/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." -ok "operator-docker found." - -# ─── Start WireGuard once to generate wg0.conf ─────────────────────────────── -if [ ! -f "$WG_CONF" ]; then - info "wg0.conf not found — starting WireGuard to generate it..." - cd "$OPERATOR_DIR" - docker compose up -d wireguard - for i in $(seq 1 15); do - [ -f "$WG_CONF" ] && break - sleep 2 - done - [ -f "$WG_CONF" ] || fail "wg0.conf not generated after 30s. Check: docker logs wireguard" - docker compose stop wireguard - ok "wg0.conf generated." -fi - -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..." -DEADLINE=$(( $(date +%s) + 180 )) - i=0 - while [ $(date +%s) -lt $DEADLINE ]; do - HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ - -H "Authorization: Bearer $API_TOKEN" \ - "$API_URL/wireguard/peers" 2>/dev/null || echo "000") - [ "$HTTP_CODE" = "200" ] && break - i=$(( i + 1 )) - warn "API not ready (HTTP $HTTP_CODE) — waited ${i}x5s..." - sleep 5 - done -[ "$HTTP_CODE" = "200" ] || fail "API not responding after retries (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 "========================================="