154 lines
6.4 KiB
Bash
Executable File
154 lines
6.4 KiB
Bash
Executable File
#!/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"
|