Merge pull request 'Fix: Backup-Script mit smarter Rotation und Lock-Schutz gegen parallele Läufe' (#32) from fds2610-bot/Steuerkram:main into main

Reviewed-on: https://codeberg.org/fds2610/Steuerkram/pulls/32
This commit is contained in:
fds2610 2026-04-12 07:46:41 +02:00
commit 6285e1c087
2 changed files with 93 additions and 10 deletions

View file

@ -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

View file

@ -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."