From 1f686ba9fef3d858e968f98464e051f25dd84482 Mon Sep 17 00:00:00 2001 From: zenaku Date: Wed, 30 Sep 2026 22:46:37 +0200 Subject: [PATCH] feat: unified migration CLI, remove legacy scripts, update readme --- 00-migrate-root.sh | 258 ------------------------------ 01-migrate-user.sh | 247 ----------------------------- 02-migrate-peers.sh | 277 -------------------------------- readme.md | 378 ++++++++------------------------------------ 4 files changed, 64 insertions(+), 1096 deletions(-) delete mode 100644 00-migrate-root.sh delete mode 100644 01-migrate-user.sh delete mode 100644 02-migrate-peers.sh diff --git a/00-migrate-root.sh b/00-migrate-root.sh deleted file mode 100644 index 6575beb..0000000 --- a/00-migrate-root.sh +++ /dev/null @@ -1,258 +0,0 @@ -#!/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" -_save /etc/nginx/sites-available nginx/sites-available -_save /etc/nginx/sites-enabled nginx/sites-enabled -_save /etc/nginx/nginx.conf nginx/nginx.conf - -# ─── 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/01-migrate-user.sh b/01-migrate-user.sh deleted file mode 100644 index 3c8e65c..0000000 --- a/01-migrate-user.sh +++ /dev/null @@ -1,247 +0,0 @@ -#!/bin/bash -eu -# ============================================================================= -# 01-migrate-user.sh — Run as Linux user (not root) -# Installs operator-docker and prepares for peer migration -# Auto-rollback: restores nginx, certs and legacy stack on failure -# ============================================================================= -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" -REPO_URL="git@git.simplifiedprivacy.com:zenaku/operator-docker.git" -OPERATOR_DIR="$HOME/operator-docker" -USER_HOME="$HOME" - -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_01() { - echo "" - warn "Installation failed — starting rollback..." - echo "[ROLLBACK-01] $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG" - - [ -d "$OPERATOR_DIR" ] && rm -rf "$OPERATOR_DIR" && warn "operator-docker removed." - - 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 - warn "Nginx restored." - fi - - if [ -d "$BACKUP_DIR/letsencrypt" ]; then - sudo cp -r "$BACKUP_DIR/letsencrypt/." /etc/letsencrypt/ 2>/dev/null || true - warn "Certificates restored." - fi - - 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 - - sudo systemctl start nginx 2>/dev/null || true - warn "Rollback complete. Check: cat $LOG" -} -trap rollback_01 ERR - -echo "" -echo "=========================================" -echo " 01 - operator-docker installation" -echo " $(date '+%Y-%m-%d %H:%M:%S')" -echo "=========================================" -echo "[START-01] $(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 backup found. Run: sudo bash 00-migrate-root.sh" -[ -f "$BACKUP_DIR/wg0-legacy.conf" ] || fail "No peer backup found. Run: sudo bash 00-migrate-root.sh" -[ -f "$BACKUP_DIR/legacy_compose_dir" ] || fail "No legacy snapshot found. Run: sudo bash 00-migrate-root.sh" -ok "Backups found." - -# ─── CLEAN FAILED INSTALL ───────────────────────────────────────────────────── -sep -info "Checking for previous installation..." - -_env_is_corrupt() { - local env_file="$1" - [ ! -f "$env_file" ] && return 1 - - local base_domain - base_domain=$(grep -E '^BASE_DOMAIN=' "$env_file" 2>/dev/null \ - | head -1 | cut -d= -f2- | tr -d '[:space:]' || true) - - [ -z "$base_domain" ] && { warn ".env: BASE_DOMAIN is empty."; return 0; } - - echo "$base_domain" | grep -qE '[/;|&<> ]' && { - warn ".env: BASE_DOMAIN contains invalid characters: '$base_domain'"; return 0; } - - echo "$base_domain" | grep -qE '^[0-9]+-|^ash' && { - warn ".env: BASE_DOMAIN looks like a script name: '$base_domain'"; return 0; } - - [ "${#base_domain}" -gt 253 ] && { - warn ".env: BASE_DOMAIN too long (${#base_domain} chars)."; return 0; } - - return 1 -} - -_purge_failed_install() { - warn "Cleaning up failed installation..." - echo "[PURGE] $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG" - - if [ -f "$OPERATOR_DIR/docker-compose.yml" ]; then - cd "$OPERATOR_DIR" - docker compose down --remove-orphans 2>/dev/null && warn "Previous stack stopped." || true - cd "$HOME" - fi - - local project - project=$(basename "$OPERATOR_DIR" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9' '-') - while IFS= read -r cname; do - docker rm -f "$cname" 2>/dev/null && warn "Container removed: $cname" || true - done < <(docker ps -a --format '{{.Names}}' | grep -i "^${project}" 2>/dev/null || true) - - rm -rf "$OPERATOR_DIR" - ok "Directory $OPERATOR_DIR removed." -} - -if [ -d "$OPERATOR_DIR" ]; then - if _env_is_corrupt "$OPERATOR_DIR/.env"; then - warn "Corrupt .env detected from previous attempt." - _purge_failed_install - elif [ ! -f "$OPERATOR_DIR/docker-compose.yml" ]; then - warn "Incomplete repo (missing docker-compose.yml). Cleaning..." - _purge_failed_install - else - warn "Existing directory found. Removing for fresh install..." - _purge_failed_install - fi -else - info "No previous installation found." -fi - -# ─── SSH KEY ────────────────────────────────────────────────────────────────── -sep -info "Checking SSH key..." -[ -f "$HOME/.ssh/id_ed25519" ] || fail "SSH key not found. Run: sudo bash 00-migrate-root.sh" -ok "SSH key found." - -info "Checking access to private repo..." -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 "Repo access authorized." -else - echo "" - warn "Could not authenticate with private repo." - echo "" - echo " Send this SSH key to Simplified Privacy:" - echo "" - cat "$HOME/.ssh/id_ed25519.pub" - echo "" - fail "Once authorized, run again: bash 01-migrate-user.sh" -fi - -# ─── CLONE REPO ─────────────────────────────────────────────────────────────── -sep -info "Cloning operator-docker repository..." - -GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no' \ - git clone "$REPO_URL" "$OPERATOR_DIR" || fail "Could not clone repository." -ok "Repository cloned to $OPERATOR_DIR" - -[ -f "$OPERATOR_DIR/install.sh" ] || fail "install.sh not found after clone. Invalid repo." -ok "install.sh found." - -# ─── INSTALL ────────────────────────────────────────────────────────────────── -sep -info "Running installer..." -info "WireGuard will NOT start — 02 will start it with the legacy identity." -echo "" - -cd "$OPERATOR_DIR" -sudo systemctl start nginx 2>/dev/null || true -echo "$USER_HOME" > /tmp/.migration_home -sudo MIGRATION_MODE=1 bash install.sh || { rm -f /tmp/.migration_home; fail "Installer failed. Check output above."; } -rm -f /tmp/.migration_home - -if _env_is_corrupt "$OPERATOR_DIR/.env"; then - fail "install.sh finished but generated an invalid .env. Check install.sh." -fi -ok ".env generated with valid values." - -# ─── WG0.CONF PLACEHOLDER ───────────────────────────────────────────────────── -sep -info "Checking wg0.conf..." -WG_CONF_DIR="$OPERATOR_DIR/services/wireguard/config/wg_confs" -WG_CONF_FILE="$WG_CONF_DIR/wg0.conf" - -if [ ! -f "$WG_CONF_FILE" ]; then - warn "wg0.conf missing (MIGRATION_MODE skips WireGuard). Creating placeholder..." - mkdir -p "$WG_CONF_DIR" - cat > "$WG_CONF_FILE" << 'WGEOF' -[Interface] -Address = 10.13.13.1/16 -ListenPort = 51820 -PrivateKey = PLACEHOLDER -PostUp = /config/scripts/post-up.sh -PreDown = /config/scripts/pre-down.sh -WGEOF - ok "Placeholder created. 02 will inject the legacy private key." -else - ok "wg0.conf already exists." -fi - -# ─── RESTORE EXTERNAL NGINX CONFIGS ─────────────────────────────────────────── -sep -info "Restoring external Nginx configs..." - -for SITE in "$BACKUP_DIR/nginx/sites-available/"*; do - NAME=$(basename "$SITE") - if ! grep -q "$NAME" /etc/nginx/sites-available/* 2>/dev/null; then - sudo cp "$SITE" "/etc/nginx/sites-available/$NAME" 2>/dev/null || true - if [ -L "$BACKUP_DIR/nginx/sites-enabled/$NAME" ] || \ - [ -f "$BACKUP_DIR/nginx/sites-enabled/$NAME" ]; then - sudo ln -sf "/etc/nginx/sites-available/$NAME" \ - "/etc/nginx/sites-enabled/$NAME" 2>/dev/null || true - ok "Config restored: $NAME" - fi - fi -done - -sudo nginx -t && sudo systemctl reload nginx 2>/dev/null \ - || warn "Nginx reload failed — check manually." - -trap - ERR -echo "[END-01] $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG" - -echo "" -echo "=========================================" -echo " Installation complete" -echo " Next: bash 02-migrate-peers.sh" -echo "=========================================" -echo "" diff --git a/02-migrate-peers.sh b/02-migrate-peers.sh deleted file mode 100644 index 7e9ea91..0000000 --- a/02-migrate-peers.sh +++ /dev/null @@ -1,277 +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." -[ -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 "=========================================" diff --git a/readme.md b/readme.md index 8b10bff..2bf16a5 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ -# migration-guide +# operator-migration -[🇺🇸English](#english) · [🇪🇸Español](#español) +[🇺🇸 English](#english) · [🇪🇸 Español](#español) --- @@ -8,189 +8,64 @@ Zero-downtime migration from the legacy WireGuard + Go API stack to operator-docker. Existing VPN clients reconnect automatically — no new configuration required on their end. ---- - ## How it works +The server's WireGuard identity (private key → public key) is preserved. All peers are migrated to the new API. Clients reconnect without any config change. + ```text -Before migration - Legacy stack (sp-env-wireguard-ipv6) - ├── wireguard (linuxserver/wireguard) - ├── wireguard-api (Go) - └── nginx-certbot - -After migration - operator-docker - ├── wireguard (same identity — same public key) - ├── operator-api (FastAPI) - ├── marzban - ├── hysteria2 - └── mysql +Before After + wireguard (linuxserver) wireguard (same identity) + wireguard-api (Go) operator-api (FastAPI) + nginx-certbot marzban + hysteria2 + mysql ``` -The server's WireGuard identity (private key → public key) is preserved. -All peers are migrated via the new API. Clients reconnect without any config change. - ---- - ## Prerequisites - Legacy stack running (`docker ps` shows `wireguard`) - Domain with DNS pointing to this server -- One subdomain per service (`api`, `legacy`, `cdn`, `news` or similar) -- SSH key authorized by Simplified Privacy (generated in step 00) -- Port 80/TCP reachable externally (required by Let's Encrypt) +- SSH key authorized by Simplified Privacy +- Port 80/TCP open externally (required by Let's Encrypt) ---- +## Migration -## Migration steps - -| Script | Run as | Description | -| :----- | :----- | :---------- | -| `00-migrate-root.sh` | root | Backup, shutdown legacy, clean system | -| `01-migrate-user.sh` | user | Clone repo, install operator-docker | -| `02-migrate-peers.sh` | user | Inject identity, migrate peers | - ---- - -## Usage - -Scripts can be run from any directory: +Run as your Linux user (not root): ```bash -sudo bash ~/migration/00-migrate-root.sh -bash ~/migration/01-migrate-user.sh -bash ~/migration/02-migrate-peers.sh -``` - ---- - -## Step 00 — Backup and cleanup - -Run as root: - -```bash -sudo bash 00-migrate-root.sh +bash migration.sh ``` What it does: -| Action | Detail | -| :----- | :----- | -| Detects legacy WireGuard container | By image or name | -| Backs up private key and public key | Saved to `~/.wg-migration/` | -| Backs up all peers | From `wg showconf wg0` | -| Backs up Nginx configs and SSL certs | Full `/etc/nginx` and `/etc/letsencrypt` | -| Backs up UFW rules | Snapshot only | -| Generates SSH key | For repo access | -| Shuts down legacy stack | `docker compose down` | -| Cleans Nginx | Removes all sites for fresh install | -| Verifies port 51820 is free | Fails if still in use | - -At the end, the script prints the SSH public key. Send it to Simplified Privacy before running `01`. - ---- - -## Step 01 — Install operator-docker - -Once Simplified Privacy authorizes the SSH key, run as your Linux user: - -```bash -bash 01-migrate-user.sh -``` - -What it does: - -| Action | Detail | -| :----- | :----- | -| Validates backups exist | Fails early if `00` was not run | -| Cleans any failed previous install | Detects corrupt `.env`, incomplete repo | -| Clones private repo | Via SSH | -| Runs `install.sh` in migration mode | WireGuard is NOT started yet | -| Creates `wg0.conf` placeholder | `02` will inject the real private key | -| Restores external Nginx configs | e.g. mail server vhosts | - -> `MIGRATION_MODE=1` tells the installer to skip starting WireGuard. The `02` script starts it after injecting the legacy identity. - ---- - -## Step 02 — Migrate identity and peers - -```bash -bash 02-migrate-peers.sh -``` - -What it does: - -| Action | Detail | -| :----- | :----- | -| Stops legacy WireGuard | Via `docker compose stop` in legacy dir | -| Injects legacy private key | Into `wg0.conf` and server keyfiles | -| Starts new WireGuard | With the legacy identity | -| Verifies public key matches | Fails and rolls back if it doesn't | -| Verifies API is responding | `GET /wireguard/peers` must return 200 | -| Migrates all peers | `POST /wireguard/peers` for each peer | -| Destroys private key from disk | `shred` or `rm` | - ---- +1. Requests sudo once — no further prompts +2. Verifies SSH access and clones operator-docker +3. Creates a full backup — WireGuard keys, peers, Nginx, SSL certs +4. Shuts down the legacy stack +5. Runs the operator-docker installer +6. Injects the legacy WireGuard identity into the new stack +7. Migrates all peers via the new API +8. Destroys the private key from disk ## Rollback -Each script has automatic rollback on failure: - -- `00` — No rollback needed (legacy stays running until `02`) -- `01` — Removes `operator-docker`, restores Nginx and certs, restarts legacy -- `02` — Stops new WireGuard, restores `wg0.conf`, restarts legacy - -Manual rollback if needed: +If something goes wrong — or you want to revert — run: ```bash -# Restore SSL certificates -sudo cp -r ~/.wg-migration/letsencrypt/. /etc/letsencrypt/ - -# Restore Nginx -sudo cp -r ~/.wg-migration/nginx/sites-available/. /etc/nginx/sites-available/ -sudo cp -r ~/.wg-migration/nginx/sites-enabled/. /etc/nginx/sites-enabled/ -sudo systemctl restart nginx - -# Remove operator-docker -rm -rf ~/operator-docker - -# Restart legacy stack -LEGACY_DIR=$(cat ~/.wg-migration/legacy_compose_dir) -cd "$LEGACY_DIR" && docker compose up -d +bash rollback.sh ``` -WireGuard identity and peers are never modified in the legacy stack — they remain intact. +What it does: ---- - -## Backup location - -All backups are stored in `~/.wg-migration/`: - -| File | Content | -| :--- | :------ | -| `private_key` | WireGuard private key (deleted after `02`) | -| `public_key` | WireGuard public key | -| `wg0-legacy.conf` | Full peer list | -| `wg0-legacy.conf.original` | Unmodified copy | -| `legacy.env` | Legacy stack `.env` | -| `legacy_compose_dir` | Path to legacy stack | -| `nginx/` | Nginx sites and config | -| `letsencrypt/` | SSL certificates | -| `ufw-snapshot.txt` | UFW rules | -| `network_info` | IPv4/IPv6 detection result | -| `ssh_pubkey` | SSH public key for repo access | -| `migration.log` | Full log of all steps | - ---- +1. Stops operator-docker completely +2. Removes SSL certificates issued for the new subdomains +3. Restores the Nginx configuration from backup +4. Restores SSL certificates from backup +5. Starts the legacy Go stack +6. Deletes the migration backup ## Notes -- Existing VPN clients reconnect automatically after `02` completes — no config changes needed. -- The private key is destroyed from disk after successful peer migration. -- If port 80 is blocked externally, SSL certificate issuance will fail at step 01. Contact your VPS provider or use DNS challenge manually. +- The private key is destroyed from disk after successful migration. +- If port 80 is blocked, SSL issuance will fail. Contact your VPS provider or use DNS challenge manually. - IPv6 is detected and enabled automatically. --- @@ -199,187 +74,62 @@ All backups are stored in `~/.wg-migration/`: Migración sin tiempo de inactividad desde el stack legacy WireGuard + Go API hacia operator-docker. Los clientes VPN existentes se reconectan automáticamente — no requieren ningún cambio de configuración. ---- - ## Cómo funciona +La identidad WireGuard del servidor (clave privada → clave pública) se preserva. Todos los peers se migran a la nueva API. Los clientes se reconectan sin cambios. + ```text -Antes de la migración - Stack legacy (sp-env-wireguard-ipv6) - ├── wireguard (linuxserver/wireguard) - ├── wireguard-api (Go) - └── nginx-certbot - -Después de la migración - operator-docker - ├── wireguard (misma identidad — misma clave pública) - ├── operator-api (FastAPI) - ├── marzban - ├── hysteria2 - └── mysql +Antes Después + wireguard (linuxserver) wireguard (misma identidad) + wireguard-api (Go) operator-api (FastAPI) + nginx-certbot marzban + hysteria2 + mysql ``` -La identidad WireGuard del servidor (clave privada → clave pública) se preserva. -Todos los peers se migran vía la nueva API. Los clientes se reconectan sin cambios. - ---- - ## Requisitos - Stack legacy corriendo (`docker ps` muestra `wireguard`) - Dominio con DNS apuntando a este servidor -- Un subdominio por servicio (`api`, `legacy`, `cdn`, `news` o similar) -- SSH key autorizada por Simplified Privacy (generada en el paso 00) -- Puerto 80/TCP accesible externamente (requerido por Let's Encrypt) +- SSH key autorizada por Simplified Privacy +- Puerto 80/TCP abierto externamente (requerido por Let's Encrypt) ---- +## Migración -## Pasos de migración - -| Script | Ejecutar como | Descripción | -| :----- | :------------ | :---------- | -| `00-migrate-root.sh` | root | Backup, apaga legacy, limpia sistema | -| `01-migrate-user.sh` | usuario | Clona repo, instala operator-docker | -| `02-migrate-peers.sh` | usuario | Inyecta identidad, migra peers | - ---- - -## Uso - -Los scripts se pueden ejecutar desde cualquier directorio: +Ejecutar como usuario Linux (no root): ```bash -sudo bash ~/migration/00-migrate-root.sh -bash ~/migration/01-migrate-user.sh -bash ~/migration/02-migrate-peers.sh +bash migration.sh ``` ---- +Pasos: -## Paso 00 — Backup y limpieza - -Ejecutar como root: - -```bash -sudo bash 00-migrate-root.sh -``` - -Acciones: - -| Acción | Detalle | -| :----- | :------ | -| Detecta el contenedor WireGuard legacy | Por imagen o nombre | -| Respalda clave privada y pública | Guardadas en `~/.wg-migration/` | -| Respalda todos los peers | Desde `wg showconf wg0` | -| Respalda Nginx y certificados SSL | `/etc/nginx` y `/etc/letsencrypt` completos | -| Respalda reglas UFW | Solo snapshot | -| Genera SSH key | Para acceso al repo | -| Apaga el stack legacy | `docker compose down` | -| Limpia Nginx | Elimina todos los sites para instalación limpia | -| Verifica que el puerto 51820 esté libre | Falla si sigue en uso | - -Al finalizar, el script muestra la clave SSH pública. Envíala a Simplified Privacy antes de ejecutar el `01`. - ---- - -## Paso 01 — Instalar operator-docker - -Una vez que Simplified Privacy autorice la SSH key, ejecutar como usuario Linux: - -```bash -bash 01-migrate-user.sh -``` - -Acciones: - -| Acción | Detalle | -| :----- | :------ | -| Valida que existan los backups | Falla si no se ejecutó el `00` | -| Limpia instalación anterior fallida | Detecta `.env` corrupto, repo incompleto | -| Clona el repo privado | Vía SSH | -| Ejecuta `install.sh` en modo migración | WireGuard NO se inicia todavía | -| Crea placeholder de `wg0.conf` | El `02` inyectará la clave privada real | -| Restaura configs Nginx externos | Por ejemplo, vhosts del servidor de correo | - -> `MIGRATION_MODE=1` indica al instalador que omita el inicio de WireGuard. El script `02` lo inicia después de inyectar la identidad legacy. - ---- - -## Paso 02 — Migrar identidad y peers - -```bash -bash 02-migrate-peers.sh -``` - -Acciones: - -| Acción | Detalle | -| :----- | :------ | -| Detiene WireGuard legacy | Vía `docker compose stop` en el directorio legacy | -| Inyecta la clave privada legacy | En `wg0.conf` y keyfiles del servidor | -| Inicia el nuevo WireGuard | Con la identidad legacy | -| Verifica que la clave pública coincida | Falla y hace rollback si no coincide | -| Verifica que la API responda | `GET /wireguard/peers` debe devolver 200 | -| Migra todos los peers | `POST /wireguard/peers` por cada peer | -| Destruye la clave privada del disco | `shred` o `rm` | - ---- +1. Solicita sudo una vez — sin interrupciones posteriores +2. Verifica acceso SSH y clona operator-docker +3. Crea un backup completo — claves WireGuard, peers, Nginx, certificados SSL +4. Apaga el stack legacy +5. Ejecuta el instalador de operator-docker +6. Inyecta la identidad WireGuard legacy en el nuevo stack +7. Migra todos los peers vía la nueva API +8. Destruye la clave privada del disco ## Rollback -Cada script tiene rollback automático en caso de fallo: - -- `00` — Sin rollback necesario (el legacy sigue corriendo hasta el `02`) -- `01` — Elimina `operator-docker`, restaura Nginx y certs, reinicia legacy -- `02` — Detiene el nuevo WireGuard, restaura `wg0.conf`, reinicia legacy - -Rollback manual si es necesario: +Si algo falla — o si se quiere revertir — ejecutar: ```bash -# Restaurar certificados SSL -sudo cp -r ~/.wg-migration/letsencrypt/. /etc/letsencrypt/ - -# Restaurar Nginx -sudo cp -r ~/.wg-migration/nginx/sites-available/. /etc/nginx/sites-available/ -sudo cp -r ~/.wg-migration/nginx/sites-enabled/. /etc/nginx/sites-enabled/ -sudo systemctl restart nginx - -# Eliminar operator-docker -rm -rf ~/operator-docker - -# Reiniciar stack legacy -LEGACY_DIR=$(cat ~/.wg-migration/legacy_compose_dir) -cd "$LEGACY_DIR" && docker compose up -d +bash rollback.sh ``` -La identidad WireGuard y los peers nunca se modifican en el stack legacy — permanecen intactos. +Pasos: ---- - -## Ubicación del backup - -Todos los backups se almacenan en `~/.wg-migration/`: - -| Archivo | Contenido | -| :------ | :-------- | -| `private_key` | Clave privada WireGuard (eliminada tras el `02`) | -| `public_key` | Clave pública WireGuard | -| `wg0-legacy.conf` | Lista completa de peers | -| `wg0-legacy.conf.original` | Copia sin modificar | -| `legacy.env` | `.env` del stack legacy | -| `legacy_compose_dir` | Ruta al stack legacy | -| `nginx/` | Sites y configuración de Nginx | -| `letsencrypt/` | Certificados SSL | -| `ufw-snapshot.txt` | Reglas UFW | -| `network_info` | Resultado de detección IPv4/IPv6 | -| `ssh_pubkey` | Clave SSH pública para acceso al repo | -| `migration.log` | Log completo de todos los pasos | - ---- +1. Detiene operator-docker completamente +2. Elimina los certificados SSL emitidos para los nuevos subdominios +3. Restaura la configuración de Nginx desde el backup +4. Restaura los certificados SSL desde el backup +5. Inicia el stack Go legacy +6. Elimina el backup de migración ## Notas -- Los clientes VPN existentes se reconectan automáticamente al finalizar el `02` — no requieren cambios de configuración. -- La clave privada se destruye del disco tras la migración exitosa de peers. -- Si el puerto 80 está bloqueado externamente, la emisión de certificados SSL fallará en el paso 01. Contacta a tu proveedor VPS o usa DNS challenge manualmente. -- IPv6 se detecta y habilita automáticamente. \ No newline at end of file +- La clave privada se destruye del disco tras la migración exitosa. +- Si el puerto 80 está bloqueado, la emisión de certificados fallará. Contacta a tu proveedor VPS o usa DNS challenge manualmente. +- IPv6 se detecta y habilita automáticamente.