#!/usr/bin/env bash # # ZBrain Restore # =============== # Восстанавливает ZBrain из бэкапа созданного backup.sh. # # Использование: # sudo bash scripts/restore.sh # # Где - имя папки в BACKUP_DIR (например, 20260520-031500) # # ВАЖНО: для расшифровки нужен приватный age ключ. # Положи его на VM перед запуском restore (например, /tmp/backup.age). # Скрипт спросит путь к ключу. set -euo pipefail # ============================================================ # Конфигурация # ============================================================ BACKUP_DIR="${BACKUP_DIR:-/var/backups/zbrain}" LOG_FILE="/var/log/zbrain/restore.log" log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE" } err() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" | tee -a "$LOG_FILE" >&2 } # ============================================================ # Аргументы # ============================================================ if [[ $# -lt 1 ]]; then echo "Usage: $0 " echo "" echo "Available backups:" ls -1 "$BACKUP_DIR" 2>/dev/null | grep -E '^[0-9]{8}-[0-9]{6}$' | sort -r | head -10 exit 1 fi TIMESTAMP="$1" BACKUP_PATH="$BACKUP_DIR/$TIMESTAMP" [[ -d "$BACKUP_PATH" ]] || { err "Бэкап не найден: $BACKUP_PATH"; exit 1; } [[ $EUID -eq 0 ]] || { err "Требуется sudo"; exit 1; } # ============================================================ # Age ключ для расшифровки # ============================================================ read -p "Путь к age приватному ключу: " AGE_KEY_PATH [[ -f "$AGE_KEY_PATH" ]] || { err "Файл не найден: $AGE_KEY_PATH"; exit 1; } # Тест ключа if ! age -d -i "$AGE_KEY_PATH" "$BACKUP_PATH/_globals.sql.age" > /dev/null 2>&1; then err "Не могу расшифровать с этим ключом" exit 1 fi # ============================================================ # Подтверждение # ============================================================ cat <&1 | tee -a "$LOG_FILE" | grep -v "already exists" || true rm /tmp/_globals.sql log "✓ Globals" # ============================================================ # Восстановление БД # ============================================================ for encrypted_dump in "$BACKUP_PATH"/*.sql.dump.age; do [[ -f "$encrypted_dump" ]] || continue db_name=$(basename "$encrypted_dump" .sql.dump.age) [[ "$db_name" == "_globals" ]] && continue log "Восстанавливаю $db_name..." # Дропаем существующую БД (если есть) sudo -u postgres dropdb --if-exists "$db_name" # Создаём пустую sudo -u postgres createdb "$db_name" # Расшифровываем dump dump_file="/tmp/${db_name}.dump" age -d -i "$AGE_KEY_PATH" -o "$dump_file" "$encrypted_dump" # Восстанавливаем sudo -u postgres pg_restore --dbname="$db_name" --no-owner --role=postgres "$dump_file" 2>&1 | tee -a "$LOG_FILE" || true rm "$dump_file" log "✓ $db_name" done # ============================================================ # Конфиги (опционально) # ============================================================ read -p "Восстановить конфиги? (включая /etc/zbrain) [y/N]: " RESTORE_CONFIG if [[ "$RESTORE_CONFIG" =~ ^[Yy]$ ]]; then log "Восстанавливаю конфиги..." age -d -i "$AGE_KEY_PATH" -o /tmp/config.tar.gz "$BACKUP_PATH/config.tar.gz.age" tar xzf /tmp/config.tar.gz -C / rm /tmp/config.tar.gz log "✓ Конфиги" if [[ -f "$BACKUP_PATH/brains-meta.tar.gz.age" ]]; then log "Восстанавливаю метаданные брейнов..." age -d -i "$AGE_KEY_PATH" -o /tmp/brains-meta.tar.gz "$BACKUP_PATH/brains-meta.tar.gz.age" tar xzf /tmp/brains-meta.tar.gz -C /var/lib/zbrain/ rm /tmp/brains-meta.tar.gz log "✓ Brain configs" fi fi # ============================================================ # Финал # ============================================================ log "=== Restore завершён ===" log "" log "Следующие шаги:" log " 1. Перезапусти системные сервисы:" log " sudo systemctl restart 'zbrain-gbrain-*.service'" log " 2. Перезапусти brainhub:" log " cd /opt/zbrain/deploy/docker && docker compose restart" log " 3. Проверь health endpoint каждого сервиса" log "" log "ВАЖНО: удали age приватный ключ с этой VM после restore!" log " shred -u $AGE_KEY_PATH"