diff --git a/01-migrate-user.sh b/01-migrate-user.sh new file mode 100644 index 0000000..e9bdee3 --- /dev/null +++ b/01-migrate-user.sh @@ -0,0 +1,235 @@ +#!/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" + +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..." + +[ -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 +sudo MIGRATION_MODE=1 bash install.sh || fail "Installer failed. Check output above." + +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 ""