feat: Backup-Skript mit 20 Tage Aufbewahrung + Restore
scripts/backup.sh: - MySQL-Dump (utf8mb4, single-transaction, routines, triggers) -> gz - Uploads-Volume per alpine-tar -> tar.gz - Loescht Backups aelter als 20 Tage (find -mtime) - Liest MYSQL_PASSWORD via grep aus .env (kein source - .env enthaelt MAIL_FROM mit <> die bash sonst als Redirect deutet) - Konfigurierbar via env PROJECT_DIR/BACKUP_DIR/RETENTION_DAYS scripts/restore.sh: - Listet verfuegbare Backups bei Aufruf ohne Argument - Modi: full (default), db-only, uploads-only - Sicherheitsabfrage vor Ueberschreiben Cron auf Server: taeglich 03:00 -> /var/log/pfandsystem-demo-backup.log Backup-Ziel: /root/backups/pfandsystem-demo/
This commit is contained in:
54
scripts/backup.sh
Executable file
54
scripts/backup.sh
Executable file
@@ -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)"
|
||||
52
scripts/restore.sh
Executable file
52
scripts/restore.sh
Executable file
@@ -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 <timestamp> [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."
|
||||
Reference in New Issue
Block a user