Fix: Backup-Script mit smarter Rotation und Lock-Schutz gegen parallele Läufe
This commit is contained in:
parent
f672069e6f
commit
e065f352c7
2 changed files with 93 additions and 10 deletions
|
|
@ -2,6 +2,15 @@
|
|||
|
||||
---
|
||||
|
||||
## 2026-04-12 – Backup: smarte Rotation und Lock-Schutz
|
||||
|
||||
### Geändert
|
||||
- `scripts/backup.sh`: Rotation auf 7 Tages- / 4 Wochen- / 12 Monats-Buckets umgestellt (statt pauschaler 90-Tage-Löschung)
|
||||
- `scripts/backup.sh`: `flock`-Lock verhindert parallele Läufe bei Verzögerungen
|
||||
- `scripts/backup.sh`: Pfade sind absolut (Script funktioniert auch aus Cron ohne Verzeichniswechsel)
|
||||
|
||||
---
|
||||
|
||||
## 2026-04-12 – Uploads: named Volume → Bind Mount
|
||||
|
||||
### Geändert
|
||||
|
|
|
|||
|
|
@ -1,18 +1,37 @@
|
|||
#!/bin/bash
|
||||
# Steuerkram – Backup-Script
|
||||
# Steuerkram – Backup-Script mit smarter Rotation
|
||||
# GoBD: Datensicherung muss regelmäßig erfolgen.
|
||||
# Usage: ./scripts/backup.sh
|
||||
#
|
||||
# Retention:
|
||||
# - letzte 7 Tage: täglich (ein Backup pro Tag)
|
||||
# - Tage 8 – 28: wöchentlich
|
||||
# - Tage 29 – 365: monatlich
|
||||
# - älter als 1 Jahr: werden gelöscht
|
||||
|
||||
set -e
|
||||
set -eo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
BACKUP_DIR="$PROJECT_DIR/backups"
|
||||
LOCK_FILE="/tmp/steuerkram-backup.lock"
|
||||
|
||||
# Verhindert mehrfachen gleichzeitigen Start (z.B. bei Verzögerung durch langen Backup-Lauf)
|
||||
exec 9>"$LOCK_FILE"
|
||||
if ! flock -n 9; then
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') WARN: Backup läuft bereits (Lock: $LOCK_FILE). Abbruch." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_DIR="./backups"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
echo "Erstelle Backup $TIMESTAMP..."
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') Erstelle Backup $TIMESTAMP..."
|
||||
|
||||
# MariaDB Dump (aus dem Container)
|
||||
docker compose exec -T db mariadb-dump \
|
||||
docker compose -f "$PROJECT_DIR/docker-compose.yml" \
|
||||
--project-directory "$PROJECT_DIR" \
|
||||
exec -T db mariadb-dump \
|
||||
--user=steuerkram \
|
||||
--password=steuerkram \
|
||||
--single-transaction \
|
||||
|
|
@ -21,12 +40,67 @@ docker compose exec -T db mariadb-dump \
|
|||
steuerkram > "$BACKUP_DIR/steuerkram_db_$TIMESTAMP.sql"
|
||||
|
||||
# Uploads sichern
|
||||
tar -czf "$BACKUP_DIR/steuerkram_uploads_$TIMESTAMP.tar.gz" ./uploads/
|
||||
tar -czf "$BACKUP_DIR/steuerkram_uploads_$TIMESTAMP.tar.gz" \
|
||||
-C "$PROJECT_DIR" uploads/
|
||||
|
||||
echo "Backup abgeschlossen:"
|
||||
echo " DB: $BACKUP_DIR/steuerkram_db_$TIMESTAMP.sql"
|
||||
echo " Uploads: $BACKUP_DIR/steuerkram_uploads_$TIMESTAMP.tar.gz"
|
||||
|
||||
# Alte Backups löschen (älter als 90 Tage)
|
||||
find "$BACKUP_DIR" -name "steuerkram_*" -mtime +90 -delete
|
||||
echo "Alte Backups bereinigt (>90 Tage)."
|
||||
# --- Smarte Rotation ---
|
||||
echo ""
|
||||
echo "Starte Rotation..."
|
||||
|
||||
NOW=$(date +%s)
|
||||
declare -A keep_daily # YYYYMMDD → timestamp (letzte 7 Tage, täglich)
|
||||
declare -A keep_weekly # Wochennr. → timestamp (Tage 8–28, wöchentlich)
|
||||
declare -A keep_monthly # YYYYMM → timestamp (Tage 29–365, monatlich)
|
||||
|
||||
# Alle vorhandenen Timestamps ermitteln, älteste zuerst
|
||||
# (neuere Einträge überschreiben Buckets → neuestes Backup pro Periode bleibt)
|
||||
mapfile -t TIMESTAMPS < <(
|
||||
for f in "$BACKUP_DIR"/steuerkram_db_????????_??????.sql; do
|
||||
[ -f "$f" ] || continue
|
||||
basename "$f" .sql | sed 's/steuerkram_db_//'
|
||||
done | sort
|
||||
)
|
||||
|
||||
for TS in "${TIMESTAMPS[@]}"; do
|
||||
DATE_PART="${TS:0:8}" # YYYYMMDD
|
||||
YEAR="${TS:0:4}"
|
||||
MONTH="${TS:4:2}"
|
||||
DAY="${TS:6:2}"
|
||||
|
||||
FILE_EPOCH=$(date -d "${YEAR}-${MONTH}-${DAY}" +%s)
|
||||
AGE_DAYS=$(( (NOW - FILE_EPOCH) / 86400 ))
|
||||
WEEK_BUCKET=$(( AGE_DAYS / 7 ))
|
||||
|
||||
if [ "$AGE_DAYS" -le 7 ]; then keep_daily["$DATE_PART"]="$TS"
|
||||
elif [ "$AGE_DAYS" -le 28 ]; then keep_weekly["$WEEK_BUCKET"]="$TS"
|
||||
elif [ "$AGE_DAYS" -le 365 ]; then keep_monthly["${YEAR}${MONTH}"]="$TS"
|
||||
fi
|
||||
# Älter als 365 Tage → kein Bucket → wird weiter unten gelöscht
|
||||
done
|
||||
|
||||
# Keep-Set aus allen Buckets zusammenführen
|
||||
declare -A KEEP
|
||||
for TS in \
|
||||
$([ ${#keep_daily[@]} -gt 0 ] && echo "${keep_daily[@]}") \
|
||||
$([ ${#keep_weekly[@]} -gt 0 ] && echo "${keep_weekly[@]}") \
|
||||
$([ ${#keep_monthly[@]} -gt 0 ] && echo "${keep_monthly[@]}") ; do
|
||||
KEEP["$TS"]=1
|
||||
done
|
||||
|
||||
# Backups löschen, die in keinem Bucket landen
|
||||
DELETED=0
|
||||
for TS in "${TIMESTAMPS[@]}"; do
|
||||
if [ -z "${KEEP[$TS]+x}" ]; then
|
||||
rm -f "$BACKUP_DIR/steuerkram_db_$TS.sql"
|
||||
rm -f "$BACKUP_DIR/steuerkram_uploads_$TS.tar.gz"
|
||||
echo " Gelöscht: $TS"
|
||||
DELETED=$((DELETED + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
echo " Behalten: ${#KEEP[@]} | täglich: ${#keep_daily[@]} | wöchentlich: ${#keep_weekly[@]} | monatlich: ${#keep_monthly[@]}"
|
||||
echo " Gelöscht: $DELETED"
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') Backup & Rotation abgeschlossen."
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue