235 lines
9.4 KiB
Bash
235 lines
9.4 KiB
Bash
#!/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 ""
|