#!/bin/bash # # Payload CMS - PostgreSQL Restore-Skript # Stellt die Datenbank aus einem Backup wieder her. # # Verwendung: # ./restore-db.sh # Interaktiv (zeigt verfügbare Backups) # ./restore-db.sh # Direktes Restore # ./restore-db.sh --from-s3 # Restore aus S3 (interaktiv) # ./restore-db.sh --from-s3 # Restore aus S3 (direkt) # ./restore-db.sh --list # Backups auflisten # ./restore-db.sh --list-s3 # S3-Backups auflisten # # Voraussetzungen: # - ~/.pgpass mit korrekten Berechtigungen (chmod 600) # - ~/.s3cfg für S3-Restore (chmod 600) # set -euo pipefail # ============================================================================ # Konfiguration # ============================================================================ # Datenbank-Verbindung (Passwort via ~/.pgpass) DB_HOST="${PGHOST:-10.10.181.101}" DB_PORT="${PGPORT:-5432}" DB_NAME="${PGDATABASE:-payload_db}" DB_USER="${PGUSER:-payload}" # Backup-Verzeichnis BACKUP_DIR="/home/payload/backups/postgres" # S3-Konfiguration S3_BUCKET="${S3_BUCKET:-c2s}" S3_PATH="${S3_PATH:-backups/postgres}" # Temporäres Verzeichnis für S3-Downloads TEMP_DIR="/tmp/payload-restore" # ============================================================================ # Farben und Formatierung # ============================================================================ RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # ============================================================================ # Funktionen # ============================================================================ print_header() { echo "" echo -e "${BLUE}==============================================${NC}" echo -e "${BLUE} Payload CMS - Datenbank Restore${NC}" echo -e "${BLUE}==============================================${NC}" echo "" } print_step() { echo -e "${BLUE}[$(date '+%H:%M:%S')]${NC} $1" } print_success() { echo -e "${GREEN}[✓]${NC} $1" } print_warning() { echo -e "${YELLOW}[!]${NC} $1" } print_error() { echo -e "${RED}[✗]${NC} ERROR: $1" >&2 } check_pgpass() { local pgpass_file="$HOME/.pgpass" if [[ ! -f "$pgpass_file" ]]; then print_error ".pgpass nicht gefunden: $pgpass_file" exit 1 fi local perms perms=$(stat -c%a "$pgpass_file" 2>/dev/null || echo "000") if [[ "$perms" != "600" ]]; then print_error ".pgpass hat unsichere Berechtigungen: $perms (erwartet: 600)" exit 1 fi } check_s3cfg() { local s3cfg_file="$HOME/.s3cfg" if [[ ! -f "$s3cfg_file" ]]; then print_error ".s3cfg nicht gefunden - S3-Restore nicht möglich" return 1 fi if ! command -v s3cmd &> /dev/null; then print_error "s3cmd nicht installiert" return 1 fi return 0 } test_db_connection() { print_step "Teste Datenbankverbindung..." if ! psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT 1" > /dev/null 2>&1; then print_error "Datenbankverbindung fehlgeschlagen!" print_error "Prüfe ~/.pgpass und Netzwerkverbindung zu $DB_HOST:$DB_PORT" exit 1 fi print_success "Datenbankverbindung OK" } list_local_backups() { echo "" echo -e "${BLUE}Lokale Backups in ${BACKUP_DIR}:${NC}" echo "" if [[ ! -d "$BACKUP_DIR" ]]; then print_warning "Backup-Verzeichnis existiert nicht" return 1 fi local count=0 while IFS= read -r file; do if [[ -n "$file" ]]; then local size size=$(du -h "$file" | cut -f1) local date date=$(stat -c%y "$file" | cut -d'.' -f1) local basename basename=$(basename "$file") count=$((count + 1)) printf " %2d) %-45s %6s %s\n" "$count" "$basename" "$size" "$date" fi done < <(find "$BACKUP_DIR" -name "payload_db_*.sql.gz" -type f | sort -r) if [[ $count -eq 0 ]]; then print_warning "Keine Backups gefunden" return 1 fi echo "" return 0 } list_s3_backups() { echo "" echo -e "${BLUE}S3-Backups in s3://${S3_BUCKET}/${S3_PATH}/:${NC}" echo "" if ! check_s3cfg; then return 1 fi local count=0 while IFS= read -r line; do if [[ -n "$line" ]]; then local date size path date=$(echo "$line" | awk '{print $1, $2}') size=$(echo "$line" | awk '{print $3}') path=$(echo "$line" | awk '{print $4}') local basename basename=$(basename "$path") # Größe formatieren if [[ $size -gt 1048576 ]]; then size="$((size / 1048576))M" elif [[ $size -gt 1024 ]]; then size="$((size / 1024))K" else size="${size}B" fi count=$((count + 1)) printf " %2d) %-45s %6s %s\n" "$count" "$basename" "$size" "$date" fi done < <(s3cmd ls "s3://${S3_BUCKET}/${S3_PATH}/" 2>/dev/null | grep "payload_db_.*\.sql\.gz" | sort -r) if [[ $count -eq 0 ]]; then print_warning "Keine S3-Backups gefunden" return 1 fi echo "" return 0 } select_local_backup() { local backups=() while IFS= read -r file; do if [[ -n "$file" ]]; then backups+=("$file") fi done < <(find "$BACKUP_DIR" -name "payload_db_*.sql.gz" -type f | sort -r) if [[ ${#backups[@]} -eq 0 ]]; then print_error "Keine lokalen Backups gefunden" exit 1 fi list_local_backups read -p "Backup-Nummer auswählen (1-${#backups[@]}): " selection if ! [[ "$selection" =~ ^[0-9]+$ ]] || [[ $selection -lt 1 ]] || [[ $selection -gt ${#backups[@]} ]]; then print_error "Ungültige Auswahl" exit 1 fi echo "${backups[$((selection-1))]}" } select_s3_backup() { local backups=() while IFS= read -r line; do if [[ -n "$line" ]]; then local path path=$(echo "$line" | awk '{print $4}') backups+=("$path") fi done < <(s3cmd ls "s3://${S3_BUCKET}/${S3_PATH}/" 2>/dev/null | grep "payload_db_.*\.sql\.gz" | sort -r) if [[ ${#backups[@]} -eq 0 ]]; then print_error "Keine S3-Backups gefunden" exit 1 fi list_s3_backups read -p "Backup-Nummer auswählen (1-${#backups[@]}): " selection if ! [[ "$selection" =~ ^[0-9]+$ ]] || [[ $selection -lt 1 ]] || [[ $selection -gt ${#backups[@]} ]]; then print_error "Ungültige Auswahl" exit 1 fi echo "${backups[$((selection-1))]}" } download_from_s3() { local s3_path="$1" local filename filename=$(basename "$s3_path") local local_path="${TEMP_DIR}/${filename}" mkdir -p "$TEMP_DIR" print_step "Lade Backup von S3 herunter: $filename" if s3cmd get "$s3_path" "$local_path" 2>/dev/null; then print_success "Download abgeschlossen" echo "$local_path" else print_error "Download fehlgeschlagen" exit 1 fi } verify_backup() { local backup_file="$1" print_step "Verifiziere Backup-Integrität..." if ! gzip -t "$backup_file" 2>/dev/null; then print_error "Backup-Datei ist beschädigt (gzip-Test fehlgeschlagen)" exit 1 fi print_success "Backup-Integrität OK" } get_db_stats() { local table_count row_count table_count=$(psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c \ "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public';" 2>/dev/null | tr -d ' ') row_count=$(psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c \ "SELECT SUM(n_live_tup) FROM pg_stat_user_tables;" 2>/dev/null | tr -d ' ') echo "Tabellen: ${table_count:-0}, Zeilen: ${row_count:-0}" } stop_application() { print_step "Stoppe Payload CMS..." if command -v pm2 &> /dev/null; then if pm2 list 2>/dev/null | grep -q "payload"; then pm2 stop payload 2>/dev/null || true print_success "Payload gestoppt" else print_warning "Payload läuft nicht unter PM2" fi if pm2 list 2>/dev/null | grep -q "queue-worker"; then pm2 stop queue-worker 2>/dev/null || true print_success "Queue-Worker gestoppt" fi else print_warning "PM2 nicht verfügbar - bitte Anwendung manuell stoppen!" read -p "Weiter wenn Anwendung gestoppt? [Enter] " -r fi } start_application() { print_step "Starte Payload CMS..." if command -v pm2 &> /dev/null; then pm2 start payload 2>/dev/null || true pm2 start queue-worker 2>/dev/null || true print_success "Anwendung gestartet" else print_warning "PM2 nicht verfügbar - bitte Anwendung manuell starten!" fi } perform_restore() { local backup_file="$1" local filename filename=$(basename "$backup_file") echo "" echo -e "${YELLOW}╔════════════════════════════════════════════════════════════╗${NC}" echo -e "${YELLOW}║ WARNUNG: Datenbank-Restore überschreibt alle Daten! ║${NC}" echo -e "${YELLOW}╚════════════════════════════════════════════════════════════╝${NC}" echo "" echo " Backup: $filename" echo " Ziel: $DB_NAME @ $DB_HOST:$DB_PORT" echo " Aktuell: $(get_db_stats)" echo "" read -p "Restore durchführen? Tippe 'RESTORE' zur Bestätigung: " confirmation if [[ "$confirmation" != "RESTORE" ]]; then print_warning "Restore abgebrochen" exit 0 fi echo "" # Anwendung stoppen stop_application # Restore durchführen print_step "Führe Restore durch..." local start_time start_time=$(date +%s) if gunzip -c "$backup_file" | psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -q 2>/dev/null; then local end_time end_time=$(date +%s) local duration=$((end_time - start_time)) print_success "Restore abgeschlossen (${duration}s)" else print_error "Restore fehlgeschlagen!" start_application exit 1 fi # Statistik nach Restore print_step "Verifiziere Restore..." echo " Neue Statistik: $(get_db_stats)" # Anwendung starten start_application echo "" echo -e "${GREEN}╔════════════════════════════════════════════════════════════╗${NC}" echo -e "${GREEN}║ Restore erfolgreich abgeschlossen! ║${NC}" echo -e "${GREEN}╚════════════════════════════════════════════════════════════╝${NC}" echo "" } cleanup_temp() { if [[ -d "$TEMP_DIR" ]]; then rm -rf "$TEMP_DIR" fi } show_usage() { echo "Verwendung: $0 [OPTION] [BACKUP-DATEI]" echo "" echo "Optionen:" echo " (keine) Interaktive Auswahl aus lokalen Backups" echo " Direktes Restore aus lokaler Datei" echo " --from-s3 Interaktive Auswahl aus S3-Backups" echo " --from-s3 Direktes Restore aus S3 (Dateiname)" echo " --list Lokale Backups auflisten" echo " --list-s3 S3-Backups auflisten" echo " --help Diese Hilfe anzeigen" echo "" echo "Beispiele:" echo " $0 # Interaktiv" echo " $0 /path/to/backup.sql.gz # Direkt lokal" echo " $0 --from-s3 # Interaktiv von S3" echo " $0 --from-s3 payload_db_2025-12-11_03-00-00.sql.gz" echo "" } # ============================================================================ # Hauptprogramm # ============================================================================ main() { trap cleanup_temp EXIT # Hilfe anzeigen if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then show_usage exit 0 fi # Backups auflisten if [[ "${1:-}" == "--list" ]]; then list_local_backups exit 0 fi if [[ "${1:-}" == "--list-s3" ]]; then list_s3_backups exit 0 fi print_header # Voraussetzungen prüfen check_pgpass test_db_connection local backup_file="" # S3-Restore if [[ "${1:-}" == "--from-s3" ]]; then if ! check_s3cfg; then exit 1 fi local s3_path if [[ -n "${2:-}" ]]; then # Direkter S3-Pfad s3_path="s3://${S3_BUCKET}/${S3_PATH}/${2}" else # Interaktive Auswahl s3_path=$(select_s3_backup) fi backup_file=$(download_from_s3 "$s3_path") # Lokales Restore mit Dateiangabe elif [[ -n "${1:-}" ]]; then backup_file="$1" if [[ ! -f "$backup_file" ]]; then print_error "Backup-Datei nicht gefunden: $backup_file" exit 1 fi # Interaktive lokale Auswahl else backup_file=$(select_local_backup) fi # Backup verifizieren verify_backup "$backup_file" # Restore durchführen perform_restore "$backup_file" } main "$@"