M8: Backup/Restore-Prozess und Monitoring dokumentiert

docs/betrieb-backup-monitoring.md: taeglicher mysqldump-Cron-Job mit
Aufbewahrung, Restore-Befehl inklusive Hinweis auf scripts/migrate.php,
vierteljaehrlicher Restore-Test. Produktive PHP-Fehlerkonfiguration
(display_errors aus, log_errors an) und die aktiv zu beobachtenden
Signale ohne dediziertes APM-Tool: audit_log fuer ungewoehnliche
Admin-Aktionen, rate_limit_attempts fuer Brute-Force-Versuche,
outbound_emails.status=failed fuer Mailversand-Probleme.

Damit ist M8 fuer den geplanten Scope abgeschlossen, mit einer bewusst
offenen Ausnahme (Content-Security-Policy, braucht Template-Bereinigung
der bestehenden Inline-Styles).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 20:19:20 +02:00
co-authored by Claude Sonnet 5
parent f60c0bb85e
commit 3fc65812ef
2 changed files with 118 additions and 4 deletions
+106
View File
@@ -0,0 +1,106 @@
# Backup, Restore und Monitoring im Produktivbetrieb
Stand: 2026-07-15
Dieses Dokument beschreibt die für den Produktivbetrieb auf dem Webspace
empfohlenen Backup-, Restore- und Monitoring-Maßnahmen. Es ergänzt
`docs/betrieb-mail.md` und `docs/m8-haertung.md`.
## Backups
Die App hat kein eigenes Backup-Tooling; das ist bei einer MySQL-Datenbank
auf Webspace-Hosting bewusst nicht nötig `mysqldump` reicht.
Empfohlener täglicher Cron-Job (Zeitpunkt außerhalb der Hauptnutzungszeit):
```bash
#!/usr/bin/env bash
set -euo pipefail
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
BACKUP_DIR="/pfad/ausserhalb/des/webroots/backups"
mkdir -p "$BACKUP_DIR"
mysqldump \
--single-transaction \
--routines \
--triggers \
-h "$DB_HOST" -P "${DB_PORT:-3306}" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" \
| gzip > "$BACKUP_DIR/kaffeeliste_${TIMESTAMP}.sql.gz"
# Aufbewahrung: 14 Tage taegliche Backups behalten, aeltere loeschen
find "$BACKUP_DIR" -name 'kaffeeliste_*.sql.gz' -mtime +14 -delete
```
Wichtige Punkte:
- `--single-transaction` sorgt für einen konsistenten Snapshot ohne
Tabellen zu sperren (InnoDB, wie hier durchgängig verwendet).
- Der Backup-Ordner muss **außerhalb** des über HTTP erreichbaren Webroots
liegen, sonst wären Dumps öffentlich abrufbar.
- Zusätzlich zu lokalen Backups auf dem Webspace mindestens eine Kopie an
einen anderen Ort übertragen (z. B. verschlüsselt in Cloud-Speicher),
damit ein Ausfall des Webspace-Anbieters nicht auch die Backups
vernichtet.
- `var/uploads`, `var/mail` und `var/sessions` enthalten keine dauerhaft
relevanten Daten (Uploads werden nach Verarbeitung gelöscht,
`var/mail` ist nur der Dev-Log-Transport) und müssen nicht gesichert
werden.
## Restore
```bash
gunzip -c kaffeeliste_20260715_030000.sql.gz | mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME"
```
Danach `php scripts/migrate.php` laufen lassen, falls das Backup älter als
die zuletzt eingespielten Migrationen ist (das Skript ist idempotent und
wendet nur fehlende Migrationen an).
**Restore-Test:** Mindestens einmal pro Quartal einen echten Restore gegen
eine separate Test-Datenbank durchführen und die App dagegen starten
(`scripts/run-dev-server.sh` mit den Test-DB-Zugangsdaten). Ein Backup, das
nie zurückgespielt wurde, ist kein verlässliches Backup.
## Monitoring und Fehlerlogging
### PHP-Fehlerausgabe
Im Dev-Modus zeigt `display_errors=1` Fehler direkt im Browser (siehe
`.local/php-dev.ini`) das ist für lokale Entwicklung richtig, aber
produktiv ein Sicherheitsrisiko (Stacktraces, Dateipfade). Produktiv muss
gelten:
```ini
display_errors = Off
log_errors = On
error_log = /pfad/ausserhalb/des/webroots/logs/php-error.log
```
Die meisten Webspace-Anbieter setzen das bereits serverseitig; im Zweifel
per `.htaccess` oder `ini_set()` am Anfang von `config.php` erzwingen.
### Was aktiv beobachtet werden sollte
- **`php-error.log`**: Fatal Errors und Warnings. Ein plötzlicher Anstieg
deutet meist auf eine kaputte Migration oder einen fehlerhaften Deploy
hin.
- **`audit_log`**: Ungewöhnliche Häufung sicherheitsrelevanter Aktionen
(`participant.access_granted`, `tenant_settings.updated`,
`year_end_bonus.distributed`, `tenant_data.exported`) außerhalb der
üblichen Nutzungszeiten eines Mandanten.
- **`rate_limit_attempts`**: Viele Treffer in kurzer Zeit für einen
`login_ip:*`- oder `login_email:*`-Bucket zeigen einen laufenden
Brute-Force-Versuch, auch wenn er durch das Rate-Limit bereits
abgewehrt wird.
- **`outbound_emails` mit `status = 'failed'`**: Deutet meist auf ein
Problem mit dem lokalen Mailversand hin (siehe
`docs/betrieb-mail.md`), zum Beispiel einen falsch konfigurierten
`APP_MAIL_FROM` oder ein Zustellproblem beim Webspace-Anbieter.
### Bewusst nicht umgesetzt
Ein dediziertes APM-/Monitoring-Tool (z. B. Uptime-Checks, strukturiertes
Log-Shipping, Alerting) ist für den aktuellen Umfang nicht eingerichtet.
Das lohnt sich erst mit echtem Produktivbetrieb und mehreren zahlenden
Mandanten; bis dahin reichen die oben genannten manuellen Prüfpunkte.