feat: unified migration CLI, remove legacy scripts, update readme
This commit is contained in:
parent
5f07b24a6f
commit
1f686ba9fe
4 changed files with 64 additions and 1096 deletions
|
|
@ -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 "========================================="
|
||||
|
|
@ -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 ""
|
||||
|
|
@ -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 "========================================="
|
||||
378
readme.md
378
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.
|
||||
- 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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue