diff --git a/scripts/backup.sh b/scripts/backup.sh new file mode 100755 index 0000000..6f7032f --- /dev/null +++ b/scripts/backup.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# Dockly-Pfandsystem Demo - Backup-Skript +# Sichert MySQL-Dump + Uploads-Volume. Aufbewahrung: 20 Tage. + +set -euo pipefail + +PROJECT_DIR="${PROJECT_DIR:-/root/pfandsystem-demo}" +BACKUP_DIR="${BACKUP_DIR:-/root/backups/pfandsystem-demo}" +RETENTION_DAYS="${RETENTION_DAYS:-20}" +TIMESTAMP="$(date +%Y-%m-%d_%H-%M)" + +# Passwort aus .env extrahieren (ohne source - .env hat Werte mit < > die bash sonst stoeren) +MYSQL_PASSWORD="$(grep -E '^MYSQL_PASSWORD=' "$PROJECT_DIR/.env" | head -n1 | cut -d= -f2-)" +if [[ -z "$MYSQL_PASSWORD" ]]; then + echo "FEHLER: MYSQL_PASSWORD nicht in $PROJECT_DIR/.env gefunden" >&2 + exit 1 +fi + +mkdir -p "$BACKUP_DIR" + +# 1) MySQL-Dump +DB_FILE="$BACKUP_DIR/db_${TIMESTAMP}.sql.gz" +echo "[$(date +%T)] DB-Dump -> $DB_FILE" +docker exec pfandsystem-demo-mysql \ + mysqldump --default-character-set=utf8mb4 \ + --single-transaction --routines --triggers \ + -upfandsystem -p"${MYSQL_PASSWORD}" pfandsystem_demo \ + 2>/dev/null | gzip > "$DB_FILE" + +# 2) Uploads-Volume +UPLOADS_FILE="$BACKUP_DIR/uploads_${TIMESTAMP}.tar.gz" +echo "[$(date +%T)] Uploads-Backup -> $UPLOADS_FILE" +docker run --rm \ + -v pfandsystem-demo_backend_uploads:/data:ro \ + -v "$BACKUP_DIR":/backup \ + alpine:3 \ + tar czf "/backup/uploads_${TIMESTAMP}.tar.gz" -C /data . + +# 3) Alte Backups loeschen +echo "[$(date +%T)] Aufraeumen (>${RETENTION_DAYS} Tage)" +find "$BACKUP_DIR" -maxdepth 1 -type f \ + \( -name 'db_*.sql.gz' -o -name 'uploads_*.tar.gz' \) \ + -mtime "+${RETENTION_DAYS}" -delete + +# 4) Zusammenfassung +DB_SIZE=$(du -h "$DB_FILE" | cut -f1) +UP_SIZE=$(du -h "$UPLOADS_FILE" | cut -f1) +TOTAL_SIZE=$(du -sh "$BACKUP_DIR" | cut -f1) +FILE_COUNT=$(find "$BACKUP_DIR" -maxdepth 1 -type f | wc -l) + +echo "[$(date +%T)] Backup fertig" +echo " DB: $DB_SIZE ($DB_FILE)" +echo " Uploads: $UP_SIZE ($UPLOADS_FILE)" +echo " Backup-Verzeichnis gesamt: $TOTAL_SIZE ($FILE_COUNT Dateien)" diff --git a/scripts/restore.sh b/scripts/restore.sh new file mode 100755 index 0000000..f48895f --- /dev/null +++ b/scripts/restore.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# Dockly-Pfandsystem Demo - Restore-Skript +# Stellt einen Backup-Stand wieder her. +# Aufruf: +# ./restore.sh # listet verfuegbare Backups +# ./restore.sh 2026-05-26_03-00 # stellt Stand wieder her (DB + Uploads) +# ./restore.sh 2026-05-26_03-00 db-only # nur DB +# +# ACHTUNG: Ueberschreibt bestehende DB-Daten und Uploads. + +set -euo pipefail + +PROJECT_DIR="${PROJECT_DIR:-/root/pfandsystem-demo}" +BACKUP_DIR="${BACKUP_DIR:-/root/backups/pfandsystem-demo}" +TS="${1:-}" +MODE="${2:-full}" + +MYSQL_PASSWORD="$(grep -E '^MYSQL_PASSWORD=' "$PROJECT_DIR/.env" | head -n1 | cut -d= -f2-)" + +if [[ -z "$TS" ]]; then + echo "Verfuegbare Backups:" + ls -1t "$BACKUP_DIR" 2>/dev/null | grep -E '^db_' | sed 's/^db_//; s/\.sql\.gz$//' | head -30 + echo + echo "Aufruf: $0 [db-only|uploads-only|full]" + exit 0 +fi + +DB_FILE="$BACKUP_DIR/db_${TS}.sql.gz" +UP_FILE="$BACKUP_DIR/uploads_${TS}.tar.gz" + +[[ -f "$DB_FILE" ]] || { echo "Nicht gefunden: $DB_FILE"; exit 1; } + +read -p "Backup '$TS' wirklich wiederherstellen (Modus: $MODE)? [yes/N] " ANS +[[ "$ANS" == "yes" ]] || { echo "Abbruch."; exit 0; } + +if [[ "$MODE" != "uploads-only" ]]; then + echo "[$(date +%T)] DB-Restore aus $DB_FILE" + gunzip -c "$DB_FILE" | docker exec -i pfandsystem-demo-mysql \ + mysql --default-character-set=utf8mb4 -upfandsystem -p"${MYSQL_PASSWORD}" pfandsystem_demo +fi + +if [[ "$MODE" != "db-only" ]]; then + [[ -f "$UP_FILE" ]] || { echo "Uploads-Backup fehlt: $UP_FILE"; exit 1; } + echo "[$(date +%T)] Uploads-Restore aus $UP_FILE" + docker run --rm \ + -v pfandsystem-demo_backend_uploads:/data \ + -v "$BACKUP_DIR":/backup:ro \ + alpine:3 \ + sh -c "rm -rf /data/* && tar xzf /backup/uploads_${TS}.tar.gz -C /data" +fi + +echo "[$(date +%T)] Restore fertig."