# StatusQuo_Updates/scripts/update.ps1 # Zentraler Updater - wird von update.bat per Invoke-Expression geladen. # Erwartet im aufrufenden Scope: # $proj - Projektname (z.B. 'VI3DGL', 'FLD-Schichtplanung', 'DRIVE', 'Portal_Union') # $root - Installationspfad (Portal-Wurzel mit index.html, VERSION usw.) # $stopBat - (optional) Stop-Skript-Dateiname; ''=kein Stop; nicht gesetzt=Fallback 'stop.bat' # $startBat - (optional) Start-Skript-Dateiname; nicht gesetzt=Fallback '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' $DistBase = "https://updates.rhino.nrw/rhino/StatusQuo_Updates/raw/branch/main/$proj" $_stopName = if ($null -ne $stopBat) { $stopBat } else { 'stop.bat' } $_startName = if ($null -ne $startBat) { $startBat } else { 'dgl.bat' } # --------------------------------------------------------------------------- # 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 } } # Ausschlussliste: immer .backup/TempUpdate, plus Einträge aus .backupignore # (große, von Updates NIE berührte Content-Ordner, z.B. _archiv/dokumente). # Ausgeschlossene Ordner werden weder gesichert noch beim Rollback verändert. function Get-ExcludeNames { param([switch]$ForWipe) # WICHTIG: Array strikt als Array aufbauen. 'if (){@(x)}' würde ein # 1-Element-Array zu einem Skalar entpacken → '+=' verkettet dann Strings. $ex = @('.backup') if (-not $ForWipe) { $ex = $ex + 'TempUpdate' } $f = Join-Path $root '.backupignore' if (Test-Path -LiteralPath $f) { $extra = @(Get-Content -LiteralPath $f | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' -and -not $_.StartsWith('#') }) $ex = $ex + $extra } return $ex } # Vollständiger Snapshot der Portal-Wurzel als ein ZIP (außer .backup/TempUpdate # und .backupignore-Einträgen). Erfasst alle Code-Unterordner inkl. data/ # → echtes 1:1 des veränderbaren Stands, 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 = @(Get-ExcludeNames) $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 + .backupignore-Ordner), dann Snapshot zurückspielen. # Ausgeschlossene Content-Ordner bleiben unangetastet an Ort und Stelle. $ex = @(Get-ExcludeNames -ForWipe) Get-ChildItem -LiteralPath $root -Force | Where-Object { $ex -notcontains $_.Name } | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue Expand-Archive -LiteralPath $Zip -DestinationPath $root -Force } # --------------------------------------------------------------------------- # 1. Versionsvergleich # --------------------------------------------------------------------------- $localVer = '0.0.0' $verFile = Join-Path $root 'VERSION' if (Test-Path -LiteralPath $verFile) { $localVer = (Get-Content $verFile -Raw).Trim() } Write-Host "Prüfe Update für $proj ..." try { $remoteVer = (Invoke-WebRequest -Uri "$DistBase/VERSION" -UseBasicParsing -TimeoutSec 15).Content.Trim() } catch { Write-Host "FEHLER: Update-Server nicht erreichbar. Netzverbindung prüfen." -ForegroundColor Red return } Write-Host "Lokal: v$localVer" Write-Host "Remote: v$remoteVer" # Nur echtes Upgrade anbieten (kein versehentlicher Downgrade) $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 } $yn = Read-Host "Update von v$localVer auf v$remoteVer installieren? [j/N]" if ($yn -notin 'j','J','y','Y') { Write-Host "Abgebrochen."; return } # --------------------------------------------------------------------------- # 2. Bundles herunterladen (Server läuft noch) # --------------------------------------------------------------------------- $tmp = Join-Path $root 'TempUpdate' Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue New-Item -ItemType Directory -Path $tmp | Out-Null Write-Host "Lade Manifest ..." $manifest = (Invoke-WebRequest -Uri "$DistBase/MANIFEST.txt" -UseBasicParsing -TimeoutSec 30).Content $bundles = ($manifest -split "`n" | Where-Object { $_ -match '^sync-bundle-' } | ForEach-Object { ($_ -split ' ')[0] } | Sort-Object -Unique) foreach ($b in $bundles) { Write-Host " $b ..." Invoke-WebRequest -Uri "$DistBase/$b" -OutFile (Join-Path $tmp $b) -UseBasicParsing -TimeoutSec 120 } Write-Host " sync-entpacken.bat ..." Invoke-WebRequest -Uri "$DistBase/sync-entpacken.bat" -OutFile (Join-Path $tmp 'sync-entpacken.bat') -UseBasicParsing -TimeoutSec 30 # --------------------------------------------------------------------------- # 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 { $chk = (Invoke-WebRequest -Uri "$DistBase/CHECKSUMS.txt" -UseBasicParsing -TimeoutSec 15).Content $allOk = $true foreach ($line in ($chk -split "`n")) { $line = $line.Trim() if ($line -eq '' -or $line.StartsWith('#')) { continue } $parts = $line -split '\s+', 2 if ($parts.Count -lt 2) { continue } $expected = $parts[0].ToLower() $filename = $parts[1].Trim().TrimStart('*') # sha256sum nutzt evtl. '*' Prefix $localPath = Join-Path $tmp $filename if (-not (Test-Path -LiteralPath $localPath)) { continue } $actual = (Get-FileHash -LiteralPath $localPath -Algorithm SHA256).Hash.ToLower() if ($actual -ne $expected) { Write-Host "FEHLER: Checksumme stimmt nicht für $filename!" -ForegroundColor Red $allOk = $false } } if (-not $allOk) { Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue Write-Host "Update abgebrochen — keine Datei verändert. Bitte Support kontaktieren." -ForegroundColor Red return } Write-Host " Alle Checksummen OK." -ForegroundColor Green } catch { Write-Host " (Keine CHECKSUMS.txt — Integritätsprüfung übersprungen)" -ForegroundColor DarkGray } # --------------------------------------------------------------------------- # 4. Server stoppen # --------------------------------------------------------------------------- Write-Host "Stoppe Server ..." Stop-Portal # --------------------------------------------------------------------------- # 5. Snapshot (transaktionssicher: ohne gültiges Backup kein Update) # --------------------------------------------------------------------------- Write-Host "Erstelle vollständige Sicherung ..." try { $snap = New-Snapshot $snapSize = '{0:N1} MB' -f ((Get-Item -LiteralPath $snap).Length / 1MB) Write-Host " Snapshot: $snap ($snapSize)" -ForegroundColor Cyan } catch { Write-Host "FEHLER beim Sichern: $($_.Exception.Message)" -ForegroundColor Red Write-Host "Update abgebrochen — keine Datei verändert. Server wird wieder gestartet." -ForegroundColor Yellow Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue Start-Portal return } # --------------------------------------------------------------------------- # 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 "=== Update abgeschlossen ===" -ForegroundColor Green Write-Host " Sicherung: $snap" -ForegroundColor DarkGray Write-Host " Bei Problemen: rollback.bat doppelklicken (funktioniert auch offline)." -ForegroundColor DarkGray