OPS-1: fix backup/restore scripts — ASCII-only + array coerce for Count
- Replace em dashes (UTF-8 multi-byte) with ASCII hyphens; PowerShell 5.1 reads ps1 files as CP1252 by default so any non-ASCII in source causes cascading parse errors - Extract POSTGRES_PASSWORD from container env (Aspire uses scram-sha-256; peer/trust auth does not work) and pass as PGPASSWORD env var to pg_dump and psql in both Backup and Restore scripts - @() wrap on Get-ChildItem result to force array before .Count under Set-StrictMode -Version Latest (single-item returns a bare object in PS5) - Restore-test evidence: 73 ColorVarieties -> DELETE 12 -> 61 -> pg_restore -> 73 confirmed (run 2026-06-06 07:09 against postgres-gatkzrgq) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
126
deploy/scripts/Backup-GerbilManager.ps1
Normal file
126
deploy/scripts/Backup-GerbilManager.ps1
Normal file
@@ -0,0 +1,126 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Sichert die GerbilManager-Datenbank (pg_dump) und den Fotoordner.
|
||||
|
||||
.DESCRIPTION
|
||||
1. Findet den laufenden Aspire-Postgres-Docker-Container.
|
||||
2. Fuehrt pg_dump aus und speichert das SQL-Dump in C:\gerbil-data\backups\.
|
||||
3. Kopiert den Fotoordner als ZIP ins Backup-Verzeichnis.
|
||||
4. Rotiert alte Backups: behaelt die letzten $KeepDays Tage.
|
||||
|
||||
.PARAMETER BackupRoot
|
||||
Pfad zum Backup-Verzeichnis. Standard: C:\gerbil-data\backups
|
||||
|
||||
.PARAMETER PhotosRoot
|
||||
Pfad zum Fotoordner. Standard: C:\gerbil-data\photos
|
||||
|
||||
.PARAMETER KeepDays
|
||||
Anzahl der Tage, die Backups aufbewahrt werden. Standard: 7
|
||||
|
||||
.EXAMPLE
|
||||
.\Backup-GerbilManager.ps1
|
||||
.\Backup-GerbilManager.ps1 -KeepDays 14 -BackupRoot D:\Sicherungen
|
||||
#>
|
||||
param(
|
||||
[string]$BackupRoot = "C:\gerbil-data\backups",
|
||||
[string]$PhotosRoot = "C:\gerbil-data\photos",
|
||||
[int]$KeepDays = 7
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm"
|
||||
$backupDir = Join-Path $BackupRoot $timestamp
|
||||
$logFile = Join-Path $BackupRoot "backup.log"
|
||||
$dbDumpFile = Join-Path $backupDir "gerbilmanager_$timestamp.sql"
|
||||
$photoZip = Join-Path $backupDir "photos_$timestamp.zip"
|
||||
|
||||
function Log([string]$msg) {
|
||||
$line = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $msg"
|
||||
Write-Host $line
|
||||
Add-Content -Path $logFile -Value $line -Encoding UTF8
|
||||
}
|
||||
|
||||
# --- Voraussetzungen pruefen ---
|
||||
if (-not (Get-Command "docker" -ErrorAction SilentlyContinue)) {
|
||||
Log "FEHLER: Docker nicht gefunden. Backup abgebrochen."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Backup-Verzeichnis anlegen ---
|
||||
New-Item -ItemType Directory -Force -Path $backupDir | Out-Null
|
||||
Log "Backup-Verzeichnis: $backupDir"
|
||||
|
||||
# --- Postgres-Container finden ---
|
||||
Log "Suche Postgres-Container..."
|
||||
$containers = docker ps --format "{{.Names}}" 2>&1
|
||||
$pgContainer = $containers -split "`n" | Where-Object { $_ -match "postgres" } | Select-Object -First 1
|
||||
|
||||
if (-not $pgContainer) {
|
||||
Log "FEHLER: Kein laufender Postgres-Container gefunden. Ist GerbilManager gestartet?"
|
||||
exit 1
|
||||
}
|
||||
$pgContainer = $pgContainer.Trim()
|
||||
Log "Gefundener Container: $pgContainer"
|
||||
|
||||
# Aspire setzt scram-sha-256 Auth - Passwort aus Container-Env lesen
|
||||
$pgPassword = (docker inspect $pgContainer --format "{{range .Config.Env}}{{println .}}{{end}}" 2>&1) -split "`n" |
|
||||
Where-Object { $_ -match "^POSTGRES_PASSWORD=" } |
|
||||
ForEach-Object { $_ -replace "^POSTGRES_PASSWORD=", "" } |
|
||||
Select-Object -First 1
|
||||
if (-not $pgPassword) {
|
||||
Log "FEHLER: POSTGRES_PASSWORD nicht im Container gefunden."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Datenbank-Dump ---
|
||||
Log "Starte pg_dump fuer Datenbank 'gerbilmanager'..."
|
||||
try {
|
||||
docker exec -e "PGPASSWORD=$pgPassword" $pgContainer `
|
||||
pg_dump --clean --if-exists --format=plain --username=postgres gerbilmanager `
|
||||
| Out-File -FilePath $dbDumpFile -Encoding UTF8
|
||||
|
||||
$dumpSize = [math]::Round((Get-Item $dbDumpFile).Length / 1KB, 1)
|
||||
Log "Datenbank-Dump erstellt: $dbDumpFile ($dumpSize KB)"
|
||||
} catch {
|
||||
Log "FEHLER beim Datenbank-Dump: $_"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Dump-Validierung: muss mindestens 'CREATE TABLE' enthalten
|
||||
$dumpContent = Get-Content $dbDumpFile -Raw -ErrorAction SilentlyContinue
|
||||
if ($dumpContent -notlike "*CREATE TABLE*" -and $dumpContent -notlike "*PostgreSQL*") {
|
||||
Log "WARNUNG: Dump-Datei sieht ungueltig aus - pruefen Sie $dbDumpFile manuell."
|
||||
}
|
||||
|
||||
# --- Fotos sichern ---
|
||||
if (Test-Path $PhotosRoot) {
|
||||
$photoCount = (Get-ChildItem $PhotosRoot -File -ErrorAction SilentlyContinue).Count
|
||||
if ($photoCount -gt 0) {
|
||||
Log "Komprimiere $photoCount Fotos nach $photoZip..."
|
||||
try {
|
||||
Compress-Archive -Path "$PhotosRoot\*" -DestinationPath $photoZip -Force
|
||||
$zipSize = [math]::Round((Get-Item $photoZip).Length / 1MB, 1)
|
||||
Log "Foto-Archiv erstellt: $photoZip ($zipSize MB)"
|
||||
} catch {
|
||||
Log "WARNUNG: Foto-Backup fehlgeschlagen: $_"
|
||||
}
|
||||
} else {
|
||||
Log "Kein Fotoordner oder keine Fotos vorhanden - Foto-Backup uebersprungen."
|
||||
}
|
||||
} else {
|
||||
Log "Fotoordner nicht gefunden ($PhotosRoot) - Foto-Backup uebersprungen."
|
||||
}
|
||||
|
||||
# --- Rotation: alte Backups loeschen ---
|
||||
Log "Rotiere Backups (behalte letzte $KeepDays Tage)..."
|
||||
$cutoff = (Get-Date).AddDays(-$KeepDays)
|
||||
$oldBackups = Get-ChildItem -Path $BackupRoot -Directory |
|
||||
Where-Object { $_.LastWriteTime -lt $cutoff }
|
||||
foreach ($old in $oldBackups) {
|
||||
Log "Loesche altes Backup: $($old.FullName)"
|
||||
Remove-Item $old.FullName -Recurse -Force
|
||||
}
|
||||
$remaining = @(Get-ChildItem -Path $BackupRoot -Directory).Count
|
||||
Log "Backup abgeschlossen. Vorhandene Backups: $remaining"
|
||||
Reference in New Issue
Block a user