109 lines
3.7 KiB
Bash
Executable file
109 lines
3.7 KiB
Bash
Executable file
#!/bin/bash
|
||
# 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 -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)
|
||
mkdir -p "$BACKUP_DIR"
|
||
|
||
echo "$(date '+%Y-%m-%d %H:%M:%S') Erstelle Backup $TIMESTAMP..."
|
||
|
||
# MariaDB Dump (aus dem Container)
|
||
docker compose -f "$PROJECT_DIR/docker-compose.yml" \
|
||
--project-directory "$PROJECT_DIR" \
|
||
exec -T db mariadb-dump \
|
||
--user=steuerkram \
|
||
--password=steuerkram \
|
||
--single-transaction \
|
||
--routines \
|
||
--triggers \
|
||
steuerkram > "$BACKUP_DIR/steuerkram_db_$TIMESTAMP.sql"
|
||
|
||
# Uploads sichern (im Container ausführen – umgeht Host-Berechtigungen auf Bind-Mount-Dateien)
|
||
docker compose -f "$PROJECT_DIR/docker-compose.yml" \
|
||
--project-directory "$PROJECT_DIR" \
|
||
exec -T web \
|
||
tar -czf - -C /var/www/html/public/static uploads/ \
|
||
> "$BACKUP_DIR/steuerkram_uploads_$TIMESTAMP.tar.gz"
|
||
|
||
echo " DB: $BACKUP_DIR/steuerkram_db_$TIMESTAMP.sql"
|
||
echo " Uploads: $BACKUP_DIR/steuerkram_uploads_$TIMESTAMP.tar.gz"
|
||
|
||
# --- 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."
|