Files
homelab-infra/ops/h-drive-nearline/pull-critical-backups.ps1
T
Micha 24d0d90670 Make dump output 0644 by default, exclude flash config from H pull
pre-backup-dumps.sh: atomic_write nimmt jetzt einen optionalen
mode-Parameter (Default 0644). Damit sind alle DB-/SQLite-/BoltDB-
/Mongo-Dumps konsistent 0644 und vom Nearline-Pull lesbar. Die
sensible unraid-flash-config-Familie (.tar.gz, .sha256, .manifest)
ruft explizit mit mode 600 auf und bleibt damit Operator-only.
Loest das Permission-Problem fuer filebrowser.bolt.dump (Source
ist 0640) im naechsten regulaeren Dump-Lauf.

pull-critical-backups.ps1: Jobs koennen ExcludeFiles ueber /XF
mitliefern. borg-dumps-latest schliesst die unraid-flash-config-
Artefakte aus, weil sie bewusst 0600 bleiben sollen und sonst den
Lauf abbrechen lassen. Restore-Quelle fuer Flash-Config bleibt
das Hetzner-Borg-Repo.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 20:44:50 +02:00

153 lines
4.5 KiB
PowerShell

param(
[string]$SourceRoot = "\\192.168.178.58\backups",
[string]$DestinationRoot = "H:\kallilab-nearline-backups",
[switch]$WhatIf
)
$ErrorActionPreference = "Stop"
$Jobs = @(
@{
Name = "borg-dumps-latest"
Source = Join-Path $SourceRoot "borg\dumps\latest"
Destination = Join-Path $DestinationRoot "borg-dumps\latest"
Purpose = "Latest database/application dumps (Unraid-Flash-Artefakte bewusst ausgeschlossen, weil 0600 root:root - dafuer bleibt die Hetzner-Borg-Kette die Restore-Quelle)"
# /XF schliesst bewusst die unraid-flash-config-Artefakte aus,
# weil sie hostseitig 0600 root:root sind und der SMB-Share das
# nicht ueberbruecken kann. Restore-Quelle dafuer bleibt das
# Hetzner-Borg-Repo (siehe docs/RESTORE_MATRIX.md Tier 1 Unraid OS Flash).
ExcludeFiles = @("unraid-flash-config.tar.gz", "unraid-flash-config.tar.gz.sha256", "unraid-flash-config.manifest.txt")
},
@{
Name = "gitea-bundles"
Source = Join-Path $SourceRoot "git-bundles\gitea"
Destination = Join-Path $DestinationRoot "git-bundles\gitea"
Purpose = "Verified bare-repository bundles for Gitea bootstrap"
ExcludeFiles = @()
}
)
function Assert-PathExists {
param(
[string]$Path,
[string]$Label
)
if (-not (Test-Path -LiteralPath $Path)) {
throw "$Label not found: $Path"
}
}
function Invoke-RobocopyJob {
param(
[hashtable]$Job,
[string]$LogRoot
)
$logPath = Join-Path $LogRoot ("{0}-{1}.log" -f (Get-Date -Format "yyyyMMdd-HHmmss"), $Job.Name)
New-Item -ItemType Directory -Force -Path $Job.Destination | Out-Null
$args = @(
$Job.Source,
$Job.Destination,
"/E",
"/COPY:DAT",
"/DCOPY:DAT",
"/R:2",
"/W:5",
"/FFT",
"/XJ",
"/XD",
".tmp",
"/NP",
"/TEE",
"/LOG:$logPath"
)
if ($Job.ContainsKey("ExcludeFiles") -and $Job.ExcludeFiles.Count -gt 0) {
$args += "/XF"
$args += $Job.ExcludeFiles
}
Write-Host "Running robocopy job: $($Job.Name)"
Write-Host " Source: $($Job.Source)"
Write-Host " Destination: $($Job.Destination)"
& robocopy @args
$code = $LASTEXITCODE
if ($code -gt 7) {
throw "Robocopy job '$($Job.Name)' failed with exit code $code. See log: $logPath"
}
[pscustomobject]@{
Name = $Job.Name
Source = $Job.Source
Destination = $Job.Destination
ExitCode = $code
Log = $logPath
}
}
Assert-PathExists -Path $SourceRoot -Label "Source root"
foreach ($job in $Jobs) {
Assert-PathExists -Path $job.Source -Label "Source for job '$($job.Name)'"
}
if ($WhatIf) {
Write-Host "H:/ nearline pull plan only. No files will be copied."
Write-Host "SourceRoot: $SourceRoot"
Write-Host "DestinationRoot: $DestinationRoot"
Write-Host ""
foreach ($job in $Jobs) {
Write-Host "- $($job.Name)"
Write-Host " Purpose: $($job.Purpose)"
Write-Host " Source: $($job.Source)"
Write-Host " Destination: $($job.Destination)"
}
exit 0
}
$destinationDrive = Split-Path -Qualifier $DestinationRoot
Assert-PathExists -Path $destinationDrive -Label "Destination drive"
$logRoot = Join-Path $DestinationRoot "_logs"
$reportRoot = Join-Path $DestinationRoot "_reports"
New-Item -ItemType Directory -Force -Path $DestinationRoot, $logRoot, $reportRoot | Out-Null
$results = foreach ($job in $Jobs) {
Invoke-RobocopyJob -Job $job -LogRoot $logRoot
}
$reportPath = Join-Path $reportRoot ("nearline-pull-{0}.md" -f (Get-Date -Format "yyyy-MM-dd-HHmmss"))
$lines = @()
$lines += "# H:/ Nearline Pull Report - $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
$lines += ""
$lines += "- Source root: ``$SourceRoot``"
$lines += "- Destination root: ``$DestinationRoot``"
$lines += "- Mode: non-destructive copy, no ``/MIR``, no purge"
$lines += ""
$lines += "| Job | Exit code | Source | Destination | Log |"
$lines += "|---|---:|---|---|---|"
foreach ($result in $results) {
$lines += "| $($result.Name) | $($result.ExitCode) | ``$($result.Source)`` | ``$($result.Destination)`` | ``$($result.Log)`` |"
}
$lines += ""
$lines += "Expected critical artifacts after run:"
$lines += ""
$lines += "- ``borg-dumps/latest/immich.dump``"
$lines += "- ``borg-dumps/latest/komodo-mongo.archive.gz``"
$lines += "- ``git-bundles/gitea/latest-report.md``"
$lines += "- ``git-bundles/gitea/micha/*.bundle``"
$lines += ""
$lines += "Bewusst NICHT in Nearline-Scope:"
$lines += ""
$lines += "- ``unraid-flash-config.tar.gz`` (hostseitig 0600 root:root; Restore aus Hetzner-Borg)"
$lines | Set-Content -LiteralPath $reportPath -Encoding UTF8
Write-Host "H:/ nearline pull completed."
Write-Host "Report: $reportPath"