#!/usr/bin/env bash # Sicherung einer selbst gehosteten TeamVis-Instanz mit eigener Datenbank # (install.sh, Variante „eigene DB“): die Schemas public + storage als # pg_dump im Custom-Format und die Storage-Dateien (volumes/storage). # # Liegt im Instanz-Verzeichnis (neben docker-compose.yml) und läuft per Cron: # 15 3 * * * /bin/bash /pfad/instanz/backup.sh >> $HOME/backups//backup.log 2>&1 # # Nicht gesichert wird die .env: Sie enthält alle Secrets und gehört in einen # Passwort-Safe. Ohne sie (JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY, # POSTGRES_PASSWORD) lässt sich die Instanz aus den Dumps aber nicht # wiederherstellen. Wiederherstellung: README.md → „Sicherung und # Wiederherstellung“. # # PUSH_URL (optional, Zeile in $BACKUP_DIR/push.env): Push-Monitor in Uptime # Kuma. Wird nur nach einem vollständigen Lauf aufgerufen — bleibt er aus, # alarmiert Kuma. So fällt auch ein Cron auf, der gar nicht läuft. set -euo pipefail INSTANCE_DIR="${INSTANCE_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" NAME="$(basename "$INSTANCE_DIR")" BACKUP_DIR="${BACKUP_DIR:-$HOME/backups/$NAME}" KEEP_DAYS="${KEEP_DAYS:-30}" [ -f "$BACKUP_DIR/push.env" ] && . "$BACKUP_DIR/push.env" cd "$INSTANCE_DIR" umask 077 mkdir -p "$BACKUP_DIR" STAMP="$(date +%F-%H%M)" DB="$BACKUP_DIR/db-$STAMP.dump" FILES="$BACKUP_DIR/storage-$STAMP.tar.gz" trap 'rm -f "$DB.part" "$FILES.part"' EXIT # public = TeamVis-Daten. storage = Buckets und Objekt-Metadaten, samt der # Migrationstabelle der Storage-API — so legt die API beim Wiederherstellen # nichts neu an. auth und vault legt das Postgres-Image selbst an, TeamVis # nutzt sie nicht. docker compose exec -T db sh -c \ 'PGPASSWORD="$POSTGRES_PASSWORD" pg_dump -h localhost -U supabase_admin -d "$POSTGRES_DB" -Fc -n public -n storage' \ > "$DB.part" # Ein abgeschnittener Dump fällt beim Lesen des Inhaltsverzeichnisses auf. TOC="$(docker compose exec -T db pg_restore -l < "$DB.part")" if ! grep -q "TABLE DATA public employees" <<< "$TOC"; then echo "$(date -Is) FEHLER: Dump unvollständig" >&2 exit 1 fi # --xattrs ist Pflicht: Die Storage-API (Datei-Backend) legt Content-Type und # Cache-Control jeder Datei als erweiterte Attribute (user.supabase.*) ab. # Ohne sie liefert eine wiederhergestellte Instanz die Dateien mit 500 aus # (am 09.10.2026 im Wiederherstellungstest gefunden). tar --xattrs --xattrs-include='user.*' -czf "$FILES.part" -C volumes storage gzip -t "$FILES.part" mv "$DB.part" "$DB" mv "$FILES.part" "$FILES" find "$BACKUP_DIR" \( -name 'db-*.dump' -o -name 'storage-*.tar.gz' \) -mtime +"$KEEP_DAYS" -delete echo "$(date -Is) ok $(basename "$DB") $(stat -c %s "$DB") Bytes, $(basename "$FILES") $(stat -c %s "$FILES") Bytes" if [ -n "${PUSH_URL:-}" ]; then curl -fsS -m 10 "$PUSH_URL?status=up&msg=OK" >/dev/null || echo "$(date -Is) Push an Uptime Kuma fehlgeschlagen" >&2 fi