mirror of
https://github.com/mruwnik/memory.git
synced 2026-01-02 09:12:58 +01:00
tools/restore_databases.sh: Script to restore PostgreSQL and Qdrant backups from encrypted backup files. tools/restore_files.py: Python script to restore Fernet-encrypted file backups. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
209 lines
6.8 KiB
Bash
Executable File
209 lines
6.8 KiB
Bash
Executable File
#!/bin/bash
|
|
# Restore Postgres and Qdrant databases from S3 backups
|
|
# Usage: ./restore_databases.sh [DATE]
|
|
# Example: ./restore_databases.sh 20251219
|
|
|
|
set -euo pipefail
|
|
|
|
# Configuration - read from environment or use defaults
|
|
BUCKET="${S3_BACKUP_BUCKET:-equistamp-memory-backup}"
|
|
PREFIX="${S3_BACKUP_PREFIX:-Daniel}/databases"
|
|
REGION="${S3_BACKUP_REGION:-eu-central-1}"
|
|
PASSWORD="${BACKUP_ENCRYPTION_KEY:?BACKUP_ENCRYPTION_KEY not set}"
|
|
|
|
# Target services - adjust for your environment
|
|
POSTGRES_HOST="${POSTGRES_HOST:-localhost}"
|
|
POSTGRES_PORT="${POSTGRES_PORT:-5432}"
|
|
POSTGRES_USER="${POSTGRES_USER:-kb}"
|
|
POSTGRES_DB="${POSTGRES_DB:-kb}"
|
|
QDRANT_URL="${QDRANT_URL:-http://localhost:6333}"
|
|
|
|
# Date to restore (default: list available backups)
|
|
DATE="${1:-}"
|
|
|
|
# Temp directory for downloads
|
|
TEMP_DIR=$(mktemp -d)
|
|
trap "rm -rf ${TEMP_DIR}" EXIT
|
|
|
|
log() {
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
|
|
}
|
|
|
|
error() {
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2
|
|
}
|
|
|
|
# List available backups
|
|
list_backups() {
|
|
log "Available PostgreSQL backups:"
|
|
aws s3 ls "s3://${BUCKET}/${PREFIX}/" --region "${REGION}" | grep "postgres-" | awk '{print " " $4}' | sort -r | head -10
|
|
|
|
echo ""
|
|
log "Available Qdrant backups:"
|
|
aws s3 ls "s3://${BUCKET}/${PREFIX}/" --region "${REGION}" | grep "qdrant-" | awk '{print " " $4}' | sort -r | head -10
|
|
}
|
|
|
|
# Restore PostgreSQL
|
|
restore_postgres() {
|
|
local date=$1
|
|
local s3_path="s3://${BUCKET}/${PREFIX}/postgres-${date}.sql.gz.enc"
|
|
local sql_file="${TEMP_DIR}/postgres_restore.sql"
|
|
|
|
log "Checking if Postgres backup exists: ${s3_path}"
|
|
if ! aws s3 ls "${s3_path}" --region "${REGION}" >/dev/null 2>&1; then
|
|
error "Postgres backup not found: ${s3_path}"
|
|
return 1
|
|
fi
|
|
|
|
log "Downloading and decrypting Postgres backup..."
|
|
if ! aws s3 cp "${s3_path}" - --region "${REGION}" | \
|
|
openssl enc -d -aes-256-cbc -pbkdf2 -pass "pass:${PASSWORD}" | \
|
|
gunzip > "${sql_file}"; then
|
|
error "Failed to download/decrypt Postgres backup"
|
|
return 1
|
|
fi
|
|
|
|
log "Postgres backup decrypted ($(du -h "${sql_file}" | cut -f1))"
|
|
|
|
# Check if we can connect to postgres
|
|
log "Testing PostgreSQL connection..."
|
|
if ! PGPASSWORD="${PGPASSWORD:-}" psql -h "${POSTGRES_HOST}" -p "${POSTGRES_PORT}" -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -c "SELECT 1" >/dev/null 2>&1; then
|
|
error "Cannot connect to PostgreSQL at ${POSTGRES_HOST}:${POSTGRES_PORT}"
|
|
error "Set PGPASSWORD environment variable or check connection settings"
|
|
log "SQL dump saved to: ${sql_file}"
|
|
log "You can restore manually with: psql -h ${POSTGRES_HOST} -U ${POSTGRES_USER} -d ${POSTGRES_DB} < ${sql_file}"
|
|
return 1
|
|
fi
|
|
|
|
log "Restoring to PostgreSQL..."
|
|
if PGPASSWORD="${PGPASSWORD:-}" psql -h "${POSTGRES_HOST}" -p "${POSTGRES_PORT}" -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" < "${sql_file}"; then
|
|
log "PostgreSQL restore completed successfully"
|
|
return 0
|
|
else
|
|
error "PostgreSQL restore failed (some errors may be expected for existing objects)"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Restore Qdrant
|
|
restore_qdrant() {
|
|
local date=$1
|
|
local s3_path="s3://${BUCKET}/${PREFIX}/qdrant-${date}.snapshot.enc"
|
|
local snapshot_file="${TEMP_DIR}/qdrant_restore.snapshot"
|
|
|
|
log "Checking if Qdrant backup exists: ${s3_path}"
|
|
if ! aws s3 ls "${s3_path}" --region "${REGION}" >/dev/null 2>&1; then
|
|
error "Qdrant backup not found: ${s3_path}"
|
|
return 1
|
|
fi
|
|
|
|
log "Downloading and decrypting Qdrant backup..."
|
|
if ! aws s3 cp "${s3_path}" - --region "${REGION}" | \
|
|
openssl enc -d -aes-256-cbc -pbkdf2 -pass "pass:${PASSWORD}" \
|
|
> "${snapshot_file}"; then
|
|
error "Failed to download/decrypt Qdrant backup"
|
|
return 1
|
|
fi
|
|
|
|
log "Qdrant backup decrypted ($(du -h "${snapshot_file}" | cut -f1))"
|
|
|
|
# Check if Qdrant is reachable
|
|
log "Testing Qdrant connection..."
|
|
if ! curl -sf "${QDRANT_URL}/readyz" >/dev/null 2>&1; then
|
|
error "Cannot connect to Qdrant at ${QDRANT_URL}"
|
|
log "Snapshot saved to: ${snapshot_file}"
|
|
log "You can restore manually by uploading to Qdrant"
|
|
return 1
|
|
fi
|
|
|
|
log "Uploading snapshot to Qdrant..."
|
|
local upload_response
|
|
if ! upload_response=$(curl -sf -X POST "${QDRANT_URL}/snapshots/upload?wait=true" \
|
|
-H "Content-Type: multipart/form-data" \
|
|
-F "snapshot=@${snapshot_file}" 2>&1); then
|
|
error "Failed to upload snapshot to Qdrant: ${upload_response}"
|
|
return 1
|
|
fi
|
|
|
|
log "Snapshot uploaded, recovering..."
|
|
|
|
# Extract the snapshot filename from the response
|
|
local snapshot_name
|
|
if command -v jq >/dev/null 2>&1; then
|
|
snapshot_name=$(echo "${upload_response}" | jq -r '.result.name // empty')
|
|
else
|
|
snapshot_name=$(echo "${upload_response}" | grep -o '"name":"[^"]*"' | cut -d'"' -f4)
|
|
fi
|
|
|
|
if [ -z "${snapshot_name}" ]; then
|
|
log "Upload response: ${upload_response}"
|
|
log "Snapshot uploaded but could not extract name. Check Qdrant manually."
|
|
return 0
|
|
fi
|
|
|
|
log "Recovering from snapshot: ${snapshot_name}"
|
|
if curl -sf -X PUT "${QDRANT_URL}/snapshots/recover" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"location\": \"file:///qdrant/snapshots/${snapshot_name}\"}" >/dev/null; then
|
|
log "Qdrant restore completed successfully"
|
|
return 0
|
|
else
|
|
error "Qdrant recovery failed"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Main
|
|
main() {
|
|
if [ -z "${DATE}" ]; then
|
|
log "No date specified. Listing available backups..."
|
|
echo ""
|
|
list_backups
|
|
echo ""
|
|
log "Usage: $0 <DATE>"
|
|
log "Example: $0 20251219"
|
|
exit 0
|
|
fi
|
|
|
|
log "Starting database restore for date: ${DATE}"
|
|
echo ""
|
|
|
|
local postgres_result=0
|
|
local qdrant_result=0
|
|
|
|
# Restore Postgres
|
|
echo "=========================================="
|
|
echo " PostgreSQL Restore"
|
|
echo "=========================================="
|
|
if ! restore_postgres "${DATE}"; then
|
|
postgres_result=1
|
|
fi
|
|
echo ""
|
|
|
|
# Restore Qdrant
|
|
echo "=========================================="
|
|
echo " Qdrant Restore"
|
|
echo "=========================================="
|
|
if ! restore_qdrant "${DATE}"; then
|
|
qdrant_result=1
|
|
fi
|
|
echo ""
|
|
|
|
# Summary
|
|
echo "=========================================="
|
|
echo " Summary"
|
|
echo "=========================================="
|
|
if [ $postgres_result -eq 0 ] && [ $qdrant_result -eq 0 ]; then
|
|
log "All database restores completed successfully"
|
|
exit 0
|
|
elif [ $postgres_result -ne 0 ] && [ $qdrant_result -ne 0 ]; then
|
|
error "All database restores failed"
|
|
exit 1
|
|
else
|
|
error "Some restores failed (Postgres: ${postgres_result}, Qdrant: ${qdrant_result})"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
main
|