Files
vendoo/scripts/updater/Vendoo.Updater.Host.ps1
Masterluke77andGitHub 637275effc Updater 3.4 – Lifecycle Hardening (#28)
Production-ready updater lifecycle hardening with final-head checks, Windows PowerShell 5.1 compatibility, reproducible package generation, provenance validation, payload rollback protection and documented acceptance.
2026-07-10 19:54:36 +02:00

141 lines
5.7 KiB
PowerShell

[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][string]$CoreScript,
[Parameter(Mandatory=$true)][string]$StatusFile,
[Parameter(Mandatory=$true)][string]$EventFile,
[Parameter(Mandatory=$true)][string]$InputFile,
[Parameter(Mandatory=$true)][string]$CancelFile,
[Parameter(Mandatory=$true)][string]$SessionFile,
[Parameter(Mandatory=$true)][string]$LogFile,
[switch]$SkipProductionDeploy,
[switch]$SelfTest,
[string]$SelfTestFailAt = ''
)
Set-StrictMode -Version 2.0
$ErrorActionPreference = 'Stop'
$TargetVersion = '1.43.0'
$UpdaterVersion = '3.4.0'
function Get-DefaultSteps {
return @(
[ordered]@{id='package';title='Paket prüfen';state='pending'},
[ordered]@{id='tools';title='System prüfen';state='pending'},
[ordered]@{id='github';title='GitHub verbinden';state='pending'},
[ordered]@{id='production';title='Zielzustand erkennen';state='pending'},
[ordered]@{id='workspace';title='Payload sicher vorbereiten';state='pending'},
[ordered]@{id='tests';title='Vollständig testen';state='pending'},
[ordered]@{id='pullrequest';title='Pull Request abgleichen';state='pending'},
[ordered]@{id='merge';title='Finalen Head prüfen und mergen';state='pending'},
[ordered]@{id='deploy';title='Coolify deployen';state='pending'},
[ordered]@{id='verify';title='Produktion bestätigen';state='pending'}
)
}
function Append-HostLog([string]$Level,[string]$Message) {
try {
$parent = Split-Path -Parent $LogFile
if ($parent) { New-Item -ItemType Directory -Path $parent -Force | Out-Null }
$line = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff') [$Level] $Message$([Environment]::NewLine)"
[IO.File]::AppendAllText($LogFile,$line,(New-Object Text.UTF8Encoding($false)))
} catch {}
}
function Write-HostStatus([string]$State,[string]$Phase,[string]$Detail,[string]$ErrorText='',[string]$Remediation='') {
try {
$percent = 1
if ($State -in @('failed','cancelled','success')) { $percent = 100 }
$payload = [ordered]@{
version=$TargetVersion
updaterVersion=$UpdaterVersion
percent=$percent
phase=$Phase
detail=$Detail
state=$State
error=$ErrorText
remediation=$Remediation
updatedAt=(Get-Date).ToString('o')
logFile=$LogFile
sessionFile=$SessionFile
steps=(Get-DefaultSteps)
request=$null
} | ConvertTo-Json -Depth 8
$parent = Split-Path -Parent $StatusFile
if ($parent) { New-Item -ItemType Directory -Path $parent -Force | Out-Null }
$tmp = "$StatusFile.$PID.host.tmp"
[IO.File]::WriteAllText($tmp,$payload,(New-Object Text.UTF8Encoding($false)))
Move-Item -LiteralPath $tmp -Destination $StatusFile -Force
} catch {
Append-HostLog 'HOST-ERROR' "Status konnte nicht geschrieben werden: $($_.Exception.ToString())"
}
}
try {
foreach($path in @($StatusFile,$EventFile,$InputFile,$CancelFile,$SessionFile,$LogFile)) {
$parent = Split-Path -Parent $path
if ($parent) { New-Item -ItemType Directory -Path $parent -Force | Out-Null }
}
if (-not (Test-Path -LiteralPath $CoreScript -PathType Leaf)) {
throw "Worker-Core fehlt: $CoreScript"
}
Append-HostLog 'HOST' "Worker-Host $UpdaterVersion gestartet. Core: $CoreScript"
Write-HostStatus 'running' 'PowerShell-Parserprüfung' 'Der Worker-Kern wird vor der Ausführung mit dem echten PowerShell-Parser geprüft.'
$parserTokens = $null
$parserErrors = $null
[void][System.Management.Automation.Language.Parser]::ParseFile($CoreScript,[ref]$parserTokens,[ref]$parserErrors)
if ($parserErrors -and $parserErrors.Count -gt 0) {
$details = @($parserErrors | ForEach-Object {
"{0}:{1}:{2} {3}" -f $_.Extent.File,$_.Extent.StartLineNumber,$_.Extent.StartColumnNumber,$_.Message
}) -join [Environment]::NewLine
throw "PowerShell-Parserprüfung des Worker-Kerns fehlgeschlagen.$([Environment]::NewLine)$details"
}
Append-HostLog 'HOST' 'PowerShell-Parserprüfung des Worker-Kerns erfolgreich.'
Write-HostStatus 'running' 'Worker wird gestartet' 'Der geprüfte Worker-Kern wird geladen.'
$invokeParams = @{
StatusFile=$StatusFile
EventFile=$EventFile
InputFile=$InputFile
CancelFile=$CancelFile
SessionFile=$SessionFile
LogFile=$LogFile
}
if ($SkipProductionDeploy) { $invokeParams.SkipProductionDeploy = $true }
if ($SelfTest) { $invokeParams.SelfTest = $true }
if ($SelfTestFailAt) { $invokeParams.SelfTestFailAt = $SelfTestFailAt }
& $CoreScript @invokeParams
$terminal = $false
$stateName = ''
if (Test-Path -LiteralPath $StatusFile) {
try {
$final = Get-Content -LiteralPath $StatusFile -Raw -Encoding UTF8 | ConvertFrom-Json
$stateName = [string]$final.state
$terminal = $stateName -in @('success','failed','cancelled')
} catch {
Append-HostLog 'HOST-ERROR' "Abschlussstatus ist nicht lesbar: $($_.Exception.Message)"
}
}
if (-not $terminal) {
$message = 'Der Update-Kern wurde beendet, ohne einen terminalen Status zu schreiben.'
Append-HostLog 'HOST-ERROR' $message
Write-HostStatus 'failed' 'Update-Kern unvollständig beendet' $message $message 'Das Protokoll enthält jetzt die vollständige Start- und Worker-Diagnose. Es wurden keine stillen Fehler verworfen.'
} else {
Append-HostLog 'HOST' "Worker-Core regulär beendet. Status: $stateName"
}
exit 0
}
catch {
$full = $_.Exception.ToString()
$message = $_.Exception.Message
Append-HostLog 'HOST-FATAL' $full
Write-HostStatus 'failed' 'Worker konnte nicht initialisiert werden' $message $full 'Der Fehler entstand vor oder beim Laden des Worker-Kerns. Protokoll öffnen zeigt die vollständige PowerShell-Ausnahme einschließlich Datei und Zeile.'
try { [Console]::Error.WriteLine($full) } catch {}
Start-Sleep -Milliseconds 350
exit 1
}