188 lines
5.7 KiB
PowerShell
188 lines
5.7 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")
|
|
Files = @(
|
|
"borg-ui.sqlite",
|
|
"filebrowser.bolt.dump",
|
|
"gitea.sqlite.dump",
|
|
"grafana.sqlite",
|
|
"immich.dump",
|
|
"komodo-mongo.archive.gz",
|
|
"mealie.dump",
|
|
"nextcloud.dump",
|
|
"postgresql17-authelia.dump",
|
|
"postgresql17-globals.sql",
|
|
"postgresql17-mailarchiver.dump",
|
|
"postgresql17-paperless.dump",
|
|
"speedtest-tracker.sqlite.dump",
|
|
"vaultwarden.sqlite.dump"
|
|
)
|
|
# Migration-/Cutover-Arbeitsverzeichnisse bleiben im Borg-Scope, sind aber
|
|
# keine Nearline-Pflichtartefakte und enthalten teils root-only Dateien.
|
|
ExcludeDirs = @(".tmp", "immich-vectorchord-*", "nextcloud-redis-pre-redis8-*", "pg18-major-*", "redis8-*", "shared-redis-pre-redis8-*")
|
|
},
|
|
@{
|
|
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"
|
|
Files = @("*.*")
|
|
ExcludeFiles = @()
|
|
ExcludeDirs = @(".tmp")
|
|
}
|
|
)
|
|
|
|
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
|
|
|
|
$files = @("*.*")
|
|
if ($Job.ContainsKey("Files") -and $Job.Files.Count -gt 0) {
|
|
$files = $Job.Files
|
|
}
|
|
|
|
$args = @(
|
|
$Job.Source,
|
|
$Job.Destination
|
|
)
|
|
$args += $files
|
|
$args += @(
|
|
"/E",
|
|
"/COPY:DAT",
|
|
"/DCOPY:DAT",
|
|
"/R:2",
|
|
"/W:5",
|
|
"/FFT",
|
|
"/XJ",
|
|
"/NP",
|
|
"/TEE",
|
|
"/LOG:$logPath"
|
|
)
|
|
|
|
if ($Job.ContainsKey("ExcludeDirs") -and $Job.ExcludeDirs.Count -gt 0) {
|
|
$args += "/XD"
|
|
$args += $Job.ExcludeDirs
|
|
}
|
|
|
|
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)"
|
|
|
|
# stdout an $null hängen, damit der Robocopy-Live-Output nicht
|
|
# in $results landet und die Report-Tabelle zerlegt. /LOG: + /TEE
|
|
# protokollieren weiter vollstaendig.
|
|
& robocopy @args | Out-Null
|
|
$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"
|