Steuerkram/scripts/backup.sh
2026-04-13 16:29:28 +02:00

111 lines
3.7 KiB
Bash
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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="$BACKUP_DIR/.backup.lock"
# Backup-Verzeichnis sicherstellen, bevor Lock erworben wird
mkdir -p "$BACKUP_DIR"
# 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)
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."