Add restore test automation scaffolding

This commit is contained in:
2026-05-07 11:07:46 +02:00
parent 2cc39c73f6
commit 16416d964f
6 changed files with 231 additions and 0 deletions
+3
View File
@@ -76,6 +76,9 @@ Vor lokaler Arbeit:
Nach lokaler Arbeit:
1. Aenderungen pruefen
2. bei Compose-/Backup-/Restore-Aenderungen relevante manuelle Repo-Checks ausfuehren
- `ops/policy-checks/check_repo.ps1`
- `ops/restore-tests/check-restore-freshness.ps1` oder gezielte Restore-Checks
2. Commit mit sauberer Nachricht
3. `Push origin`
4. Komodo-Webhook im Hinterkopf behalten
+8
View File
@@ -29,6 +29,9 @@ Ziel:
- `paperless-restore-test.ps1`: Paperless-Mini-Restore-Ablauf
- `paperless-plan.md`: konkreter Paperless-Testplan
- `paperless-compose.test.yml`: isolierte Testinstanz fuer Paperless inkl. Test-Postgres und Test-Redis
- `check-restore-freshness.ps1`: woechentlicher Frische-Check fuer Dumps und Reports
- `run-restore-checks.ps1`: einfacher Dispatcher fuer Restore-Jobs
- `automation-plan.md`: Host-Job- und Automatisierungsmodell
## Automatisierungsmodell
@@ -38,6 +41,11 @@ Ziel:
- Meldung: `ntfy`
- Hermes: optional nur fuer Zusammenfassung und Auswertung
Wichtig:
- `check-restore-freshness.ps1` und spaetere automatische Restore-Jobs sind fuer den Unraid-Host gedacht
- im Windows-Clone fehlen die `/mnt/user/...`-Pfade naturgemaess
## Validiertes Grundmuster
Stand nach dem ersten echten Vaultwarden-Test:
+70
View File
@@ -0,0 +1,70 @@
# Restore Automation Plan
## Ziel
Die bereits validierten Restore-Tests fuer `vaultwarden`, `gitea` und `paperless` sollen regelmaessig mit wenig Handarbeit laufen.
## Prinzip
- Ausfuehrung bleibt hostseitig
- Logik bleibt im Repo
- Reports bleiben unter `/mnt/user/backups/restore-reports`
- Restore-Arbeitsdaten bleiben unter `/mnt/user/backups/restore-lab`
- Hermes ist Reporter, nicht Operator
## V1
### Woechentlicher Frische-Check
- Script: `check-restore-freshness.ps1`
- Ziel:
- Dump-Dateien vorhanden
- Dump-Dateien nicht zu alt
- letzte Restore-Reports vorhanden
- Wirkung:
- schneller Fruehwarncheck ohne Containerstart
### Monatliche / zweimonatliche Restore-Jobs
- Script-Dispatcher: `run-restore-checks.ps1`
- Modi:
- `freshness`
- `vaultwarden`
- `gitea`
- `paperless`
- V1 ruft die existierenden dienstspezifischen Scripts zunaechst im `WhatIf`- oder Plan-Modus auf, bis die Vollautomatisierung je Dienst gezielt nachgezogen wird.
## V2
- echte Vollautomatisierung pro Dienst
- `ntfy` Erfolg/Fehler
- optional Hermes-Zusammenfassung ueber vorhandene Reports
## Host-Integration
Empfohlen:
- Unraid User Scripts
- je ein geplanter Job pro Laufklasse
- Ausfuehrung auf dem Unraid-Host, nicht im Windows-Clone
Beispiel:
1. `restore-freshness-weekly`
2. `restore-vaultwarden-monthly`
3. `restore-gitea-monthly`
4. `restore-paperless-bimonthly`
## Erfolgskriterium
Ein automatisierter Lauf ist nur dann erfolgreich, wenn:
- Script sauber endet
- Report geschrieben wird
- bei echten Restore-Laeufen der definierte Smoke-Test erfolgreich war
## Nicht automatisieren
- neue Restore-Typen ohne bewusste Freigabe
- invasive Produktiv-Restores
- Komodo-/Auth-/Secret-Umbauten im selben Job
@@ -0,0 +1,88 @@
param(
[string]$DumpRoot = "/mnt/user/backups/borg/dumps/latest",
[string]$ReportRoot = "/mnt/user/backups/restore-reports",
[int]$MaxDumpAgeHours = 36,
[int]$MaxReportAgeDays = 45
)
$checks = @(
@{ Name = "postgresql17-paperless.dump"; Path = Join-Path $DumpRoot "postgresql17-paperless.dump" },
@{ Name = "postgresql17-mailarchiver.dump"; Path = Join-Path $DumpRoot "postgresql17-mailarchiver.dump" },
@{ Name = "mealie.dump"; Path = Join-Path $DumpRoot "mealie.dump" },
@{ Name = "immich.dump"; Path = Join-Path $DumpRoot "immich.dump" }
)
$reportChecks = @(
@{ Name = "vaultwarden"; Path = Join-Path $ReportRoot "vaultwarden-*.md" },
@{ Name = "gitea"; Path = Join-Path $ReportRoot "gitea-*.md" },
@{ Name = "paperless"; Path = Join-Path $ReportRoot "paperless-*.md" }
)
$now = Get-Date
$critical = New-Object System.Collections.Generic.List[string]
$warnings = New-Object System.Collections.Generic.List[string]
$info = New-Object System.Collections.Generic.List[string]
foreach ($check in $checks) {
if (-not (Test-Path $check.Path)) {
$critical.Add("DUMP_MISSING $($check.Name)")
continue
}
$item = Get-Item $check.Path
$ageHours = ($now - $item.LastWriteTime).TotalHours
if ($ageHours -gt $MaxDumpAgeHours) {
$warnings.Add(("DUMP_STALE {0} age={1:N1}h" -f $check.Name, $ageHours))
} else {
$info.Add(("DUMP_OK {0} age={1:N1}h" -f $check.Name, $ageHours))
}
}
foreach ($check in $reportChecks) {
$latest = Get-ChildItem -Path $ReportRoot -Filter ([System.IO.Path]::GetFileName($check.Path)) -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
if (-not $latest) {
$warnings.Add("REPORT_MISSING $($check.Name)")
continue
}
$ageDays = ($now - $latest.LastWriteTime).TotalDays
if ($ageDays -gt $MaxReportAgeDays) {
$warnings.Add(("REPORT_STALE {0} age={1:N1}d file={2}" -f $check.Name, $ageDays, $latest.Name))
} else {
$info.Add(("REPORT_OK {0} age={1:N1}d file={2}" -f $check.Name, $ageDays, $latest.Name))
}
}
Write-Output "# Restore Freshness Check"
Write-Output ""
Write-Output ("Timestamp: {0}" -f $now.ToString("yyyy-MM-dd HH:mm:ss"))
Write-Output ("Critical: {0}" -f $critical.Count)
Write-Output ("Warnings: {0}" -f $warnings.Count)
Write-Output ("Info: {0}" -f $info.Count)
Write-Output ""
if ($critical.Count -gt 0) {
Write-Output "## Critical"
$critical | ForEach-Object { Write-Output ("- {0}" -f $_) }
Write-Output ""
}
if ($warnings.Count -gt 0) {
Write-Output "## Warnings"
$warnings | ForEach-Object { Write-Output ("- {0}" -f $_) }
Write-Output ""
}
if ($info.Count -gt 0) {
Write-Output "## Info"
$info | ForEach-Object { Write-Output ("- {0}" -f $_) }
}
if ($critical.Count -gt 0) {
exit 1
}
exit 0
+38
View File
@@ -0,0 +1,38 @@
param(
[ValidateSet("freshness","vaultwarden","gitea","paperless")]
[string]$Mode,
[switch]$WhatIf
)
$base = Split-Path -Parent $MyInvocation.MyCommand.Path
switch ($Mode) {
"freshness" {
& (Join-Path $base "check-restore-freshness.ps1")
exit $LASTEXITCODE
}
"vaultwarden" {
if ($WhatIf) {
& (Join-Path $base "vaultwarden-restore-test.ps1") -WhatIf
} else {
& (Join-Path $base "vaultwarden-restore-test.ps1")
}
exit $LASTEXITCODE
}
"gitea" {
if ($WhatIf) {
& (Join-Path $base "gitea-restore-test.ps1") -WhatIf
} else {
& (Join-Path $base "gitea-restore-test.ps1")
}
exit $LASTEXITCODE
}
"paperless" {
if ($WhatIf) {
& (Join-Path $base "paperless-restore-test.ps1") -WhatIf
} else {
& (Join-Path $base "paperless-restore-test.ps1")
}
exit $LASTEXITCODE
}
}
+24
View File
@@ -37,6 +37,30 @@ Spaeter:
- `immich` als eigener Sprint
## Konkreter Kalender
- Jeden Montag, 06:30:
- `check-restore-freshness.ps1`
- Jeden 1. Samstag im Monat, 07:00:
- `vaultwarden`
- Jeden 3. Samstag im Monat, 07:00:
- `gitea`
- Jeden 2. Monat am 2. Samstag, 08:00:
- `paperless`
- Quartalsweise am 1. Werktag des Quartals:
- DR-/Restore-Sanity-Check
## Betriebsmodus
- V1:
- Jobs laufen hostseitig manuell oder per User Script
- `ntfy` ist optional und folgt nach stabiler Basis
- Hermes wertet spaeter nur Reports aus
- V2:
- fester Host-Schedule
- `ntfy` bei Erfolg/Fehler
- Hermes erzeugt Zusammenfassungen und Overviews
## Automatisierung
Automatisch: