6.2 KiB
Betrieb: Sicherung, Wiederherstellung, Aufräumen
Was gesichert werden muss
| Was | Wo | Ohne das … |
|---|---|---|
| Datenbank | Volume einkaufsapp_db_data |
ist alles weg |
.env |
Projektverzeichnis | Sessions ungültig, Push-Anmeldungen tot, DB-Zugang verloren |
| Projektverzeichnis | Quelltext | neu aus dem Archiv holen – kein Datenverlust |
Der Quelltext gehört ins Versionsverwaltungssystem, die .env ausdrücklich
nicht: Sie enthält Datenbankpasswort, SECRET_KEY, SMTP-Zugangsdaten und
den privaten VAPID-Schlüssel. Sie gehört in den Passwortmanager oder in eine
verschlüsselte Sicherung.
Warum die .env so wichtig ist: Ein verlorener SECRET_KEY ist verschmerzbar
(alle müssen sich neu anmelden), ein verlorener VAPID_PRIVATE_KEY auch (alle
müssen Push neu einschalten). Ein verlorenes DB_PASSWORD bei erhaltener
Datenbank ist dagegen ärgerlich – dann kommt man an die eigenen Daten nur noch
über das Root-Passwort heran.
Datenbanksicherung
cd /opt/einkaufsapp
# Konsistenter Auszug ohne Anhalten der Anwendung
docker compose exec -T db mariadb-dump \
-u root -p"$(grep '^DB_ROOT_PASSWORD=' .env | cut -d= -f2-)" \
--single-transaction --quick --routines --events \
--default-character-set=utf8mb4 \
einkaufsapp | gzip > "einkaufsapp-$(date +%F).sql.gz"
--single-transaction ist der entscheidende Schalter: Er nimmt einen
konsistenten Stand aus einem Zeitpunkt, ohne die Tabellen zu sperren. Ohne ihn
kann eine Sicherung mitten in einer Änderung entstehen – zum Beispiel mit einem
Listeneintrag, dessen Artikel noch fehlt.
--quick verhindert, dass große Tabellen komplett in den Arbeitsspeicher
geladen werden.
Als tägliche Aufgabe
sudo tee /etc/cron.daily/einkaufsapp-backup >/dev/null <<'EOF'
#!/bin/sh
set -eu
cd /opt/einkaufsapp
PASS=$(grep '^DB_ROOT_PASSWORD=' .env | cut -d= -f2-)
DEST=/var/backups/einkaufsapp
mkdir -p "$DEST"
docker compose exec -T db mariadb-dump -u root -p"$PASS" \
--single-transaction --quick --routines --events \
--default-character-set=utf8mb4 einkaufsapp \
| gzip > "$DEST/db-$(date +%F).sql.gz"
# Vierzehn Tage aufbewahren
find "$DEST" -name 'db-*.sql.gz' -mtime +14 -delete
EOF
sudo chmod +x /etc/cron.daily/einkaufsapp-backup
Der Auszug landet damit im Dateisystem und wird von deiner vorhandenen Borgmatic-Sicherung mit erfasst. Das ist der bessere Weg, als das Volume direkt zu sichern: Ein Dateisystemabbild einer laufenden Datenbank ist nicht zuverlässig wiederherstellbar.
Prüfen, dass die Sicherung etwas taugt:
zcat /var/backups/einkaufsapp/db-$(date +%F).sql.gz | tail -5
Am Ende muss -- Dump completed on … stehen. Fehlt die Zeile, ist der Auszug
abgebrochen – eine abgeschnittene Sicherung sieht sonst genauso aus wie eine
vollständige.
Wiederherstellung
cd /opt/einkaufsapp
docker compose down
docker volume rm einkaufsapp_db_data # ACHTUNG: löscht den aktuellen Stand
docker compose up -d db
sleep 20 # MariaDB initialisiert sich
zcat einkaufsapp-2026-08-08.sql.gz | docker compose exec -T db \
mariadb -u root -p"$(grep '^DB_ROOT_PASSWORD=' .env | cut -d= -f2-)" einkaufsapp
docker compose up -d
docker compose logs --tail=20 api
Alembic bringt das Schema beim Start auf den neuesten Stand, falls die Sicherung aus einer älteren Fassung stammt. Der umgekehrte Fall – neuere Sicherung, ältere Anwendung – geht nicht; dann erst den Quelltext aktualisieren.
Einmal im Jahr ausprobieren. Eine Sicherung, die nie zurückgespielt wurde, ist eine Vermutung, keine Sicherung.
Aufräumen
Läuft von selbst: einmal 30 Sekunden nach dem Start des api-Containers,
danach alle CLEANUP_INTERVAL_HOURS (Voreinstellung 24).
| Was | Frist | Einstellung |
|---|---|---|
| Weich gelöschte Listen, Einträge, Artikel, Märkte, Warengruppen | 30 Tage | CLEANUP_DELETED_DAYS |
| Outbox-Quittungen | 7 Tage | CLEANUP_OPS_DAYS |
| Abgelaufene Sitzungen | sofort | – |
| Rate-Limit-Zähler | 1 Tag | – |
| Verbrauchte Mail-Token | 7 Tage nach Ablauf | – |
| Abgelaufene Einladungen und öffentliche Links | 30 Tage nach Ablauf | CLEANUP_DELETED_DAYS |
| Produktzwischenspeicher | mindestens 1 Jahr | – |
Manuell auslösen (als Administrator angemeldet):
curl -sS -b cookies.txt -H "X-CSRF-Token: $CSRF" \
-X POST https://einkauf.example.de/api/admin/cleanup
Im Log erscheint nach jedem Durchlauf eine Zeile wie
Aufräumen: 12 gelöschte Einträge, 340 Outbox-Quittungen.
Warum die 30 Tage nicht kürzer sein sollten: So lange kann ein Gerät offline bleiben und beim nächsten Abgleich noch erfahren, dass eine Liste gelöscht wurde. Wird früher aufgeräumt, taucht die Liste auf dem Gerät weiter auf, bis jemand sie von Hand entfernt.
Aktualisieren
cd /opt/einkaufsapp
# Sicherung ZUERST - Migrationen lassen sich nicht immer zurücknehmen
/etc/cron.daily/einkaufsapp-backup
# Neuen Stand einspielen, dann:
./tools/check-env.sh .env
node tools/check-js.mjs
python3 tools/check-nginx.py web
docker compose up -d --build
docker compose logs --tail=30 api # Migrationen beobachten
Nach Änderungen an den Dateien unter web/html/js/ muss VERSION in
web/html/sw.js hochgezählt werden – sonst behalten bereits installierte
Clients die alte Fassung.
docker compose up -d, nicht restart: Ein Neustart übernimmt keine
geänderten Werte aus der .env. Das hat in diesem Projekt schon mehrfach für
Verwirrung gesorgt.
Speicherplatz im Blick behalten
docker compose exec db mariadb -u root -p einkaufsapp -e "
SELECT table_name AS Tabelle,
ROUND(data_length/1024/1024, 1) AS 'Daten MB',
ROUND(index_length/1024/1024, 1) AS 'Index MB',
table_rows AS 'Zeilen (geschätzt)'
FROM information_schema.tables
WHERE table_schema = 'einkaufsapp'
ORDER BY data_length DESC;"
Erwartungsgemäß wachsen price_point (jeder erfasste Preis) und
product_cache (jeder gescannte Strichcode) am stärksten. Beide sind gewollt:
Die Preisdatenbank ist der Zweck, und der Zwischenspeicher verhindert, dass
jeder Scan nach draußen geht.
Wächst applied_op unerwartet, liegt ein Gerät mit einer festhängenden Outbox
vor – dann lohnt ein Blick ins Log des api-Containers.