main
This commit is contained in:
Executable
+153
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# ZBrain Restore
|
||||
# ===============
|
||||
# Восстанавливает ZBrain из бэкапа созданного backup.sh.
|
||||
#
|
||||
# Использование:
|
||||
# sudo bash scripts/restore.sh <backup-timestamp>
|
||||
#
|
||||
# Где <backup-timestamp> - имя папки в 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 <backup-timestamp>"
|
||||
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 <<EOF
|
||||
|
||||
╔════════════════════════════════════════════════════════════╗
|
||||
║ ВНИМАНИЕ ║
|
||||
║ ║
|
||||
║ Восстановление перезапишет все существующие БД ZBrain! ║
|
||||
║ ║
|
||||
║ Бэкап: $TIMESTAMP ║
|
||||
║ Файлы: ║
|
||||
$(ls "$BACKUP_PATH" | sed 's/^/║ /' | awk '{printf "%-60s║\n", $0}')
|
||||
║ ║
|
||||
╚════════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
read -p "Введите 'YES' для продолжения: " CONFIRM
|
||||
[[ "$CONFIRM" == "YES" ]] || { log "Отменено пользователем"; exit 0; }
|
||||
|
||||
log "=== Начинаю restore из $TIMESTAMP ==="
|
||||
|
||||
# ============================================================
|
||||
# Восстановление глобальных объектов
|
||||
# ============================================================
|
||||
log "Восстанавливаю глобальные Postgres объекты..."
|
||||
age -d -i "$AGE_KEY_PATH" -o /tmp/_globals.sql "$BACKUP_PATH/_globals.sql.age"
|
||||
sudo -u postgres psql -f /tmp/_globals.sql 2>&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"
|
||||
Reference in New Issue
Block a user