stable install, tested test-migrate.root user fix

This commit is contained in:
zenaku 2026-10-04 19:30:53 +02:00
parent ee41570c8f
commit 8f8d98c713
3 changed files with 415 additions and 0 deletions

View file

@ -0,0 +1,120 @@
#!/bin/bash -eu
# =============================================================================
# migration.sh — WireGuard Go → FastAPI migration
# Run as Linux user (not root). Asks for sudo once.
# =============================================================================
if [ "$EUID" -eq 0 ]; then
echo "[X] Run as your Linux user, not root: bash migration.sh"
exit 1
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
sudo chown -R "$USER:$USER" "$SCRIPT_DIR" 2>/dev/null || true
OPERATOR_DIR="$HOME/operator-docker"
BACKUP_DIR="$HOME/.wg-migration"
LOG="$BACKUP_DIR/migration.log"
REPO_URL="git@git.simplifiedprivacy.com:zenaku/operator-docker.git"
TOTAL_STEPS=4
# ─── Borrow UI from operator-docker if available, else minimal fallback ───────
if [ -f "$OPERATOR_DIR/lib/ui.sh" ]; then
source "$OPERATOR_DIR/lib/ui.sh"
source "$OPERATOR_DIR/lib/log.sh"
source "$OPERATOR_DIR/lib/steps.sh"
else
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; RESET='\033[0m'
ok() { echo -e "${GREEN}[OK]${RESET} $1"; [ -f "$LOG" ] && echo "[OK] $1" >> "$LOG"; }
info() { echo -e "${CYAN}[i]${RESET} $1"; [ -f "$LOG" ] && echo "[i] $1" >> "$LOG"; }
warn() { echo -e "${YELLOW}[!]${RESET} $1"; [ -f "$LOG" ] && echo "[!] $1" >> "$LOG"; }
print_header() { echo ""; echo " $1 $2"; echo ""; }
print_section() { echo -e "\n${CYAN}─── $1 ───${RESET}"; }
log_ok() { ok "$1"; }
log_info() { info "$1"; }
log_warn() { warn "$1"; }
log_error() { echo -e "${RED}[X]${RESET} $1"; [ -f "$LOG" ] && echo "[X] $1" >> "$LOG"; }
fi
fail() { log_error "$1"; exit 1; }
mkdir -p "$BACKUP_DIR"
: > "$LOG" 2>/dev/null || true
print_header "WireGuard Migration" "v1.0"
# ─── SUDO once ────────────────────────────────────────────────────────────────
info "Requesting sudo credentials (used throughout migration)..."
sudo -v || fail "sudo required. Check your permissions."
ok "Sudo credentials cached."
# ─── STEP 1: Backup (go-legacy stays running) ────────────────────────────────
print_section "Step 1/$TOTAL_STEPS — Backup WireGuard identity & peers"
[ -f "$BACKUP_DIR/private_key" ] && ok "Backup already exists — skipping step 1." || {
info "Running backup (sudo required)..."
sudo bash "$SCRIPT_DIR/steps/00-backup.sh" || fail "Backup failed."
}
# ─── STEP 2: SSH + Clone operator-docker ─────────────────────────────────────
print_section "Step 2/$TOTAL_STEPS — Verify SSH & Clone operator-docker"
[ -f "$HOME/.ssh/id_ed25519" ] || fail "SSH key not found. Run step 1 first (00-backup.sh generates it)."
SSH_TEST=$(GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10' \
ssh -T git@git.simplifiedprivacy.com 2>&1 || true)
if echo "$SSH_TEST" | grep -qiE "Welcome|successfully authenticated"; then
ok "SSH access authorized."
else
warn "SSH not authorized. Share this key with Simplified Privacy:"
echo ""
cat "$HOME/.ssh/id_ed25519.pub"
echo ""
fail "Once authorized, run again: bash migration.sh"
fi
if [ -d "$OPERATOR_DIR" ]; then
warn "operator-docker already exists — skipping clone."
else
info "Cloning operator-docker..."
GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no' \
git clone "$REPO_URL" "$OPERATOR_DIR" || fail "Could not clone repository."
ok "Cloned to $OPERATOR_DIR"
fi
# Re-source UI from freshly cloned repo (better version)
if [ -f "$OPERATOR_DIR/lib/ui.sh" ]; then
source "$OPERATOR_DIR/lib/ui.sh"
source "$OPERATOR_DIR/lib/log.sh"
source "$OPERATOR_DIR/lib/steps.sh"
fi
fail() { log_error "$1"; exit 1; }
# ─── STEP 3: Install operator-docker (preflight runs inside as step 3) ───────
print_section "Step 3/$TOTAL_STEPS — Install operator-docker"
[ -f "$OPERATOR_DIR/.env" ] && ok ".env exists — skipping install." || {
info "Running installer..."
cd "$OPERATOR_DIR"
sudo systemctl start nginx 2>/dev/null || true
echo "$HOME" > /tmp/.migration_home
sudo MIGRATION_MODE=1 bash install.sh || { rm -f /tmp/.migration_home; fail "Installer failed."; }
rm -f /tmp/.migration_home
ok "operator-docker installed."
}
# ─── STEP 4: Migrate peers & identity (shuts down go-legacy here) ────────────
sudo chown -R "$USER:$USER" "$OPERATOR_DIR"
print_section "Step 4/$TOTAL_STEPS — Migrate WireGuard identity & peers"
# Use sg to activate docker group without requiring re-login.
if sg docker -c "docker info &>/dev/null" 2>/dev/null; then
sg docker -c "bash '$SCRIPT_DIR/steps/02-peers.sh'" || fail "Peer migration failed."
else
bash "$SCRIPT_DIR/steps/02-peers.sh" || fail "Peer migration failed."
fi
echo ""
echo "========================================="
echo " Migration complete"
echo " Log: cat $LOG"
echo "========================================="

View file

@ -71,6 +71,9 @@ if [ -z "$LEGACY_WG" ]; then
fi
[ -z "$LEGACY_WG" ] && fail "No legacy WireGuard container found. Check: docker ps"
ok "Legacy WireGuard: $LEGACY_WG"
docker inspect --format '{{.Id}}' "$LEGACY_WG" > "$BACKUP_DIR/legacy_wg_id"
chown "$LINUX_USER":"$LINUX_USER" "$BACKUP_DIR/legacy_wg_id"
ok "Legacy WireGuard ID saved."
# ─── DETECT LEGACY STACK ──────────────────────────────────────────────────────
sep

View file

@ -0,0 +1,292 @@
#!/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
if [ -f "$BACKUP_DIR/legacy_wg_id" ]; then
LEGACY_WG_ID=$(cat "$BACKUP_DIR/legacy_wg_id" | tr -d '[:space:]')
sudo modprobe ip6table_filter 2>/dev/null || true
docker start "$LEGACY_WG_ID" 2>/dev/null || true
sleep 5
LEGACY_UP=$(docker ps --filter "id=$LEGACY_WG_ID" --format '{{.Names}}' | head -1 || true)
[ -n "$LEGACY_UP" ] && ok "Legacy restored: $LEGACY_UP" || \
warn "Legacy failed to start. Run manually: docker start $LEGACY_WG_ID"
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
fail "Docker socket not accessible. Check that your user is in the docker group."
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_wg_id" ] || fail "No legacy WireGuard ID found."
ok "Backups found."
[ -d "$OPERATOR_DIR" ] || fail "operator-docker not found. Run 01 first."
ok "operator-docker found."
# ─── GENERATE wg0.conf FROM LEGACY KEY ───────────────────────────────────────
if [ ! -f "$WG_CONF" ]; then
info "Generating wg0.conf from legacy key..."
mkdir -p "$(dirname "$WG_CONF")"
LEGACY_PRIVATE_KEY=$(cat "$BACKUP_DIR/private_key" | tr -d '[:space:]')
cat > "$WG_CONF" << WGCONF
[Interface]
Address = 10.13.0.1/16
ListenPort = 51820
PrivateKey = ${LEGACY_PRIVATE_KEY}
PostUp = /usr/bin/post-up %i %i
PostDown = /usr/bin/post-down %i %i
WGCONF
ok "wg0.conf generated."
fi
for CONTAINER in operator-api mysql; do
STATUS=$(docker inspect -f '{{.State.Running}}' "$CONTAINER" 2>/dev/null || echo "false")
[ "$STATUS" = "true" ] || fail "Container '$CONTAINER' is not running."
ok "Running: $CONTAINER"
done
# ─── LOAD CREDENTIALS ─────────────────────────────────────────────────────────
sep
info "Loading credentials..."
set -a; source "$OPERATOR_DIR/.env"; set +a
[ -z "${API_TOKEN:-}" ] && fail "API_TOKEN not found in .env"
[ -z "${BASE_DOMAIN:-}" ] && fail "BASE_DOMAIN not found in .env"
[ -z "${API_SUBDOMAIN:-}" ] && fail "API_SUBDOMAIN not found in .env"
echo "$BASE_DOMAIN" | grep -qE '[/;|&<> ]|^[0-9]+-' && \
fail "Invalid BASE_DOMAIN in .env: '$BASE_DOMAIN'"
API_URL="https://${API_SUBDOMAIN}.${BASE_DOMAIN}"
ok "API URL: $API_URL"
# ─── STOP LEGACY WIREGUARD ────────────────────────────────────────────────────
sep
info "Stopping legacy WireGuard..."
LEGACY_WG_ID=$(cat "$BACKUP_DIR/legacy_wg_id" | tr -d '[:space:]')
docker stop "$LEGACY_WG_ID" 2>/dev/null || true
docker rm "$LEGACY_WG_ID" 2>/dev/null || true
sleep 3
STILL_UP=$(docker ps -q --filter "id=$LEGACY_WG_ID" || true)
[ -n "$STILL_UP" ] && fail "Legacy WireGuard is still running."
ok "Legacy WireGuard stopped and removed."
# ─── BACKUP NEW CONF ──────────────────────────────────────────────────────────
sep
info "Backing up new wg0.conf..."
WG_CONF_BAK="$WG_CONF.bak.$(date +%s)"
cp "$WG_CONF" "$WG_CONF_BAK"
ok "Backup: $WG_CONF_BAK"
# ─── INJECT PRIVATE KEY ───────────────────────────────────────────────────────
sep
info "Injecting legacy private key..."
LEGACY_PRIVATE_KEY=$(cat "$BACKUP_DIR/private_key" | tr -d '[:space:]')
LEGACY_PUBLIC_KEY=$(cat "$BACKUP_DIR/public_key" | tr -d '[:space:]')
[ -z "$LEGACY_PRIVATE_KEY" ] && fail "Private key is empty."
[ -z "$LEGACY_PUBLIC_KEY" ] && fail "Public key is empty."
sed -i "s|^PrivateKey = .*|PrivateKey = $LEGACY_PRIVATE_KEY|" "$WG_CONF"
ok "Private key written to wg0.conf."
WG_SERVER_KEY="$OPERATOR_DIR/services/wireguard/config/server/privatekey-server"
WG_SERVER_PUB="$OPERATOR_DIR/services/wireguard/config/server/publickey-server"
mkdir -p "$(dirname "$WG_SERVER_KEY")"
echo "$LEGACY_PRIVATE_KEY" > "$WG_SERVER_KEY"
echo "$LEGACY_PUBLIC_KEY" > "$WG_SERVER_PUB"
ok "Private key written to server keyfile."
# ─── START NEW WIREGUARD ──────────────────────────────────────────────────────
sep
info "Starting new WireGuard with legacy identity..."
echo "[WIREGUARD-START] $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG"
cd "$OPERATOR_DIR"
sudo modprobe ip6table_filter 2>/dev/null || true
docker compose up -d --force-recreate wireguard
info "Waiting for startup..."
sleep 8
STATUS=$(docker inspect -f '{{.State.Running}}' wireguard 2>/dev/null || echo "false")
[ "$STATUS" = "true" ] || fail "New WireGuard failed to start. Check: docker logs wireguard"
ok "New WireGuard running."
# ─── VERIFY PUBLIC KEY ────────────────────────────────────────────────────────
sep
info "Verifying server identity..."
NEW_PUBLIC_KEY=$(docker exec wireguard cat /config/server/publickey-server 2>/dev/null | \
tr -d '[:space:]' || true)
if [ -z "$NEW_PUBLIC_KEY" ]; then
NEW_PUBLIC_KEY=$(docker exec wireguard wg show wg0 2>/dev/null | \
grep "public key" | awk '{print $NF}' | tr -d '[:space:]' || true)
fi
[ -z "$NEW_PUBLIC_KEY" ] && fail "Could not retrieve public key from new WireGuard."
if [ "$NEW_PUBLIC_KEY" != "$LEGACY_PUBLIC_KEY" ]; then
warn "Public key MISMATCH!"
warn " Expected : $LEGACY_PUBLIC_KEY"
warn " Got : $NEW_PUBLIC_KEY"
fail "Server identity was not preserved."
fi
ok "Public key verified: $NEW_PUBLIC_KEY"
ok "Existing users will reconnect automatically."
# ─── VERIFY API ───────────────────────────────────────────────────────────────
sep
info "Verifying API..."
DEADLINE=$(( $(date +%s) + 180 ))
i=0
while [ $(date +%s) -lt $DEADLINE ]; do
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \
-H "Authorization: Bearer $API_TOKEN" \
"$API_URL/wireguard/peers" 2>/dev/null || echo "000")
[ "$HTTP_CODE" = "200" ] && break
i=$(( i + 1 ))
warn "API not ready (HTTP $HTTP_CODE) — waited ${i}x5s..."
sleep 5
done
[ "$HTTP_CODE" = "200" ] || fail "API not responding after retries (HTTP $HTTP_CODE)."
ok "API responding."
# ─── MIGRATE PEERS ────────────────────────────────────────────────────────────
sep
info "Migrating peers to new API..."
LEGACY_CONF=$(cat "$BACKUP_DIR/wg0-legacy.conf")
MIGRATED=0
FAILED=0
CURRENT_PUBKEY=""
CURRENT_IPS=""
migrate_peer() {
local PUBKEY="$1"
local IPS="$2"
IPS_JSON=$(echo "$IPS" | tr ',' '\n' | \
sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//' | \
awk 'NF {print "\"" $0 "\""}' | paste -sd ',' | \
sed 's/^/[/' | sed 's/$/]/')
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 15 \
-X POST "$API_URL/wireguard/peers" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"public_key\": \"$PUBKEY\", \"allowed_ips\": $IPS_JSON}" 2>/dev/null || echo "000")
if [ "$HTTP_CODE" = "200" ]; then
ok "Peer migrated: ${PUBKEY:0:20}... → $IPS"
MIGRATED=$((MIGRATED + 1))
else
warn "Peer failed: ${PUBKEY:0:20}... (HTTP $HTTP_CODE)"
FAILED=$((FAILED + 1))
fi
}
while IFS= read -r line; do
if [[ "$line" == "[Peer]" ]]; then
if [ -n "$CURRENT_PUBKEY" ] && [ -n "$CURRENT_IPS" ]; then
migrate_peer "$CURRENT_PUBKEY" "$CURRENT_IPS"
fi
CURRENT_PUBKEY=""
CURRENT_IPS=""
elif [[ "$line" =~ ^PublicKey\ =\ (.+)$ ]]; then
CURRENT_PUBKEY="${BASH_REMATCH[1]}"
elif [[ "$line" =~ ^AllowedIPs\ =\ (.+)$ ]]; then
CURRENT_IPS="${BASH_REMATCH[1]}"
fi
done <<< "$LEGACY_CONF"
[ -n "$CURRENT_PUBKEY" ] && [ -n "$CURRENT_IPS" ] && migrate_peer "$CURRENT_PUBKEY" "$CURRENT_IPS"
# ─── VERIFY PEERS ─────────────────────────────────────────────────────────────
sep
info "Verifying peers in WireGuard..."
REGISTERED=$(docker exec wireguard wg show wg0 | grep -c "^peer:" 2>/dev/null || echo 0)
ok "$REGISTERED peers active in wg0."
# ─── DESTROY PRIVATE KEY ──────────────────────────────────────────────────────
sep
info "Destroying private key..."
command -v shred &>/dev/null && \
shred -u "$BACKUP_DIR/private_key" 2>/dev/null || \
rm -f "$BACKUP_DIR/private_key"
ok "Private key deleted from disk."
trap - ERR
echo "[END-02] $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG"
echo ""
echo "========================================="
echo " Migration complete"
echo "========================================="
echo ""
echo " Public key : $NEW_PUBLIC_KEY"
echo " API : $API_URL"
echo " Peers migrated : $MIGRATED"
if [ "$FAILED" -gt 0 ]; then
echo -e " ${RED}Peers failed : $FAILED${RESET}"
echo " Check: cat $LOG"
else
echo " Errors : none"
fi
echo ""
echo " Existing users reconnect automatically"
echo " without new configuration."
echo ""
echo " Log: cat $LOG"
echo "========================================="