fix: transaktionales Update mit ZIP-Snapshot (1:1) + Auto-Rollback
This commit is contained in:
+130
-89
@@ -2,24 +2,77 @@
|
|||||||
# Zentraler Updater - wird von update.bat per Invoke-Expression geladen.
|
# Zentraler Updater - wird von update.bat per Invoke-Expression geladen.
|
||||||
# Erwartet im aufrufenden Scope:
|
# Erwartet im aufrufenden Scope:
|
||||||
# $proj - Projektname (z.B. 'VI3DGL', 'FLD-Schichtplanung', 'DRIVE', 'Portal_Union')
|
# $proj - Projektname (z.B. 'VI3DGL', 'FLD-Schichtplanung', 'DRIVE', 'Portal_Union')
|
||||||
# $root - Installationspfad (Verzeichnis mit server.ps1, VERSION usw.)
|
# $root - Installationspfad (Portal-Wurzel mit index.html, VERSION usw.)
|
||||||
# $stopBat - (optional) Dateiname des Stop-Skripts, z.B. 'DRIVE_Stop.bat'.
|
# $stopBat - (optional) Stop-Skript-Dateiname; ''=kein Stop; nicht gesetzt=Fallback 'stop.bat'
|
||||||
# Leerstring '' = kein Stop-Schritt. Nicht gesetzt = Fallback auf 'stop.bat'.
|
# $startBat - (optional) Start-Skript-Dateiname; nicht gesetzt=Fallback 'dgl.bat'
|
||||||
# $startBat - (optional) Dateiname des Start-Skripts, z.B. 'DRIVE_Start.bat'.
|
#
|
||||||
# Nicht gesetzt = Fallback auf 'dgl.bat'.
|
# Strategie: Transaktionales Update mit vollständigem ZIP-Snapshot vor jeder
|
||||||
|
# Änderung. Bei Fehler wird der vorherige Stand automatisch 1:1 wiederhergestellt.
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
$DistBase = "https://updates.rhino.nrw/rhino/StatusQuo_Updates/raw/branch/main/$proj"
|
$DistBase = "https://updates.rhino.nrw/rhino/StatusQuo_Updates/raw/branch/main/$proj"
|
||||||
|
|
||||||
# Stop/Start-Namen VOR jeder Ueberschreibung aus dem aufrufenden Scope lesen
|
|
||||||
$_stopName = if ($null -ne $stopBat) { $stopBat } else { 'stop.bat' }
|
$_stopName = if ($null -ne $stopBat) { $stopBat } else { 'stop.bat' }
|
||||||
$_startName = if ($null -ne $startBat) { $startBat } else { 'dgl.bat' }
|
$_startName = if ($null -ne $startBat) { $startBat } else { 'dgl.bat' }
|
||||||
|
|
||||||
# Lokale Version lesen
|
# ---------------------------------------------------------------------------
|
||||||
|
# Hilfsfunktionen
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
function Stop-Portal {
|
||||||
|
if ($_stopName -ne '') {
|
||||||
|
$p = Join-Path $root $_stopName
|
||||||
|
if (Test-Path -LiteralPath $p) { & cmd /c "`"$p`""; Start-Sleep -Seconds 2 }
|
||||||
|
else { Write-Host " (Stop-Skript '$_stopName' nicht gefunden — übersprungen)" -ForegroundColor DarkYellow }
|
||||||
|
} else { Write-Host " (Kein Stop-Skript konfiguriert — übersprungen)" -ForegroundColor DarkYellow }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Start-Portal {
|
||||||
|
$p = Join-Path $root $_startName
|
||||||
|
if (Test-Path -LiteralPath $p) {
|
||||||
|
Start-Process -FilePath 'cmd.exe' -ArgumentList "/c `"$p`"" -WindowStyle Hidden
|
||||||
|
Write-Host "Server gestartet." -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "Start-Skript '$_startName' nicht gefunden — bitte Server manuell starten." -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Vollständiger Snapshot der Portal-Wurzel als ein ZIP (alles außer .backup + TempUpdate).
|
||||||
|
# Erfasst ALLE Unterordner inkl. data/ → echtes 1:1, inkl. später gelöschter Dateien.
|
||||||
|
function New-Snapshot {
|
||||||
|
$ts = Get-Date -Format 'yyyyMMdd-HHmmss'
|
||||||
|
$bk = Join-Path $root '.backup'
|
||||||
|
if (-not (Test-Path -LiteralPath $bk)) { New-Item -ItemType Directory -Path $bk -Force | Out-Null }
|
||||||
|
$zip = Join-Path $bk "snapshot-$ts.zip"
|
||||||
|
$exclude = @('.backup','TempUpdate')
|
||||||
|
$items = Get-ChildItem -LiteralPath $root -Force | Where-Object { $exclude -notcontains $_.Name }
|
||||||
|
if (-not $items) { throw "Portal-Wurzel ist leer — nichts zu sichern." }
|
||||||
|
Compress-Archive -Path $items.FullName -DestinationPath $zip -CompressionLevel Optimal -Force
|
||||||
|
# Integrität prüfen (öffnen + Eintragszahl)
|
||||||
|
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||||
|
$z = [System.IO.Compression.ZipFile]::OpenRead($zip)
|
||||||
|
$cnt = $z.Entries.Count
|
||||||
|
$z.Dispose()
|
||||||
|
if ($cnt -lt 1) { throw "Snapshot ist leer/ungültig." }
|
||||||
|
# Aufbewahrung: max. 5 Snapshots
|
||||||
|
Get-ChildItem -LiteralPath $bk -Filter 'snapshot-*.zip' | Sort-Object Name -Descending |
|
||||||
|
Select-Object -Skip 5 | Remove-Item -Force -ErrorAction SilentlyContinue
|
||||||
|
return $zip
|
||||||
|
}
|
||||||
|
|
||||||
|
function Restore-Snapshot {
|
||||||
|
param([string]$Zip)
|
||||||
|
# Wurzel leeren (außer .backup), dann Snapshot zurückspielen
|
||||||
|
Get-ChildItem -LiteralPath $root -Force | Where-Object { $_.Name -ne '.backup' } |
|
||||||
|
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
Expand-Archive -LiteralPath $Zip -DestinationPath $root -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 1. Versionsvergleich
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
$localVer = '0.0.0'
|
$localVer = '0.0.0'
|
||||||
$verFile = Join-Path $root 'VERSION'
|
$verFile = Join-Path $root 'VERSION'
|
||||||
if (Test-Path -LiteralPath $verFile) { $localVer = (Get-Content $verFile -Raw).Trim() }
|
if (Test-Path -LiteralPath $verFile) { $localVer = (Get-Content $verFile -Raw).Trim() }
|
||||||
|
|
||||||
# Remote-Version abfragen
|
|
||||||
Write-Host "Prüfe Update für $proj ..."
|
Write-Host "Prüfe Update für $proj ..."
|
||||||
try {
|
try {
|
||||||
$remoteVer = (Invoke-WebRequest -Uri "$DistBase/VERSION" -UseBasicParsing -TimeoutSec 15).Content.Trim()
|
$remoteVer = (Invoke-WebRequest -Uri "$DistBase/VERSION" -UseBasicParsing -TimeoutSec 15).Content.Trim()
|
||||||
@@ -31,20 +84,26 @@ try {
|
|||||||
Write-Host "Lokal: v$localVer"
|
Write-Host "Lokal: v$localVer"
|
||||||
Write-Host "Remote: v$remoteVer"
|
Write-Host "Remote: v$remoteVer"
|
||||||
|
|
||||||
if ($localVer -eq $remoteVer) {
|
# Nur echtes Upgrade anbieten (kein versehentlicher Downgrade)
|
||||||
Write-Host "Bereits auf aktuellem Stand. Kein Update nötig." -ForegroundColor Green
|
$isNewer = $false
|
||||||
|
try { $isNewer = ([System.Version]$remoteVer -gt [System.Version]$localVer) }
|
||||||
|
catch { $isNewer = ($remoteVer -ne $localVer) } # nicht-numerische Version: Fallback
|
||||||
|
if (-not $isNewer) {
|
||||||
|
if ($localVer -eq $remoteVer) { Write-Host "Bereits auf aktuellem Stand. Kein Update nötig." -ForegroundColor Green }
|
||||||
|
else { Write-Host "Remote (v$remoteVer) ist nicht neuer als lokal (v$localVer) — kein Update." -ForegroundColor Yellow }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
$yn = Read-Host "Update von v$localVer auf v$remoteVer installieren? [j/N]"
|
$yn = Read-Host "Update von v$localVer auf v$remoteVer installieren? [j/N]"
|
||||||
if ($yn -notin 'j','J','y','Y') { Write-Host "Abgebrochen."; return }
|
if ($yn -notin 'j','J','y','Y') { Write-Host "Abgebrochen."; return }
|
||||||
|
|
||||||
# Temp-Ordner
|
# ---------------------------------------------------------------------------
|
||||||
|
# 2. Bundles herunterladen (Server läuft noch)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
$tmp = Join-Path $root 'TempUpdate'
|
$tmp = Join-Path $root 'TempUpdate'
|
||||||
Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue
|
Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
New-Item -ItemType Directory -Path $tmp | Out-Null
|
New-Item -ItemType Directory -Path $tmp | Out-Null
|
||||||
|
|
||||||
# Bundles laden
|
|
||||||
Write-Host "Lade Manifest ..."
|
Write-Host "Lade Manifest ..."
|
||||||
$manifest = (Invoke-WebRequest -Uri "$DistBase/MANIFEST.txt" -UseBasicParsing -TimeoutSec 30).Content
|
$manifest = (Invoke-WebRequest -Uri "$DistBase/MANIFEST.txt" -UseBasicParsing -TimeoutSec 30).Content
|
||||||
$bundles = ($manifest -split "`n" | Where-Object { $_ -match '^sync-bundle-' } |
|
$bundles = ($manifest -split "`n" | Where-Object { $_ -match '^sync-bundle-' } |
|
||||||
@@ -57,110 +116,92 @@ foreach ($b in $bundles) {
|
|||||||
Write-Host " sync-entpacken.bat ..."
|
Write-Host " sync-entpacken.bat ..."
|
||||||
Invoke-WebRequest -Uri "$DistBase/sync-entpacken.bat" -OutFile (Join-Path $tmp 'sync-entpacken.bat') -UseBasicParsing -TimeoutSec 30
|
Invoke-WebRequest -Uri "$DistBase/sync-entpacken.bat" -OutFile (Join-Path $tmp 'sync-entpacken.bat') -UseBasicParsing -TimeoutSec 30
|
||||||
|
|
||||||
# --- CHECKSUMMEN VERIFIZIEREN (optional — wird übersprungen wenn keine CHECKSUMS.txt vorhanden) ---
|
# ---------------------------------------------------------------------------
|
||||||
Write-Host "Verifiziere Checksummen ..."
|
# 3. Download-Integrität prüfen (SHA256, falls CHECKSUMS.txt vorhanden)
|
||||||
|
# Schützt gegen unvollständige/korrupte Downloads — NICHT gegen manipulierte
|
||||||
|
# Releases (gleicher Kanal). Authentizität = Release-Disziplin + Tests.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
Write-Host "Verifiziere Download-Integrität ..."
|
||||||
try {
|
try {
|
||||||
$chkContent = (Invoke-WebRequest -Uri "$DistBase/CHECKSUMS.txt" -UseBasicParsing -TimeoutSec 15).Content
|
$chk = (Invoke-WebRequest -Uri "$DistBase/CHECKSUMS.txt" -UseBasicParsing -TimeoutSec 15).Content
|
||||||
$allOk = $true
|
$allOk = $true
|
||||||
foreach ($line in ($chkContent -split "`n")) {
|
foreach ($line in ($chk -split "`n")) {
|
||||||
$line = $line.Trim()
|
$line = $line.Trim()
|
||||||
if ($line -eq '' -or $line.StartsWith('#')) { continue }
|
if ($line -eq '' -or $line.StartsWith('#')) { continue }
|
||||||
# Format: SHA256HASH filename
|
|
||||||
$parts = $line -split '\s+', 2
|
$parts = $line -split '\s+', 2
|
||||||
if ($parts.Count -lt 2) { continue }
|
if ($parts.Count -lt 2) { continue }
|
||||||
$expected = $parts[0].ToLower()
|
$expected = $parts[0].ToLower()
|
||||||
$filename = $parts[1].Trim()
|
$filename = $parts[1].Trim().TrimStart('*') # sha256sum nutzt evtl. '*' Prefix
|
||||||
$localPath = Join-Path $tmp $filename
|
$localPath = Join-Path $tmp $filename
|
||||||
if (-not (Test-Path -LiteralPath $localPath)) { continue }
|
if (-not (Test-Path -LiteralPath $localPath)) { continue }
|
||||||
$actual = (Get-FileHash -LiteralPath $localPath -Algorithm SHA256).Hash.ToLower()
|
$actual = (Get-FileHash -LiteralPath $localPath -Algorithm SHA256).Hash.ToLower()
|
||||||
if ($actual -ne $expected) {
|
if ($actual -ne $expected) {
|
||||||
Write-Host "FEHLER: Checksumme stimmt nicht für $filename!" -ForegroundColor Red
|
Write-Host "FEHLER: Checksumme stimmt nicht für $filename!" -ForegroundColor Red
|
||||||
Write-Host " Erwartet: $expected" -ForegroundColor Red
|
|
||||||
Write-Host " Erhalten: $actual" -ForegroundColor Red
|
|
||||||
$allOk = $false
|
$allOk = $false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (-not $allOk) {
|
if (-not $allOk) {
|
||||||
Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue
|
Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
Write-Host "Update abgebrochen — keine Dateien verändert. Bitte Support kontaktieren." -ForegroundColor Red
|
Write-Host "Update abgebrochen — keine Datei verändert. Bitte Support kontaktieren." -ForegroundColor Red
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Write-Host " Alle Checksummen OK." -ForegroundColor Green
|
Write-Host " Alle Checksummen OK." -ForegroundColor Green
|
||||||
} catch {
|
} catch {
|
||||||
# CHECKSUMS.txt nicht vorhanden — Prüfung überspringen (abwärtskompatibel)
|
Write-Host " (Keine CHECKSUMS.txt — Integritätsprüfung übersprungen)" -ForegroundColor DarkGray
|
||||||
Write-Host " (Keine CHECKSUMS.txt — Prüfung übersprungen)" -ForegroundColor DarkGray
|
|
||||||
}
|
}
|
||||||
# --- END CHECKSUMMEN ---
|
|
||||||
|
|
||||||
# Server stoppen
|
# ---------------------------------------------------------------------------
|
||||||
|
# 4. Server stoppen
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
Write-Host "Stoppe Server ..."
|
Write-Host "Stoppe Server ..."
|
||||||
if ($_stopName -ne '') {
|
Stop-Portal
|
||||||
$_stopPath = Join-Path $root $_stopName
|
|
||||||
if (Test-Path -LiteralPath $_stopPath) {
|
|
||||||
& cmd /c "`"$_stopPath`""
|
|
||||||
Start-Sleep -Seconds 2
|
|
||||||
} else {
|
|
||||||
Write-Host " (Stop-Skript '$_stopName' nicht gefunden — übersprungen)" -ForegroundColor DarkYellow
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Write-Host " (Kein Stop-Skript konfiguriert — übersprungen)" -ForegroundColor DarkYellow
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- BACKUP (vor jeder Dateiänderung) ---
|
# ---------------------------------------------------------------------------
|
||||||
$ts = Get-Date -Format 'yyyyMMdd-HHmmss'
|
# 5. Snapshot (transaktionssicher: ohne gültiges Backup kein Update)
|
||||||
$bkDir = Join-Path $root ".backup\$ts"
|
# ---------------------------------------------------------------------------
|
||||||
New-Item -ItemType Directory -Path $bkDir -Force | Out-Null
|
Write-Host "Erstelle vollständige Sicherung ..."
|
||||||
|
try {
|
||||||
# 1. Nutzerdaten sichern (kritisch!)
|
$snap = New-Snapshot
|
||||||
$_dataPath = Join-Path $root 'data'
|
$snapSize = '{0:N1} MB' -f ((Get-Item -LiteralPath $snap).Length / 1MB)
|
||||||
if (Test-Path -LiteralPath $_dataPath) {
|
Write-Host " Snapshot: $snap ($snapSize)" -ForegroundColor Cyan
|
||||||
Copy-Item -Path $_dataPath -Destination (Join-Path $bkDir 'data') -Recurse -Force
|
} catch {
|
||||||
$dataCount = (Get-ChildItem -LiteralPath $_dataPath -Recurse -File -ErrorAction SilentlyContinue).Count
|
Write-Host "FEHLER beim Sichern: $($_.Exception.Message)" -ForegroundColor Red
|
||||||
Write-Host " Nutzerdaten gesichert ($dataCount Dateien)" -ForegroundColor Cyan
|
Write-Host "Update abgebrochen — keine Datei verändert. Server wird wieder gestartet." -ForegroundColor Yellow
|
||||||
} else {
|
|
||||||
Write-Host " (Kein data/-Ordner gefunden — übersprungen)" -ForegroundColor DarkGray
|
|
||||||
}
|
|
||||||
|
|
||||||
# 2. Code-Dateien sichern
|
|
||||||
foreach ($ext in '*.ps1','*.html','*.js','*.css','*.bat','VERSION') {
|
|
||||||
Get-ChildItem -LiteralPath $root -File -Filter $ext -ErrorAction SilentlyContinue |
|
|
||||||
Copy-Item -Destination $bkDir -Force
|
|
||||||
}
|
|
||||||
Write-Host "Backup erstellt: $bkDir" -ForegroundColor Cyan
|
|
||||||
|
|
||||||
# 3. Alte Backups bereinigen (max. 5 behalten)
|
|
||||||
$_backupBase = Join-Path $root '.backup'
|
|
||||||
$_allBackups = Get-ChildItem -LiteralPath $_backupBase -Directory -ErrorAction SilentlyContinue |
|
|
||||||
Where-Object { $_.Name -match '^\d{8}-\d{6}$' } | Sort-Object Name -Descending
|
|
||||||
if ($_allBackups.Count -gt 5) {
|
|
||||||
$_allBackups | Select-Object -Skip 5 | ForEach-Object {
|
|
||||||
Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction SilentlyContinue
|
|
||||||
}
|
|
||||||
Write-Host " (Nur 5 neueste Backups behalten)" -ForegroundColor DarkGray
|
|
||||||
}
|
|
||||||
# --- END BACKUP ---
|
|
||||||
|
|
||||||
# Entpacken (sync-entpacken.bat aus TempUpdate → schreibt in Root eine Ebene hoeher)
|
|
||||||
Write-Host "Entpacke Update-Dateien ..."
|
|
||||||
& cmd /c "`"$(Join-Path $tmp 'sync-entpacken.bat')`""
|
|
||||||
|
|
||||||
# VERSION lokal aktualisieren
|
|
||||||
[System.IO.File]::WriteAllText($verFile, ($remoteVer + "`r`n"), [System.Text.Encoding]::UTF8)
|
|
||||||
Write-Host "Version auf v$remoteVer aktualisiert." -ForegroundColor Green
|
|
||||||
|
|
||||||
# Aufräumen
|
|
||||||
Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue
|
Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
Start-Portal
|
||||||
# Server neu starten
|
return
|
||||||
$_startPath = Join-Path $root $_startName
|
|
||||||
if (Test-Path -LiteralPath $_startPath) {
|
|
||||||
Start-Process -FilePath 'cmd.exe' -ArgumentList "/c `"$_startPath`"" -WindowStyle Hidden
|
|
||||||
Write-Host "Server gestartet." -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "Start-Skript '$_startName' nicht gefunden — bitte Server manuell starten." -ForegroundColor Yellow
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 6. Update anwenden — bei Fehler automatischer Rollback
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
Write-Host "Wende Update an ..."
|
||||||
|
try {
|
||||||
|
& cmd /c "`"$(Join-Path $tmp 'sync-entpacken.bat')`""
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "sync-entpacken.bat meldete Exitcode $LASTEXITCODE" }
|
||||||
|
[System.IO.File]::WriteAllText($verFile, ($remoteVer + "`r`n"), [System.Text.Encoding]::UTF8)
|
||||||
|
} catch {
|
||||||
|
Write-Host "FEHLER beim Anwenden: $($_.Exception.Message)" -ForegroundColor Red
|
||||||
|
Write-Host "Stelle vorherigen Stand automatisch wieder her ..." -ForegroundColor Yellow
|
||||||
|
try {
|
||||||
|
Restore-Snapshot -Zip $snap
|
||||||
|
Write-Host " Vorheriger Stand (v$localVer) wiederhergestellt." -ForegroundColor Green
|
||||||
|
} catch {
|
||||||
|
Write-Host " AUTO-ROLLBACK FEHLGESCHLAGEN! Bitte rollback.bat doppelklicken." -ForegroundColor Red
|
||||||
|
}
|
||||||
|
Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
Start-Portal
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 7. Aufräumen + Start
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Host "Version auf v$remoteVer aktualisiert." -ForegroundColor Green
|
||||||
|
Start-Portal
|
||||||
|
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host "=== Update abgeschlossen ===" -ForegroundColor Green
|
Write-Host "=== Update abgeschlossen ===" -ForegroundColor Green
|
||||||
Write-Host " Backup liegt unter: $bkDir" -ForegroundColor DarkGray
|
Write-Host " Sicherung: $snap" -ForegroundColor DarkGray
|
||||||
Write-Host " Bei Problemen: rollback.bat doppelklicken!" -ForegroundColor DarkGray
|
Write-Host " Bei Problemen: rollback.bat doppelklicken (funktioniert auch offline)." -ForegroundColor DarkGray
|
||||||
|
|||||||
Reference in New Issue
Block a user