diff --git a/docs/MASTER_TODO.md b/docs/MASTER_TODO.md index acbefbf..30b466e 100644 --- a/docs/MASTER_TODO.md +++ b/docs/MASTER_TODO.md @@ -48,7 +48,7 @@ Bewusst nicht jetzt - mit Review-Trigger. | Cold-Backup-Rotation | **Bewusst Hetzner-only** (2026-06-05). Keine zweite rotierende Cold-Kopie. Trigger: stark wachsender Datenwert, wiederholte Hetzner-Probleme, geaenderte Praeferenz | `docs/HARDWARE_INVENTORY.md` | | WAN-Ausfallschutz | **Spaeter evaluieren** (2026-06-05). Mobilfunk-Failover inaktiv; lokale Apps laufen bei WAN-Ausfall weiter. Trigger: haeufigere/laengere DSL-Ausfaelle oder kritischer Remote-Zugang | `docs/NETWORK_INVENTORY.md` | | Docker Critical Events Watcher | **Aktiviert 2026-06-05:** Unraid User Script `docker-critical-events-at-start` nutzt den Supervisor und steht in `schedule.json` auf `frequency: start`; Watcher manuell gestartet, Status `running`. Optionaler ntfy-Smoke wurde nachts bewusst nicht gesendet und kann spaeter mit `docker-critical-events-supervisor.sh smoke` nachgeholt werden | `docs/SERVICE_CATALOG.md`, `services/posture-check/docker-critical-events.sh`, `services/posture-check/unraid-user-scripts.md` | -| Negativ-Test Backup-Frische | Quartalsweise: bewusst kaputten/fehlenden Dump in Testpfad simulieren, pruefen ob `homelab-alerts` feuert | `docs/AUDIT_2026-05-25_TODO.md` | +| Negativ-Test Backup-Frische | **Automatisiert vorbereitet 2026-06-06:** `ops/restore-tests/negative-freshness-alert-test.sh` simuliert fehlende Dumps nur in einem synthetischen Restore-Lab-Pfad und sendet einen Test-Alert nach `homelab-alerts`. Quartalsweise wiederholen: `ops/restore-tests/run-restore-checks.sh freshness-negative` | `ops/restore-tests/README.md`, `docs/AUDIT_2026-05-25_TODO.md` | | End-to-end-DR-Drill | Komplett-Bootstrap Phase 1-5 auf Wegwerf-Host; realistisch erst mit zweiter Hardware (siehe auch Extern blockiert) | `docs/AUDIT_2026-05-25_TODO.md`, `docs/DISASTER_RECOVERY.md` | | Wiederkehrende Restore-Drills | Vaultwarden, Gitea, Authelia, Komodo, Paperless, Immich, Traefik, PostgreSQL, Mongo, Nextcloud, Mealie, Mail-Archiver nach Matrix-Intervallen rotieren | `docs/RESTORE_MATRIX.md`, `docs/RESTORE_HANDBOOK.md` | | Dedizierter SMB-User `veeam-baerchen` | Optional spaeter, nur wenn Unraid-User-/Share-Rechte bewusst angefasst werden | `ops/windows-reinstall/docs/windows-image-backup-baseline.md` | diff --git a/ops/restore-tests/README.md b/ops/restore-tests/README.md index 690061e..9e346ba 100644 --- a/ops/restore-tests/README.md +++ b/ops/restore-tests/README.md @@ -52,6 +52,7 @@ Ziel: - `check-restore-freshness.ps1`: woechentlicher Frische-Check fuer Dumps und Reports - `run-restore-checks.ps1`: einfacher Dispatcher fuer Restore-Jobs - `check-restore-freshness.sh`: hosttauglicher Frische-Check +- `negative-freshness-alert-test.sh`: sicherer Negativtest fuer den Frische-Alarmweg; nutzt synthetische leere Testpfade unter `/mnt/user/backups/restore-lab/freshness-negative`, veraendert keine produktiven Dumps und sendet bei erkanntem Fehler einen Test-Alert nach `homelab-alerts` - `run-restore-checks.sh`: hosttauglicher Dispatcher - `common.sh`: gemeinsame Host-Helferfunktionen - `automation-plan.md`: Host-Job- und Automatisierungsmodell @@ -100,6 +101,7 @@ Aktuell ist das erste validierte Muster vorhanden. - Bash-Dispatcher und Bash-Restore-Jobs am 2026-05-07 erfolgreich hostseitig verifiziert - Restore-Lab und Report-Pfade auf dem Host angelegt - `ntfy`-Wrapper ist fuer Host-Jobs verfuegbar +- Frische-Negativtest ist als sicherer Host-Job verfuegbar: `ops/restore-tests/run-restore-checks.sh freshness-negative`. Erwartetes Ergebnis: der synthetische leere Dump-Pfad erzeugt Criticals, ein Test-Alert erreicht `homelab-alerts`, produktive Dump-Pfade bleiben unangetastet. - Nextcloud-Restore-Test: Scaffold existiert, aber **blockiert**. Nextcloud 33 fuehrt zur Laufzeit `chmod()` auf Dateien unter `/var/www/html` aus (`OC_Util.php:486`). Auf Unraids FUSE/shfs User-Shares ist `chmod` strukturell nicht moeglich, was zu permanenter 503 fuehrt. Loesungsoptionen: (a) Restore-Lab auf ein Cache-Drive statt User Share legen, (b) Docker-Volumes statt Bind-Mounts verwenden, (c) tmpfs-Mount fuer html/ + `rsync` der Borg-Daten hinein. Bis dahin ist Nextcloud als Backlog-Item dokumentiert. - Komodo-Mongo-Daten-Restore am 2026-06-03 erfolgreich: 86904 Dokumente (inkl. 32 Stacks), Report `/mnt/user/backups/restore-reports/komodo-mongo-restore-2026-06-03.md` - naechste grosse Kandidaten sind Mailarchiver und Mealie; Nextcloud bleibt blockiert (shfs-chmod) diff --git a/ops/restore-tests/negative-freshness-alert-test.sh b/ops/restore-tests/negative-freshness-alert-test.sh new file mode 100755 index 0000000..7cea731 --- /dev/null +++ b/ops/restore-tests/negative-freshness-alert-test.sh @@ -0,0 +1,74 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +LAB_ROOT="${LAB_ROOT:-/mnt/user/backups/restore-lab/freshness-negative}" +REPORT_ROOT="${REPORT_ROOT:-/mnt/user/backups/restore-reports}" +ALERT_TOPIC="${ALERT_TOPIC:-homelab-alerts}" +INFO_TOPIC="${INFO_TOPIC:-homelab-info}" +SEND_NTFY="${SEND_NTFY:-1}" + +stamp="$(date +%F-%H%M%S)" +test_root="$LAB_ROOT/$stamp" +dump_root="$test_root/dumps" +test_report_root="$test_root/reports" +report_file="$REPORT_ROOT/freshness-negative-$stamp.md" +raw_log="$test_root/check-output.md" + +mkdir -p "$dump_root" "$test_report_root" "$REPORT_ROOT" + +cleanup() { + rm -rf "$test_root" +} +trap cleanup EXIT + +set +e +DUMP_ROOT="$dump_root" \ +REPORT_ROOT="$test_report_root" \ +MAX_DUMP_AGE_HOURS=26 \ +MAX_REPORT_AGE_DAYS=45 \ + "$SCRIPT_DIR/check-restore-freshness.sh" >"$raw_log" 2>&1 +rc=$? +set -e + +critical_count="$(awk -F': ' '/^Critical:/ {print $2; exit}' "$raw_log" | tr -d '[:space:]')" +critical_count="${critical_count:-0}" + +{ + echo "# Restore Freshness Negative Alert Test" + echo + echo "Timestamp: $(date '+%F %T')" + echo "Result: $([ "$rc" -ne 0 ] && [ "$critical_count" -gt 0 ] && echo "ok" || echo "failed")" + echo "Check exit code: $rc" + echo "Critical count: $critical_count" + echo "Synthetic dump root: $dump_root" + echo "Synthetic report root: $test_report_root" + echo "Production dump root touched: no" + echo + echo "## Check Output" + echo + cat "$raw_log" +} >"$report_file" + +if [ "$rc" -ne 0 ] && [ "$critical_count" -gt 0 ]; then + if [ "$SEND_NTFY" = "1" ]; then + "$SCRIPT_DIR/send-ntfy.sh" \ + "$ALERT_TOPIC" \ + "TEST: Restore freshness alert path ok" \ + "Negativtest erfolgreich: check-restore-freshness.sh meldete ${critical_count} Criticals gegen synthetischen leeren Testpfad. Produktive Dumps wurden nicht veraendert. Report: $report_file" \ + high + fi + echo "Negative freshness alert test ok. Report: $report_file" + exit 0 +fi + +if [ "$SEND_NTFY" = "1" ]; then + "$SCRIPT_DIR/send-ntfy.sh" \ + "$ALERT_TOPIC" \ + "TEST FAILED: Restore freshness alert path" \ + "Negativtest fehlgeschlagen: erwarteter Critical-Zustand wurde nicht erkannt. Report: $report_file" \ + high || true +fi + +echo "Negative freshness alert test failed. Report: $report_file" >&2 +exit 1 diff --git a/ops/restore-tests/run-restore-checks.sh b/ops/restore-tests/run-restore-checks.sh index ef6d84e..129419c 100755 --- a/ops/restore-tests/run-restore-checks.sh +++ b/ops/restore-tests/run-restore-checks.sh @@ -10,6 +10,9 @@ case "$MODE" in freshness) exec "$SCRIPT_DIR/check-restore-freshness.sh" ;; + freshness-negative) + exec "$SCRIPT_DIR/negative-freshness-alert-test.sh" + ;; vaultwarden) if [ "$WHATIF" = "--what-if" ]; then exec "$SCRIPT_DIR/vaultwarden-restore-test.sh" --what-if @@ -95,7 +98,7 @@ case "$MODE" in exec "$SCRIPT_DIR/shared-pg-cluster-restore-test.sh" ;; *) - echo "Usage: $0 {freshness|vaultwarden|gitea|paperless|immich|authelia|adguard|redis|nextcloud|komodo-bootstrap|komodo-mongo-restore|traefik|mailarchiver|mealie|shared-pg-cluster} [--what-if]" >&2 + echo "Usage: $0 {freshness|freshness-negative|vaultwarden|gitea|paperless|immich|authelia|adguard|redis|nextcloud|komodo-bootstrap|komodo-mongo-restore|traefik|mailarchiver|mealie|shared-pg-cluster} [--what-if]" >&2 exit 1 ;; esac