param( [string]$DumpRoot = "/mnt/user/backups/borg/dumps/latest", [string]$ReportRoot = "/mnt/user/backups/restore-reports", [int]$MaxDumpAgeHours = 26, [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" }, @{ Name = "nextcloud.dump"; Path = Join-Path $DumpRoot "nextcloud.dump" }, @{ Name = "gitea.sqlite.dump"; Path = Join-Path $DumpRoot "gitea.sqlite.dump" }, @{ Name = "vaultwarden.sqlite.dump"; Path = Join-Path $DumpRoot "vaultwarden.sqlite.dump" }, @{ Name = "speedtest-tracker.sqlite.dump"; Path = Join-Path $DumpRoot "speedtest-tracker.sqlite.dump" }, @{ Name = "filebrowser.bolt.dump"; Path = Join-Path $DumpRoot "filebrowser.bolt.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 if ($item.Length -le 0) { $critical.Add("DUMP_EMPTY $($check.Name)") continue } $ageHours = ($now - $item.LastWriteTime).TotalHours if ($ageHours -gt $MaxDumpAgeHours) { $critical.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) { if (-not (Test-Path $ReportRoot)) { $warnings.Add("REPORT_ROOT_MISSING $ReportRoot") break } $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