Add restore test automation scaffolding
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user