feat: unified migration CLI, remove legacy scripts, update readme

This commit is contained in:
Zenaku 2026-09-30 22:46:37 +02:00
parent 5f07b24a6f
commit 1f686ba9fe
4 changed files with 64 additions and 1096 deletions

View file

@ -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 "========================================="

View file

@ -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 ""

View file

@ -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 "========================================="

376
readme.md
View file

@ -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.
- 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.