1739 lines
80 KiB
PowerShell
1739 lines
80 KiB
PowerShell
$ErrorActionPreference = 'Stop'
|
||
$VendooDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||
$Version = '1.34.3'
|
||
$Port = 8124
|
||
$InstallModeFile = Join-Path $VendooDir '.vendoo-install-mode'
|
||
|
||
try {
|
||
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
||
[Console]::InputEncoding = $utf8NoBom
|
||
[Console]::OutputEncoding = $utf8NoBom
|
||
$OutputEncoding = $utf8NoBom
|
||
} catch {}
|
||
|
||
|
||
function Write-Utf8NoBomFile([string]$Path, [string]$Content) {
|
||
$utf8 = New-Object System.Text.UTF8Encoding($false)
|
||
[System.IO.File]::WriteAllText($Path, $Content, $utf8)
|
||
}
|
||
|
||
function Write-Header {
|
||
Clear-Host
|
||
Write-Host ''
|
||
Write-Host ' ╔══════════════════════════════════════╗' -ForegroundColor DarkGreen
|
||
Write-Host ' ║ ║' -ForegroundColor DarkGreen
|
||
Write-Host ' ║ V E N D O O S E T U P ║' -ForegroundColor Green
|
||
Write-Host ' ║ Listing-Generator v'$Version' ║' -ForegroundColor DarkGreen
|
||
Write-Host ' ║ ║' -ForegroundColor DarkGreen
|
||
Write-Host ' ╚══════════════════════════════════════╝' -ForegroundColor DarkGreen
|
||
Write-Host ''
|
||
}
|
||
|
||
function Write-Step($step, $text) {
|
||
Write-Host " [$step] " -ForegroundColor Cyan -NoNewline
|
||
Write-Host $text
|
||
}
|
||
|
||
function Write-OK($text) {
|
||
Write-Host " [OK] " -ForegroundColor Green -NoNewline
|
||
Write-Host $text
|
||
}
|
||
|
||
function Write-Warn($text) {
|
||
Write-Host " [!] " -ForegroundColor Yellow -NoNewline
|
||
Write-Host $text
|
||
}
|
||
|
||
function Write-Err($text) {
|
||
Write-Host " [X] " -ForegroundColor Red -NoNewline
|
||
Write-Host $text
|
||
}
|
||
|
||
function Remove-LegacyTryOnComponents {
|
||
$migrationMarker = Join-Path $VendooDir '.migration-image-stack-cleanup-1.20.0'
|
||
if (Test-Path -LiteralPath $migrationMarker) { return }
|
||
|
||
$legacyRoot = Join-Path $VendooDir 'local-ai\fashn-vton'
|
||
$legacyUploadRoot = Join-Path $VendooDir 'uploads\vto-models'
|
||
$legacyArchiveRoot = Join-Path $VendooDir 'uploads\ai-generated\legacy-models'
|
||
$hasLegacyRuntime = (Test-Path -LiteralPath $legacyRoot) -or (Test-Path -LiteralPath $legacyUploadRoot)
|
||
if ($hasLegacyRuntime) {
|
||
Write-Host ''
|
||
Write-Step 'Cleanup' 'Entferne den aufgegebenen lokalen Try-on-Stack. Eigene Bilder bleiben erhalten ...'
|
||
}
|
||
|
||
try {
|
||
Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
|
||
Where-Object { $_.CommandLine -match 'fashn-vton|local-vto|service\.py.+8190' } |
|
||
ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
|
||
} catch {}
|
||
try { & schtasks.exe /Delete /F /TN 'Vendoo FASHN VTON' 2>$null | Out-Null } catch {}
|
||
try { Remove-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Run' -Name 'VendooFashnVton' -ErrorAction SilentlyContinue } catch {}
|
||
try {
|
||
$startup = [Environment]::GetFolderPath('Startup')
|
||
if ($startup) { Remove-Item -LiteralPath (Join-Path $startup 'Vendoo-FASHN-VTON.cmd') -Force -ErrorAction SilentlyContinue }
|
||
} catch {}
|
||
|
||
if (Test-Path -LiteralPath $legacyUploadRoot) {
|
||
Get-ChildItem -LiteralPath $legacyUploadRoot -File -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
|
||
$relative = $_.FullName.Substring($legacyUploadRoot.Length).TrimStart('\')
|
||
$target = Join-Path $legacyArchiveRoot $relative
|
||
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $target) | Out-Null
|
||
if (-not (Test-Path -LiteralPath $target)) { Move-Item -LiteralPath $_.FullName -Destination $target -Force -ErrorAction SilentlyContinue }
|
||
}
|
||
Remove-Item -LiteralPath $legacyUploadRoot -Recurse -Force -ErrorAction SilentlyContinue
|
||
}
|
||
|
||
if (Test-Path -LiteralPath $legacyRoot) {
|
||
Write-Step 'Cleanup' 'Entferne alte Python-, Modell- und Dienstdateien ...'
|
||
Remove-Item -LiteralPath $legacyRoot -Recurse -Force -ErrorAction SilentlyContinue
|
||
}
|
||
@(
|
||
'scripts\install-local-vto.ps1','scripts\uninstall-local-vto.ps1',
|
||
'lib\ai-vto-bfl.mjs','lib\ai-vto-local.mjs','lib\ai-vto-model-jobs.mjs','lib\ai-vto-model-prompts.mjs',
|
||
'local-ai\workflows\vendoo_flux_tryon_api.json'
|
||
) | ForEach-Object { Remove-Item -LiteralPath (Join-Path $VendooDir $_) -Force -ErrorAction SilentlyContinue }
|
||
Get-ChildItem -LiteralPath (Join-Path $VendooDir 'logs') -Filter 'local-vto*' -File -ErrorAction SilentlyContinue |
|
||
Remove-Item -Force -ErrorAction SilentlyContinue
|
||
|
||
$legacyEnv = Join-Path $VendooDir 'local-ai\.env.local-flux'
|
||
if (Test-Path -LiteralPath $legacyEnv) {
|
||
$cleanLines = Get-Content -LiteralPath $legacyEnv -ErrorAction SilentlyContinue | Where-Object { $_ -notmatch '^LOCAL_IMAGE_TRYON_WORKFLOW_PATH=' }
|
||
Write-Utf8NoBomFile -Path $legacyEnv -Content (($cleanLines -join [Environment]::NewLine) + [Environment]::NewLine)
|
||
}
|
||
|
||
$rootEnv = Join-Path $VendooDir '.env'
|
||
if (Test-Path -LiteralPath $rootEnv) {
|
||
$cleanRootEnv = Get-Content -LiteralPath $rootEnv -ErrorAction SilentlyContinue | Where-Object {
|
||
$_ -notmatch '^(BFL_API_KEY|LOCAL_IMAGE_TRYON_WORKFLOW_PATH|VENDOO_VTO_URL|VENDOO_VTO_TOKEN)='
|
||
}
|
||
Write-Utf8NoBomFile -Path $rootEnv -Content (($cleanRootEnv -join [Environment]::NewLine) + [Environment]::NewLine)
|
||
}
|
||
|
||
$legacyDocs = @(
|
||
'SLICE_24_VIRTUAL_TRYON_PROMPT_POPUP.md','SLICE_24_1_TRYON_WORKFLOW_SELF_HEAL.md',
|
||
'SLICE_25_PRODUCTIVE_VIRTUAL_TRYON.md','SLICE_25_SHA256.json','SLICE_25_VERIFICATION.json',
|
||
'SLICE_26_LOCAL_FASHN_VTON_FLUX_PROMPT.md','SLICE_26_SHA256.json','SLICE_26_VERIFICATION.json',
|
||
'SLICE_26_1_PYTHON_INSTALLER_LIVE_STATUS.md','SLICE_26_1_SHA256.json','SLICE_26_1_VERIFICATION.json',
|
||
'SLICE_26_2_INSTALLER_ENCODING_PYTHON_DETECTION.md','SLICE_26_2_SHA256.json','SLICE_26_2_VERIFICATION.json',
|
||
'SLICE_26_3_PORTABLE_PYTHON_RUNTIME.md','SLICE_26_3_SHA256.json','SLICE_26_3_VERIFICATION.json',
|
||
'SLICE_26_4_UV_PYTHON_DETECTION.md','SLICE_26_4_SHA256.json','SLICE_26_4_VERIFICATION.json',
|
||
'SLICE_26_5_NATIVE_PROCESS_RUNNER.md','SLICE_26_5_VERIFICATION.json',
|
||
'SLICE_26_6_VTO_PIP_BOOTSTRAP_STATUS.md','SLICE_26_6_VERIFICATION.json',
|
||
'SLICE_26_7_NPM_PUBLIC_REGISTRY_ATOMIC_REPAIR.md','SLICE_26_7_VERIFICATION.json',
|
||
'SLICE_26_8_VTO_DEPENDENCY_INSTALLER.md','SLICE_26_8_VERIFICATION.json',
|
||
'SLICE_26_9_VTO_UTF8_PARSER_AUTOSTART.md','SLICE_27_FLUX_MODEL_STUDIO_LIBRARY.md','SLICE_27_VERIFICATION.json'
|
||
)
|
||
foreach ($doc in $legacyDocs) { Remove-Item -LiteralPath (Join-Path $VendooDir ('docs\' + $doc)) -Force -ErrorAction SilentlyContinue }
|
||
|
||
Write-Utf8NoBomFile -Path $migrationMarker -Content "Vendoo 1.20.0 image-stack cleanup completed."
|
||
if ($hasLegacyRuntime) { Write-OK 'Alter Try-on-Stack wurde entfernt; FLUX bleibt vollständig erhalten.' }
|
||
}
|
||
|
||
|
||
function Test-VendooPowerShellFiles {
|
||
$relativeFiles = @(
|
||
'scripts\install-local-flux.ps1',
|
||
'scripts\uninstall-local-flux.ps1',
|
||
'setup-gui.ps1'
|
||
)
|
||
$failed = $false
|
||
foreach ($relative in $relativeFiles) {
|
||
$path = Join-Path $VendooDir $relative
|
||
if (-not (Test-Path -LiteralPath $path)) {
|
||
Write-Err ("Pflichtskript fehlt: {0}" -f $relative)
|
||
$failed = $true
|
||
continue
|
||
}
|
||
$tokens = $null
|
||
$errors = $null
|
||
[System.Management.Automation.Language.Parser]::ParseFile($path, [ref]$tokens, [ref]$errors) | Out-Null
|
||
if ($errors.Count -gt 0) {
|
||
Write-Err ("PowerShell-Syntaxfehler in {0}" -f $relative)
|
||
foreach ($parseError in $errors) {
|
||
Write-Host (" Zeile {0}: {1}" -f $parseError.Extent.StartLineNumber, $parseError.Message) -ForegroundColor Red
|
||
}
|
||
$failed = $true
|
||
}
|
||
}
|
||
return (-not $failed)
|
||
}
|
||
|
||
function Test-NodeInstalled {
|
||
try {
|
||
$v = & node --version 2>$null
|
||
if ($v -match '^v(\d+)') {
|
||
$major = [int]$Matches[1]
|
||
return @{ Installed = $true; Version = $v; Major = $major }
|
||
}
|
||
} catch {}
|
||
return @{ Installed = $false; Version = $null; Major = 0 }
|
||
}
|
||
|
||
function Test-NpmInstalled {
|
||
try {
|
||
$v = & npm --version 2>$null
|
||
if ($v) { return $true }
|
||
} catch {}
|
||
return $false
|
||
}
|
||
|
||
function Get-VendooInstallState {
|
||
$nodeModules = Test-Path (Join-Path $VendooDir 'node_modules')
|
||
$envFile = Test-Path (Join-Path $VendooDir '.env')
|
||
$dbFile = Test-Path (Join-Path $VendooDir 'db\vendoo.db')
|
||
$state = 'not-installed'
|
||
if ($nodeModules -and $envFile) { $state = 'installed' }
|
||
elseif ($nodeModules -or $envFile -or $dbFile) { $state = 'partial' }
|
||
return @{ State = $state; NodeModules = $nodeModules; EnvFile = $envFile; DbFile = $dbFile }
|
||
}
|
||
|
||
function Get-LocalFluxScriptPath {
|
||
return Join-Path $VendooDir 'scripts\install-local-flux.ps1'
|
||
}
|
||
|
||
|
||
function Stop-VendooNodeProcesses {
|
||
$stopped = 0
|
||
try {
|
||
$escapedDir = [regex]::Escape($VendooDir)
|
||
$processes = Get-CimInstance Win32_Process -Filter "Name='node.exe'" -ErrorAction SilentlyContinue |
|
||
Where-Object { $_.CommandLine -and $_.CommandLine -match $escapedDir }
|
||
foreach ($process in $processes) {
|
||
try {
|
||
Stop-Process -Id $process.ProcessId -Force -ErrorAction Stop
|
||
$stopped++
|
||
} catch {}
|
||
}
|
||
} catch {}
|
||
if ($stopped -gt 0) {
|
||
Write-Warn "$stopped laufende Vendoo-Node-Prozess(e) beendet"
|
||
Start-Sleep -Milliseconds 750
|
||
}
|
||
}
|
||
|
||
function Remove-NodeModulesSafe {
|
||
$nm = Join-Path $VendooDir 'node_modules'
|
||
if (-not (Test-Path $nm)) { return $true }
|
||
|
||
Stop-VendooNodeProcesses
|
||
Write-Step '...' 'Unvollstaendige node_modules werden bereinigt...'
|
||
|
||
try {
|
||
Remove-Item $nm -Recurse -Force -Confirm:$false -ErrorAction Stop
|
||
} catch {
|
||
try {
|
||
& cmd.exe /d /c "rmdir /s /q `"$nm`"" | Out-Null
|
||
} catch {}
|
||
}
|
||
|
||
if (Test-Path $nm) {
|
||
Write-Err 'node_modules konnte nicht vollstaendig entfernt werden.'
|
||
Write-Host ' Ursache ist meist ein noch laufender Node-Prozess, Virenscanner oder Explorer-Zugriff.' -ForegroundColor Yellow
|
||
Write-Host ' Bitte alle Vendoo-/Node-Fenster schliessen, notfalls Windows neu starten und Reparieren erneut ausfuehren.' -ForegroundColor Yellow
|
||
return $false
|
||
}
|
||
return $true
|
||
}
|
||
|
||
function Repair-VendooNpmRegistryConfiguration {
|
||
$lockFile = Join-Path $VendooDir 'package-lock.json'
|
||
$npmrcFile = Join-Path $VendooDir '.npmrc'
|
||
$publicRegistry = 'https://registry.npmjs.org/'
|
||
|
||
$npmrcContent = @(
|
||
'registry=https://registry.npmjs.org/',
|
||
'audit=false',
|
||
'fund=false',
|
||
'strict-ssl=true',
|
||
'prefer-offline=true',
|
||
'fetch-retries=5',
|
||
'fetch-retry-factor=2',
|
||
'fetch-retry-mintimeout=10000',
|
||
'fetch-retry-maxtimeout=120000'
|
||
) -join [Environment]::NewLine
|
||
Write-Utf8NoBomFile -Path $npmrcFile -Content ($npmrcContent + [Environment]::NewLine)
|
||
|
||
if (-not (Test-Path -LiteralPath $lockFile)) { return $true }
|
||
try {
|
||
$raw = [System.IO.File]::ReadAllText($lockFile)
|
||
$original = $raw
|
||
$raw = [regex]::Replace(
|
||
$raw,
|
||
'https://packages\.applied-caas-gateway\d+\.internal\.api\.openai\.org/artifactory/api/npm/npm-public/',
|
||
$publicRegistry,
|
||
[System.Text.RegularExpressions.RegexOptions]::IgnoreCase
|
||
)
|
||
if ($raw -match '(?i)internal\.api\.openai\.org|applied-caas-gateway|/artifactory/api/npm/') {
|
||
Write-Err 'package-lock.json enthält weiterhin eine nicht öffentliche Registry-URL.'
|
||
Write-Host ' Das Paket wurde aus einer Build-Umgebung verunreinigt. npm ci wird aus Sicherheitsgründen nicht gestartet.' -ForegroundColor Yellow
|
||
return $false
|
||
}
|
||
if ($raw -ne $original) {
|
||
Write-Warn 'Interne Build-Registry im package-lock.json wurde automatisch auf registry.npmjs.org korrigiert.'
|
||
Write-Utf8NoBomFile -Path $lockFile -Content $raw
|
||
}
|
||
return $true
|
||
} catch {
|
||
Write-Err ("package-lock.json konnte nicht geprüft werden: {0}" -f $_.Exception.Message)
|
||
return $false
|
||
}
|
||
}
|
||
|
||
function Get-VendooNodeModulesBackupPath {
|
||
return (Join-Path $VendooDir 'node_modules.vendoo-backup')
|
||
}
|
||
|
||
function Restore-VendooNodeModulesBackup {
|
||
$nodeModules = Join-Path $VendooDir 'node_modules'
|
||
$backup = Get-VendooNodeModulesBackupPath
|
||
if (-not (Test-Path -LiteralPath $backup)) { return }
|
||
try {
|
||
if (Test-Path -LiteralPath $nodeModules) {
|
||
Remove-Item -LiteralPath $nodeModules -Recurse -Force -ErrorAction SilentlyContinue
|
||
}
|
||
Move-Item -LiteralPath $backup -Destination $nodeModules -Force -ErrorAction Stop
|
||
Write-Warn 'Vorherige node_modules wurden nach dem fehlgeschlagenen npm-Lauf wiederhergestellt.'
|
||
} catch {
|
||
Write-Warn ("node_modules-Sicherung konnte nicht automatisch wiederhergestellt werden: {0}" -f $_.Exception.Message)
|
||
}
|
||
}
|
||
|
||
function Prepare-VendooNodeModulesBackup {
|
||
$nodeModules = Join-Path $VendooDir 'node_modules'
|
||
$backup = Get-VendooNodeModulesBackupPath
|
||
Stop-VendooNodeProcesses
|
||
|
||
if ((Test-Path -LiteralPath $backup) -and -not (Test-Path -LiteralPath $nodeModules)) {
|
||
try { Move-Item -LiteralPath $backup -Destination $nodeModules -Force -ErrorAction Stop } catch {}
|
||
}
|
||
if (Test-Path -LiteralPath $backup) {
|
||
try { Remove-Item -LiteralPath $backup -Recurse -Force -ErrorAction Stop } catch {
|
||
Write-Err 'Alte node_modules-Sicherung konnte nicht entfernt werden.'
|
||
return $false
|
||
}
|
||
}
|
||
if (Test-Path -LiteralPath $nodeModules) {
|
||
try {
|
||
Move-Item -LiteralPath $nodeModules -Destination $backup -Force -ErrorAction Stop
|
||
Write-Step '...' 'Bestehende node_modules wurden vorübergehend gesichert.'
|
||
} catch {
|
||
Write-Warn 'node_modules konnten nicht atomar gesichert werden. Vendoo versucht die robuste Bereinigung.'
|
||
if (-not (Remove-NodeModulesSafe)) { return $false }
|
||
}
|
||
}
|
||
return $true
|
||
}
|
||
|
||
function Remove-VendooNodeModulesBackup {
|
||
$backup = Get-VendooNodeModulesBackupPath
|
||
if (Test-Path -LiteralPath $backup) {
|
||
try { Remove-Item -LiteralPath $backup -Recurse -Force -ErrorAction Stop } catch {
|
||
Write-Warn 'Die alte node_modules-Sicherung konnte nicht gelöscht werden und kann später manuell entfernt werden.'
|
||
}
|
||
}
|
||
}
|
||
|
||
function Invoke-VendooNpmInstall([switch]$ForceClean) {
|
||
$logDir = Join-Path $VendooDir 'logs'
|
||
if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null }
|
||
$logFile = Join-Path $logDir 'last-npm-install.log'
|
||
$lockFile = Join-Path $VendooDir 'package-lock.json'
|
||
$command = if (Test-Path $lockFile) { 'ci' } else { 'install' }
|
||
|
||
if (-not (Repair-VendooNpmRegistryConfiguration)) { return $false }
|
||
if (-not (Prepare-VendooNodeModulesBackup)) { return $false }
|
||
|
||
$npmCommand = $null
|
||
try { $npmCommand = (Get-Command 'npm.cmd' -ErrorAction Stop).Source } catch {}
|
||
if ([string]::IsNullOrWhiteSpace([string]$npmCommand)) {
|
||
Restore-VendooNodeModulesBackup
|
||
Write-Err 'npm.cmd wurde nicht gefunden.'
|
||
return $false
|
||
}
|
||
|
||
$attempt = 1
|
||
$maxAttempts = 3
|
||
while ($attempt -le $maxAttempts) {
|
||
$stdoutFile = Join-Path $logDir ('npm-install-{0}.stdout.log' -f $attempt)
|
||
$stderrFile = Join-Path $logDir ('npm-install-{0}.stderr.log' -f $attempt)
|
||
$runnerFile = Join-Path $logDir ('npm-install-{0}.cmd' -f $attempt)
|
||
Remove-Item -LiteralPath $stdoutFile,$stderrFile,$runnerFile -Force -ErrorAction SilentlyContinue
|
||
|
||
$runnerContent = @(
|
||
'@echo off',
|
||
('cd /d "{0}"' -f $VendooDir),
|
||
('call "{0}" {1} --registry=https://registry.npmjs.org/ --no-audit --no-fund --loglevel=warn' -f $npmCommand, $command),
|
||
'set "VENDOO_NPM_EXIT=%ERRORLEVEL%"',
|
||
'exit /b %VENDOO_NPM_EXIT%'
|
||
) -join "`r`n"
|
||
[System.IO.File]::WriteAllText($runnerFile, $runnerContent + "`r`n", [System.Text.Encoding]::ASCII)
|
||
|
||
$process = $null
|
||
$output = @()
|
||
$exitCode = 1
|
||
$npmTimedOut = $false
|
||
try {
|
||
$cmdArguments = ('/d /s /c ""{0}""' -f $runnerFile)
|
||
$process = Start-Process -FilePath $env:ComSpec -ArgumentList $cmdArguments -WorkingDirectory $VendooDir -PassThru -WindowStyle Hidden -RedirectStandardOutput $stdoutFile -RedirectStandardError $stderrFile
|
||
$started = Get-Date
|
||
$lastShown = -1
|
||
$npmTimeoutSeconds = 1800
|
||
while (-not $process.HasExited) {
|
||
Start-Sleep -Seconds 1
|
||
$process.Refresh()
|
||
$elapsed = [int]((Get-Date) - $started).TotalSeconds
|
||
if ($elapsed -ge $npmTimeoutSeconds) {
|
||
$npmTimedOut = $true
|
||
try { $process.Kill() } catch {}
|
||
break
|
||
}
|
||
if ($elapsed -ge 2 -and [Math]::Floor($elapsed / 2) -gt $lastShown) {
|
||
$lastShown = [Math]::Floor($elapsed / 2)
|
||
$consoleWidth = 118
|
||
try { if ($Host.UI.RawUI.WindowSize.Width -gt 20) { $consoleWidth = [Math]::Max(40, $Host.UI.RawUI.WindowSize.Width - 1) } } catch {}
|
||
$statusText = (" npm $command läuft seit ${elapsed}s (Versuch $attempt/$maxAttempts)")
|
||
if ($statusText.Length -gt $consoleWidth) { $statusText = $statusText.Substring(0, $consoleWidth) }
|
||
Write-Host ("`r" + $statusText.PadRight($consoleWidth)) -NoNewline -ForegroundColor DarkGray
|
||
}
|
||
}
|
||
$process.WaitForExit()
|
||
$process.Refresh()
|
||
$consoleWidth = 118
|
||
try { if ($Host.UI.RawUI.WindowSize.Width -gt 20) { $consoleWidth = [Math]::Max(40, $Host.UI.RawUI.WindowSize.Width - 1) } } catch {}
|
||
Write-Host ("`r" + (' ' * $consoleWidth) + "`r") -NoNewline
|
||
$exitCode = if ($npmTimedOut) { -2 } else { [int]$process.ExitCode }
|
||
|
||
$stdoutLines = if (Test-Path -LiteralPath $stdoutFile) { @(Get-Content -LiteralPath $stdoutFile -ErrorAction SilentlyContinue) } else { @() }
|
||
$stderrLines = if (Test-Path -LiteralPath $stderrFile) { @(Get-Content -LiteralPath $stderrFile -ErrorAction SilentlyContinue) } else { @() }
|
||
$output = @($stdoutLines + $stderrLines)
|
||
if ($npmTimedOut) { $output += 'Vendoo hat npm nach 30 Minuten ohne Abschluss beendet.' }
|
||
|
||
# Zweite Sicherheitsstufe: Ein echter npm-Fehler darf niemals als Exit-Code 0 durchrutschen.
|
||
$fatalOutput = @($output | Where-Object { ([string]$_) -match '(?i)^\s*npm\s+(error|ERR!)\b' })
|
||
if ($exitCode -eq 0 -and $fatalOutput.Count -gt 0) {
|
||
$exitCode = 1
|
||
$output += 'Vendoo-Sicherheitsprüfung: npm meldete Fehlertext trotz Exit-Code 0. Lauf wird als fehlgeschlagen gewertet.'
|
||
}
|
||
} catch {
|
||
$output = @($_.Exception.Message)
|
||
$exitCode = 1
|
||
}
|
||
|
||
$header = @(
|
||
('Zeit: {0}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss')),
|
||
('Befehl: npm {0} --registry=https://registry.npmjs.org/ --no-audit --no-fund --loglevel=warn' -f $command),
|
||
('Arbeitsordner: {0}' -f $VendooDir),
|
||
('Versuch: {0}/{1}' -f $attempt, $maxAttempts),
|
||
('Exit-Code: {0}' -f $exitCode),
|
||
''
|
||
)
|
||
Write-Utf8NoBomFile -Path $logFile -Content ((@($header + $output) -join [Environment]::NewLine) + [Environment]::NewLine)
|
||
|
||
Remove-Item -LiteralPath $stdoutFile,$stderrFile,$runnerFile -Force -ErrorAction SilentlyContinue
|
||
|
||
if ($exitCode -eq 0) {
|
||
$warnings = @($output | Where-Object { ([string]$_) -match '(?i)^npm\s+warn|deprecated' })
|
||
if ($warnings.Count -gt 0) {
|
||
Write-Warn ("npm $command war erfolgreich, meldete aber $($warnings.Count) Warnung(en). Details: $logFile")
|
||
}
|
||
Remove-VendooNodeModulesBackup
|
||
Write-OK "Dependencies mit npm $command installiert"
|
||
return $true
|
||
}
|
||
|
||
$text = ($output | Out-String)
|
||
$isNetworkError = $text -match '(?i)ETIMEDOUT|ECONNRESET|ENOTFOUND|EAI_AGAIN|ECONNREFUSED|network request|502 Bad Gateway|503 Service Unavailable'
|
||
if ($attempt -lt $maxAttempts -and $isNetworkError) {
|
||
$waitSeconds = if ($attempt -eq 1) { 5 } else { 15 }
|
||
Write-Warn ("npm-Netzwerkfehler. Neuer Versuch in $waitSeconds Sekunden über registry.npmjs.org.")
|
||
if (Test-Path -LiteralPath (Join-Path $VendooDir 'node_modules')) {
|
||
Remove-Item -LiteralPath (Join-Path $VendooDir 'node_modules') -Recurse -Force -ErrorAction SilentlyContinue
|
||
}
|
||
Start-Sleep -Seconds $waitSeconds
|
||
$attempt++
|
||
continue
|
||
}
|
||
|
||
Write-Err "npm $command fehlgeschlagen (Exit-Code $exitCode)"
|
||
Write-Host " Vollständiges Protokoll: $logFile" -ForegroundColor Yellow
|
||
$lastLines = @($output | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) } | Select-Object -Last 20)
|
||
foreach ($line in $lastLines) { Write-Host " $line" -ForegroundColor DarkYellow }
|
||
Restore-VendooNodeModulesBackup
|
||
return $false
|
||
}
|
||
|
||
Restore-VendooNodeModulesBackup
|
||
return $false
|
||
}
|
||
|
||
|
||
function Get-VendooInstallMode {
|
||
if (Test-Path -LiteralPath $InstallModeFile) {
|
||
try {
|
||
$mode = (Get-Content -LiteralPath $InstallModeFile -Raw).Trim().ToLowerInvariant()
|
||
if (@('windows-local', 'docker') -contains $mode) { return $mode }
|
||
} catch {}
|
||
}
|
||
if (Test-Path -LiteralPath (Join-Path $VendooDir 'node_modules')) { return 'windows-local' }
|
||
return 'windows-local'
|
||
}
|
||
|
||
function Set-VendooInstallMode([string]$Mode) {
|
||
Write-Utf8NoBomFile -Path $InstallModeFile -Content ($Mode.Trim().ToLowerInvariant() + "`n")
|
||
}
|
||
|
||
function Test-DockerAvailable {
|
||
try {
|
||
$version = & docker version --format '{{.Server.Version}}' 2>$null
|
||
if (-not $version) { return @{ Available = $false; Reason = 'Docker Engine ist nicht erreichbar.' } }
|
||
& docker compose version 1>$null 2>$null
|
||
if ($LASTEXITCODE -ne 0) { return @{ Available = $false; Reason = 'Das Docker-Compose-Plugin fehlt.' } }
|
||
return @{ Available = $true; Version = $version; Reason = '' }
|
||
} catch {
|
||
return @{ Available = $false; Reason = 'Docker Desktop bzw. Docker Engine wurde nicht gefunden.' }
|
||
}
|
||
}
|
||
|
||
function Get-SecureSetupToken {
|
||
$bytes = New-Object byte[] 32
|
||
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
|
||
try { $rng.GetBytes($bytes) } finally { $rng.Dispose() }
|
||
return (($bytes | ForEach-Object { $_.ToString('x2') }) -join '')
|
||
}
|
||
|
||
function Set-VendooEnvValue([string]$Key, [string]$Value) {
|
||
$envFile = Join-Path $VendooDir '.env'
|
||
if (-not (Test-Path -LiteralPath $envFile)) { return }
|
||
$content = Get-Content -LiteralPath $envFile -Raw
|
||
$escaped = [regex]::Escape($Key)
|
||
if ($content -match "(?m)^$escaped=") {
|
||
$content = [regex]::Replace($content, "(?m)^$escaped=.*$", "$Key=$Value")
|
||
} else {
|
||
$content = $content.TrimEnd() + "`r`n$Key=$Value`r`n"
|
||
}
|
||
Write-Utf8NoBomFile -Path $envFile -Content $content
|
||
}
|
||
|
||
function Get-VendooEnvValue([string]$Key) {
|
||
$envFile = Join-Path $VendooDir '.env'
|
||
if (-not (Test-Path -LiteralPath $envFile)) { return '' }
|
||
$line = Get-Content -LiteralPath $envFile | Where-Object { $_ -match ('^' + [regex]::Escape($Key) + '=') } | Select-Object -Last 1
|
||
if (-not $line) { return '' }
|
||
return ($line -replace ('^' + [regex]::Escape($Key) + '='), '').Trim()
|
||
}
|
||
|
||
function Initialize-VendooEnvironment([string]$Mode = 'docker') {
|
||
$envFile = Join-Path $VendooDir '.env'
|
||
$envExample = Join-Path $VendooDir '.env.example'
|
||
if (-not (Test-Path -LiteralPath $envFile)) {
|
||
if (-not (Test-Path -LiteralPath $envExample)) { throw '.env.example fehlt.' }
|
||
Copy-Item -LiteralPath $envExample -Destination $envFile
|
||
Write-OK '.env aus sicherer Vorlage erstellt'
|
||
}
|
||
if ([string]::IsNullOrWhiteSpace((Get-VendooEnvValue 'VENDOO_SETUP_TOKEN'))) {
|
||
$token = Get-SecureSetupToken
|
||
Set-VendooEnvValue 'VENDOO_SETUP_TOKEN' $token
|
||
Write-Host ''
|
||
Write-Host ' ERST-ADMIN-SETUP-CODE:' -ForegroundColor Yellow
|
||
Write-Host " $token" -ForegroundColor White
|
||
Write-Warn 'Diesen Code jetzt sicher notieren. Er wird bei der ersten Einrichtung benötigt.'
|
||
}
|
||
if ($Mode -eq 'docker') {
|
||
if ([string]::IsNullOrWhiteSpace((Get-VendooEnvValue 'VENDOO_PUBLISH_ADDRESS'))) { Set-VendooEnvValue 'VENDOO_PUBLISH_ADDRESS' '127.0.0.1' }
|
||
Set-VendooEnvValue 'VENDOO_BIND_HOST' '0.0.0.0'
|
||
Set-VendooEnvValue 'LOCAL_IMAGE_URL' 'http://host.docker.internal:8188'
|
||
} else {
|
||
if ([string]::IsNullOrWhiteSpace((Get-VendooEnvValue 'VENDOO_BIND_HOST'))) { Set-VendooEnvValue 'VENDOO_BIND_HOST' '127.0.0.1' }
|
||
Set-VendooEnvValue 'LOCAL_IMAGE_URL' 'http://127.0.0.1:8188'
|
||
}
|
||
Set-VendooEnvValue 'VENDOO_COOKIE_SECURE' 'auto'
|
||
Set-VendooEnvValue 'VENDOO_REQUIRE_SETUP_TOKEN' 'true'
|
||
}
|
||
|
||
function Invoke-VendooDockerCompose([string[]]$Arguments) {
|
||
Push-Location $VendooDir
|
||
try {
|
||
$composeArgs = @('compose', '-f', (Join-Path $VendooDir 'compose.yaml')) + $Arguments
|
||
& docker @composeArgs
|
||
return $LASTEXITCODE
|
||
} finally { Pop-Location }
|
||
}
|
||
|
||
function Install-VendooDocker {
|
||
Write-Header
|
||
Write-Host ' Docker-Installation auf diesem Windows-PC ...' -ForegroundColor White
|
||
Write-Host ''
|
||
$docker = Test-DockerAvailable
|
||
if (-not $docker.Available) {
|
||
Write-Err $docker.Reason
|
||
Write-Host ' Installiere bzw. starte Docker Desktop und führe den Installer erneut aus.' -ForegroundColor Yellow
|
||
Write-Host ' Docker Desktop enthält Docker Engine, CLI und Docker Compose.' -ForegroundColor DarkGray
|
||
return
|
||
}
|
||
Write-OK "Docker Engine $($docker.Version) erreichbar"
|
||
Initialize-VendooEnvironment -Mode 'docker'
|
||
Set-VendooInstallMode 'docker'
|
||
Write-Step '1/3' 'Compose-Konfiguration prüfen ...'
|
||
if ((Invoke-VendooDockerCompose @('config')) -ne 0) { Write-Err 'Compose-Konfiguration ist ungültig.'; return }
|
||
Write-Step '2/3' 'Vendoo-Container bauen ...'
|
||
if ((Invoke-VendooDockerCompose @('build', '--pull')) -ne 0) { Write-Err 'Docker-Build fehlgeschlagen.'; return }
|
||
Write-Step '3/3' 'Vendoo starten ...'
|
||
if ((Invoke-VendooDockerCompose @('up', '-d', '--remove-orphans')) -ne 0) { Write-Err 'Containerstart fehlgeschlagen.'; return }
|
||
Write-OK 'Vendoo läuft im Docker-Modus.'
|
||
Write-Host " Readiness: http://127.0.0.1:$Port/readyz" -ForegroundColor Cyan
|
||
Start-Process "http://127.0.0.1:$Port/login.html"
|
||
}
|
||
|
||
function Show-VendooRemoteDeploymentGuide([string]$Target) {
|
||
Write-Header
|
||
Write-Host " $Target-Installation" -ForegroundColor White
|
||
Write-Host ''
|
||
Write-Host ' Ein VPS oder NAS wird nicht unsicher vom Windows-PC ferninstalliert.' -ForegroundColor Yellow
|
||
Write-Host ' Kopiere den Vendoo-Ordner auf das Zielsystem und starte dort:' -ForegroundColor White
|
||
Write-Host ''
|
||
Write-Host ' chmod +x setup.sh' -ForegroundColor Cyan
|
||
Write-Host ' ./setup.sh' -ForegroundColor Cyan
|
||
Write-Host ''
|
||
Write-Host ' Der Linux-Assistent bietet Docker, NAS/Portainer und VPS hinter HTTPS-Reverse-Proxy an.' -ForegroundColor DarkGray
|
||
Write-Host ' Datenbank, Uploads, .env, Backups und Operations bleiben persistent und werden nicht gelöscht.' -ForegroundColor DarkGray
|
||
}
|
||
|
||
function Configure-VendooConsole {
|
||
Write-Header
|
||
Write-Host ' Geführte Konfiguration – keine Datei muss manuell bearbeitet werden.' -ForegroundColor White
|
||
Write-Host ''
|
||
$mode = Get-VendooInstallMode
|
||
$portInput = Read-Host " Port [$Port]"
|
||
if (-not [string]::IsNullOrWhiteSpace($portInput)) {
|
||
$parsed = 0
|
||
if (-not [int]::TryParse($portInput, [ref]$parsed) -or $parsed -lt 1024 -or $parsed -gt 65535) { Write-Err 'Port muss zwischen 1024 und 65535 liegen.'; return }
|
||
$script:Port = $parsed
|
||
}
|
||
Initialize-VendooEnvironment -Mode ($(if ($mode -eq 'docker') { 'docker' } else { 'windows-local' }))
|
||
Set-VendooEnvValue 'PORT' ([string]$Port)
|
||
Set-VendooEnvValue 'VENDOO_HOST_PORT' ([string]$Port)
|
||
$lan = Read-Host ' Zugriff im privaten LAN erlauben? (ja/nein)'
|
||
if ($mode -eq 'docker') {
|
||
Set-VendooEnvValue 'VENDOO_PUBLISH_ADDRESS' ($(if ($lan -eq 'ja') { '0.0.0.0' } else { '127.0.0.1' }))
|
||
} else {
|
||
Set-VendooEnvValue 'VENDOO_BIND_HOST' ($(if ($lan -eq 'ja') { '0.0.0.0' } else { '127.0.0.1' }))
|
||
}
|
||
if ($lan -eq 'ja') {
|
||
$baseUrl = Read-Host ' LAN-Basisadresse für QR-Uploads (optional, z. B. http://192.168.1.20:8124)'
|
||
if (-not [string]::IsNullOrWhiteSpace($baseUrl)) {
|
||
if ($baseUrl -notmatch '^https?://') { Write-Err 'Adresse muss mit http:// oder https:// beginnen.'; return }
|
||
Set-VendooEnvValue 'PUBLIC_BASE_URL' $baseUrl.Trim().TrimEnd('/')
|
||
}
|
||
}
|
||
$flux = Read-Host ' Lokales FLUX aktivieren? (ja/nein)'
|
||
Set-VendooEnvValue 'LOCAL_IMAGE_ENABLED' ($(if ($flux -eq 'ja') { 'true' } else { 'false' }))
|
||
$token = Get-VendooEnvValue 'VENDOO_SETUP_TOKEN'
|
||
try { Set-Clipboard -Value $token -ErrorAction Stop; Write-OK 'Erst-Admin-Code wurde in die Zwischenablage kopiert.' } catch {}
|
||
Write-OK 'Konfiguration gespeichert.'
|
||
Write-Host ' AI- und Marktplatz-Schlüssel werden später in Vendoo → Einstellungen gepflegt.' -ForegroundColor DarkGray
|
||
}
|
||
|
||
function Install-Vendoo {
|
||
Write-Header
|
||
Write-Host ' Wo soll Vendoo betrieben werden?' -ForegroundColor White
|
||
Write-Host ''
|
||
Write-Host ' 1 Lokal auf diesem Windows-PC (Node.js)' -ForegroundColor Cyan
|
||
Write-Host ' 2 Docker Desktop auf diesem Windows-PC' -ForegroundColor Cyan
|
||
Write-Host ' 3 NAS / Portainer / Docker-Server' -ForegroundColor Cyan
|
||
Write-Host ' 4 VPS / öffentlicher Linux-Server' -ForegroundColor Cyan
|
||
Write-Host ' 0 Abbrechen' -ForegroundColor DarkGray
|
||
Write-Host ''
|
||
$choice = Read-Host ' Installationsziel'
|
||
switch ($choice) {
|
||
'1' { Set-VendooInstallMode 'windows-local'; Install-VendooLocal }
|
||
'2' { Install-VendooDocker }
|
||
'3' { Show-VendooRemoteDeploymentGuide 'NAS / Portainer' }
|
||
'4' { Show-VendooRemoteDeploymentGuide 'VPS / Linux' }
|
||
'0' { return }
|
||
default { Write-Warn 'Ungültiges Installationsziel.' }
|
||
}
|
||
}
|
||
|
||
function Start-Vendoo {
|
||
if ((Get-VendooInstallMode) -eq 'docker') {
|
||
$docker = Test-DockerAvailable
|
||
if (-not $docker.Available) { Write-Err $docker.Reason; return }
|
||
if ((Invoke-VendooDockerCompose @('up', '-d')) -eq 0) {
|
||
Write-OK 'Vendoo-Container gestartet.'
|
||
Start-Process "http://127.0.0.1:$Port/"
|
||
}
|
||
return
|
||
}
|
||
Start-VendooLocal
|
||
}
|
||
|
||
function Update-Vendoo {
|
||
if ((Get-VendooInstallMode) -eq 'docker') {
|
||
$docker = Test-DockerAvailable
|
||
if (-not $docker.Available) { Write-Err $docker.Reason; return }
|
||
Initialize-VendooEnvironment -Mode 'docker'
|
||
if ((Invoke-VendooDockerCompose @('config')) -ne 0) { Write-Err 'Compose-Konfiguration ist ungültig.'; return }
|
||
if ((Invoke-VendooDockerCompose @('build', '--pull')) -ne 0) { Write-Err 'Docker-Update-Build fehlgeschlagen.'; return }
|
||
if ((Invoke-VendooDockerCompose @('up', '-d', '--remove-orphans')) -ne 0) { Write-Err 'Docker-Update konnte nicht gestartet werden.'; return }
|
||
Write-OK 'Docker-Update abgeschlossen.'
|
||
return
|
||
}
|
||
Update-VendooLocal
|
||
}
|
||
|
||
function Repair-Vendoo {
|
||
if ((Get-VendooInstallMode) -eq 'docker') {
|
||
$docker = Test-DockerAvailable
|
||
if (-not $docker.Available) { Write-Err $docker.Reason; return }
|
||
if ((Invoke-VendooDockerCompose @('config')) -ne 0) { Write-Err 'Compose-Konfiguration ist ungültig.'; return }
|
||
if ((Invoke-VendooDockerCompose @('build', '--no-cache')) -ne 0) { Write-Err 'Docker-Reparatur-Build fehlgeschlagen.'; return }
|
||
if ((Invoke-VendooDockerCompose @('up', '-d', '--force-recreate', '--remove-orphans')) -ne 0) { Write-Err 'Docker-Reparaturstart fehlgeschlagen.'; return }
|
||
Write-OK 'Docker-Container neu gebaut und gestartet.'
|
||
return
|
||
}
|
||
Repair-VendooLocal
|
||
}
|
||
|
||
function Install-VendooLocal {
|
||
Write-Header
|
||
Write-Host ' Installation starten...' -ForegroundColor White
|
||
Write-Host ''
|
||
|
||
$installState = Get-VendooInstallState
|
||
if ($installState.State -eq 'installed') {
|
||
Write-Warn 'Vendoo scheint bereits installiert zu sein.'
|
||
Write-Host ' Statt versehentlich neu zu installieren, nutze besser Starten, Update oder Reparieren.' -ForegroundColor Yellow
|
||
$choice = Read-Host ' Aktion: [S]tarten / [U]pdate / [R]eparieren / [N]eu installieren / [A]bbrechen'
|
||
if ([string]::IsNullOrWhiteSpace($choice)) {
|
||
$choice = ''
|
||
}
|
||
$choice = $choice.ToLowerInvariant()
|
||
switch ($choice) {
|
||
's' { Start-VendooLocal; return }
|
||
'u' { Update-VendooLocal; return }
|
||
'r' { Repair-VendooLocal; return }
|
||
'a' { return }
|
||
'n' { Write-Host ' Vollständige Neuinstallation wird fortgesetzt...' -ForegroundColor DarkGray }
|
||
default {
|
||
Write-Warn 'Keine gültige Aktion gewählt. Installation wurde abgebrochen.'
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
# 1. Node.js pruefen
|
||
Write-Step '1/5' 'Node.js pruefen...'
|
||
$node = Test-NodeInstalled
|
||
if (-not $node.Installed) {
|
||
Write-Err 'Node.js ist nicht installiert!'
|
||
Write-Host ''
|
||
Write-Host ' Node.js wird benoetigt (mindestens v18).' -ForegroundColor Yellow
|
||
Write-Host ' Download: https://nodejs.org/' -ForegroundColor Cyan
|
||
Write-Host ''
|
||
$dl = Read-Host ' Node.js jetzt automatisch installieren? (j/n)'
|
||
if ($dl -eq 'j') {
|
||
Write-Step '...' 'Lade Node.js Installer herunter...'
|
||
$installerUrl = 'https://nodejs.org/dist/v22.16.0/node-v22.16.0-x64.msi'
|
||
$installerPath = Join-Path $env:TEMP 'node-installer.msi'
|
||
try {
|
||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||
Invoke-WebRequest -Uri $installerUrl -OutFile $installerPath -UseBasicParsing
|
||
Write-Step '...' 'Installiere Node.js...'
|
||
Start-Process msiexec.exe -ArgumentList "/i `"$installerPath`" /qn" -Wait -NoNewWindow
|
||
$env:Path = [System.Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path', 'User')
|
||
Start-Sleep -Seconds 2
|
||
$node = Test-NodeInstalled
|
||
if ($node.Installed) {
|
||
Write-OK "Node.js $($node.Version) installiert"
|
||
} else {
|
||
Write-Err 'Installation fehlgeschlagen. Bitte manuell installieren.'
|
||
return
|
||
}
|
||
} catch {
|
||
Write-Err "Download fehlgeschlagen: $_"
|
||
Write-Host ' Bitte installiere Node.js manuell: https://nodejs.org/' -ForegroundColor Yellow
|
||
return
|
||
}
|
||
} else {
|
||
Write-Host ' Installation abgebrochen.' -ForegroundColor Yellow
|
||
return
|
||
}
|
||
} elseif ($node.Major -lt 18) {
|
||
Write-Warn "Node.js $($node.Version) ist zu alt. Mindestens v18 erforderlich."
|
||
Write-Host ' Update: https://nodejs.org/' -ForegroundColor Cyan
|
||
return
|
||
} else {
|
||
Write-OK "Node.js $($node.Version) gefunden"
|
||
}
|
||
|
||
# 2. npm pruefen
|
||
Write-Step '2/5' 'npm pruefen...'
|
||
if (Test-NpmInstalled) {
|
||
$npmV = & npm --version 2>$null
|
||
Write-OK "npm v$npmV gefunden"
|
||
} else {
|
||
Write-Err 'npm nicht gefunden. Wird mit Node.js mitgeliefert.'
|
||
return
|
||
}
|
||
|
||
# 3. Dependencies reproduzierbar installieren
|
||
Write-Step '3/5' 'Dependencies installieren (npm ci)...'
|
||
if (-not (Invoke-VendooNpmInstall)) { return }
|
||
|
||
# 4. Sichere Konfiguration erzeugen bzw. übernehmen
|
||
Write-Step '4/5' 'Sichere Konfiguration prüfen...'
|
||
Initialize-VendooEnvironment -Mode 'windows-local'
|
||
Write-OK 'Konfiguration ist vollständig; keine manuelle Dateibearbeitung erforderlich'
|
||
|
||
# 5. Ordner pruefen
|
||
Write-Step '5/5' 'Verzeichnisse pruefen...'
|
||
$dirs = @('uploads', 'db')
|
||
foreach ($d in $dirs) {
|
||
$p = Join-Path $VendooDir $d
|
||
if (-not (Test-Path $p)) {
|
||
New-Item -ItemType Directory -Path $p -Force | Out-Null
|
||
}
|
||
}
|
||
Write-OK 'Verzeichnisse OK'
|
||
|
||
Write-Host ''
|
||
Write-Host ' ========================================' -ForegroundColor Green
|
||
Write-Host ' Installation abgeschlossen!' -ForegroundColor Green
|
||
Write-Host ' ========================================' -ForegroundColor Green
|
||
Write-Host ''
|
||
Write-Host " Starte Vendoo mit: " -NoNewline
|
||
Write-Host 'npm start' -ForegroundColor Cyan
|
||
Write-Host " Oder im Dev-Modus: " -NoNewline
|
||
Write-Host 'npm run dev' -ForegroundColor Cyan
|
||
Write-Host " Dann oeffne: " -NoNewline
|
||
Write-Host "http://localhost:$Port" -ForegroundColor Cyan
|
||
Write-Host ''
|
||
|
||
if ($script:AutoStartAfterInstall) {
|
||
Start-VendooLocalDetached
|
||
return
|
||
}
|
||
$start = Read-Host ' Vendoo jetzt starten? (j/n)'
|
||
if ($start -eq 'j') {
|
||
Start-VendooLocal
|
||
}
|
||
}
|
||
|
||
function Start-VendooLocalDetached {
|
||
Write-Step '...' 'Starte Vendoo im eigenen Prozess ...'
|
||
Start-Process -FilePath 'node.exe' -ArgumentList @('bootstrap.mjs') -WorkingDirectory $VendooDir -WindowStyle Minimized
|
||
Start-Sleep -Seconds 2
|
||
Write-OK 'Vendoo wurde gestartet.'
|
||
}
|
||
|
||
function Start-VendooLocal {
|
||
Write-Host ''
|
||
Write-Step '...' 'Starte Vendoo-Server...'
|
||
Push-Location $VendooDir
|
||
Start-Process "http://localhost:$Port/?build=1.34.3"
|
||
& node bootstrap.mjs
|
||
Pop-Location
|
||
}
|
||
|
||
function Update-VendooLocal {
|
||
Write-Header
|
||
Write-Host ' Update durchfuehren...' -ForegroundColor White
|
||
Write-Host ''
|
||
|
||
# Dependencies reproduzierbar aktualisieren
|
||
Write-Step '1/2' 'Dependencies aktualisieren...'
|
||
if (-not (Invoke-VendooNpmInstall)) { return }
|
||
|
||
# Neue .env Eintraege pruefen
|
||
Write-Step '2/2' '.env pruefen...'
|
||
$envFile = Join-Path $VendooDir '.env'
|
||
$envExample = Join-Path $VendooDir '.env.example'
|
||
if ((Test-Path $envFile) -and (Test-Path $envExample)) {
|
||
$current = Get-Content $envFile -Raw
|
||
$example = Get-Content $envExample
|
||
$added = 0
|
||
foreach ($line in $example) {
|
||
if ($line -match '^([A-Z_]+)=' -and $current -notmatch "(?m)^$($Matches[1])=") {
|
||
Add-Content $envFile "`n$line"
|
||
$added++
|
||
}
|
||
}
|
||
if ($added -gt 0) {
|
||
Write-OK "$added neue Konfigurationseintraege hinzugefuegt"
|
||
} else {
|
||
Write-OK '.env ist aktuell'
|
||
}
|
||
}
|
||
|
||
Write-Host ''
|
||
Write-Host ' Update abgeschlossen!' -ForegroundColor Green
|
||
Write-Host ''
|
||
}
|
||
|
||
function Repair-VendooLocal {
|
||
Write-Header
|
||
Write-Host ' Reparatur durchfuehren...' -ForegroundColor White
|
||
Write-Host ''
|
||
|
||
# 1. node_modules robust loeschen und reproduzierbar neu installieren
|
||
Write-Step '1/3' 'node_modules neu installieren...'
|
||
if (-not (Invoke-VendooNpmInstall -ForceClean)) { return }
|
||
|
||
# 2. .env pruefen
|
||
Write-Step '2/3' '.env pruefen...'
|
||
$envFile = Join-Path $VendooDir '.env'
|
||
if (-not (Test-Path $envFile)) {
|
||
$envExample = Join-Path $VendooDir '.env.example'
|
||
if (Test-Path $envExample) { Copy-Item $envExample $envFile }
|
||
Write-Warn '.env fehlte und wurde neu erstellt'
|
||
} else {
|
||
Write-OK '.env vorhanden'
|
||
}
|
||
|
||
# 3. Ordner pruefen
|
||
Write-Step '3/3' 'Verzeichnisse pruefen...'
|
||
foreach ($d in @('uploads', 'db')) {
|
||
$p = Join-Path $VendooDir $d
|
||
if (-not (Test-Path $p)) { New-Item -ItemType Directory -Path $p -Force | Out-Null }
|
||
}
|
||
Write-OK 'Verzeichnisse OK'
|
||
|
||
Write-Host ''
|
||
Write-Host ' Reparatur abgeschlossen!' -ForegroundColor Green
|
||
Write-Host ''
|
||
}
|
||
|
||
function Uninstall-VendooLocal {
|
||
Write-Header
|
||
Write-Host ' Vendoo deinstallieren' -ForegroundColor Red
|
||
Write-Host ''
|
||
Write-Warn 'Dies entfernt node_modules und optionale Daten.'
|
||
Write-Host ''
|
||
|
||
$confirm = Read-Host ' Wirklich deinstallieren? (ja/nein)'
|
||
if ($confirm -ne 'ja') {
|
||
Write-Host ' Abgebrochen.' -ForegroundColor Yellow
|
||
return
|
||
}
|
||
|
||
$keepData = Read-Host ' Datenbank und Uploads behalten? (j/n)'
|
||
|
||
Write-Step '1/2' 'node_modules entfernen...'
|
||
$nm = Join-Path $VendooDir 'node_modules'
|
||
if (Test-Path $nm) {
|
||
Remove-Item $nm -Recurse -Force -Confirm:$false
|
||
Write-OK 'node_modules entfernt'
|
||
} else {
|
||
Write-OK 'node_modules nicht vorhanden'
|
||
}
|
||
|
||
if ($keepData -ne 'j') {
|
||
Write-Step '2/2' 'Daten entfernen...'
|
||
$dbDir = Join-Path $VendooDir 'db'
|
||
$uploadsDir = Join-Path $VendooDir 'uploads'
|
||
if (Test-Path $dbDir) {
|
||
Remove-Item (Join-Path $dbDir '*.db'), (Join-Path $dbDir '*.db-shm'), (Join-Path $dbDir '*.db-wal') -Force -ErrorAction SilentlyContinue
|
||
Write-OK 'Datenbank entfernt'
|
||
}
|
||
if (Test-Path $uploadsDir) {
|
||
Get-ChildItem $uploadsDir -File | Remove-Item -Force -Confirm:$false
|
||
Write-OK 'Uploads entfernt'
|
||
}
|
||
$envFile = Join-Path $VendooDir '.env'
|
||
if (Test-Path $envFile) {
|
||
Remove-Item $envFile -Force -Confirm:$false
|
||
Write-OK '.env entfernt'
|
||
}
|
||
} else {
|
||
Write-OK 'Datenbank und Uploads beibehalten'
|
||
}
|
||
|
||
Write-Host ''
|
||
Write-Host ' Vendoo wurde deinstalliert.' -ForegroundColor Green
|
||
Write-Host ' Die Projektdateien bleiben erhalten.' -ForegroundColor DarkGray
|
||
Write-Host ' Zum erneuten Installieren: setup.bat ausfuehren.' -ForegroundColor DarkGray
|
||
Write-Host ''
|
||
}
|
||
|
||
function Install-LocalFlux {
|
||
Write-Header
|
||
Write-Host ' Lokales FLUX / ComfyUI installieren' -ForegroundColor White
|
||
Write-Host ''
|
||
$script = Get-LocalFluxScriptPath
|
||
if (-not (Test-Path $script)) {
|
||
Write-Err 'Installationsskript fuer lokales FLUX wurde nicht gefunden.'
|
||
return
|
||
}
|
||
|
||
$stateFile = Join-Path $VendooDir 'local-ai\install-state.json'
|
||
$existingInstall = Test-Path (Join-Path $VendooDir 'local-ai\comfyui\ComfyUI_windows_portable\ComfyUI\main.py')
|
||
if ($existingInstall) {
|
||
Write-OK 'ComfyUI ist bereits installiert.'
|
||
$existingChoice = Read-Host ' [S]tatus / [U]pdate / [M]odell nachinstallieren / [N]eu installieren / [A]bbrechen'
|
||
if ([string]::IsNullOrWhiteSpace($existingChoice)) { return }
|
||
$existingChoice = $existingChoice.ToLowerInvariant()
|
||
switch ($existingChoice) {
|
||
's' { Show-LocalFluxStatus; return }
|
||
'u' {
|
||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $script -UpdateOnly -StartNow
|
||
return
|
||
}
|
||
'm' {
|
||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $script -InstallModel -StartNow
|
||
return
|
||
}
|
||
'n' { }
|
||
default { return }
|
||
}
|
||
}
|
||
|
||
Write-Host ' Hinweis: Das FLUX-Modell ist sehr gross und kann lange herunterladen.' -ForegroundColor Yellow
|
||
$modelChoice = Read-Host ' FLUX.1-schnell FP8 direkt mitinstallieren? (j/n)'
|
||
$autoChoice = Read-Host ' Lokalen Bildserver bei Windows-Anmeldung automatisch starten? (j/n)'
|
||
$startChoice = Read-Host ' Nach Installation sofort starten? (j/n)'
|
||
|
||
$arguments = @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $script)
|
||
if ($modelChoice -eq 'j') { $arguments += '-InstallModel' }
|
||
if ($autoChoice -eq 'j') { $arguments += '-EnableAutostart' }
|
||
if ($startChoice -eq 'j') { $arguments += '-StartNow' }
|
||
if ($existingInstall) { $arguments += '-Force' }
|
||
|
||
Write-Step '1/1' 'Starte automatischen lokalen FLUX-Installer...'
|
||
& powershell.exe @arguments
|
||
if ($LASTEXITCODE -eq 0) {
|
||
Write-OK 'Lokales FLUX / ComfyUI wurde eingerichtet.'
|
||
} else {
|
||
Write-Err 'Lokale FLUX-Installation ist fehlgeschlagen.'
|
||
if (Test-Path -LiteralPath $stateFile) {
|
||
try {
|
||
$failedState = Get-Content -LiteralPath $stateFile -Raw | ConvertFrom-Json
|
||
Write-Host " Phase: $($failedState.phase)" -ForegroundColor Yellow
|
||
Write-Host " Fortschritt: $($failedState.progress)%" -ForegroundColor Yellow
|
||
Write-Host " Fehler: $($failedState.message)" -ForegroundColor Red
|
||
} catch {}
|
||
Write-Host " Statusdatei: $stateFile" -ForegroundColor Yellow
|
||
}
|
||
Write-Host " Log: $(Join-Path $VendooDir 'logs\local-flux-install.log')" -ForegroundColor Yellow
|
||
Write-Host ' Option 7 kann nach dem Fix erneut ausgeführt werden; vorhandene vollständige Downloads werden weiterverwendet.' -ForegroundColor DarkGray
|
||
}
|
||
Write-Host ''
|
||
}
|
||
|
||
|
||
function Get-LocalFluxStartupRecovery {
|
||
$stateFile = Join-Path $VendooDir 'local-ai\install-state.json'
|
||
$extractRoot = Join-Path $VendooDir 'local-ai\extract-temp'
|
||
$stdoutFile = Join-Path $VendooDir 'logs\local-flux-7zip-output.log'
|
||
$stderrFile = Join-Path $VendooDir 'logs\local-flux-7zip-error.log'
|
||
$portableRoot = Join-Path $VendooDir 'local-ai\comfyui\ComfyUI_windows_portable'
|
||
$finalMain = Join-Path $portableRoot 'ComfyUI\main.py'
|
||
$embeddedPython = Join-Path $portableRoot 'python_embeded\python.exe'
|
||
$modelPath = Join-Path $portableRoot 'ComfyUI\models\checkpoints\flux1-schnell-fp8.safetensors'
|
||
$partialModel = "$modelPath.part"
|
||
|
||
if (-not (Test-Path -LiteralPath $stateFile)) { return $null }
|
||
|
||
try {
|
||
$state = Get-Content -LiteralPath $stateFile -Raw | ConvertFrom-Json
|
||
} catch {
|
||
return $null
|
||
}
|
||
|
||
try {
|
||
$active = @(Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
|
||
Where-Object {
|
||
$_.CommandLine -and
|
||
$_.CommandLine -match [regex]::Escape($VendooDir) -and
|
||
($_.Name -match '^(7z|7zG|powershell)\.exe$') -and
|
||
$_.CommandLine -match 'install-local-flux|extract-temp|ComfyUI_windows_portable|flux1-schnell'
|
||
})
|
||
if ($active.Count -gt 0) { return $null }
|
||
} catch {}
|
||
|
||
# Bekannter Slice-22-Fehlalarm nach erfolgreicher 7-Zip-Entpackung.
|
||
if ((Test-Path -LiteralPath $extractRoot) -and -not (Test-Path -LiteralPath $finalMain)) {
|
||
$knownFalseFailure = (
|
||
$state.status -eq 'failed' -and
|
||
[bool]$state.can_resume -and
|
||
[string]$state.message -match '7-Zip Exit-Code\s*\)' -and
|
||
[string]$state.message -match 'Everything is Ok'
|
||
)
|
||
|
||
if ($knownFalseFailure) {
|
||
$stderrText = ''
|
||
if (Test-Path -LiteralPath $stderrFile) {
|
||
try { $stderrText = (Get-Content -LiteralPath $stderrFile -Raw -ErrorAction Stop).Trim() } catch {}
|
||
}
|
||
|
||
$stdoutText = ''
|
||
if (Test-Path -LiteralPath $stdoutFile) {
|
||
try { $stdoutText = Get-Content -LiteralPath $stdoutFile -Raw -ErrorAction Stop } catch {}
|
||
}
|
||
|
||
if ([string]::IsNullOrWhiteSpace($stderrText) -and $stdoutText -match '(?im)^Everything is Ok\s*$') {
|
||
$portable = Get-ChildItem -LiteralPath $extractRoot -Directory -Recurse -ErrorAction SilentlyContinue |
|
||
Where-Object {
|
||
$_.Name -match '^ComfyUI_windows_portable' -and
|
||
(Test-Path -LiteralPath (Join-Path $_.FullName 'ComfyUI\main.py')) -and
|
||
(Test-Path -LiteralPath (Join-Path $_.FullName 'python_embeded\python.exe'))
|
||
} |
|
||
Select-Object -First 1
|
||
|
||
if ($portable) {
|
||
return @{
|
||
Mode = 'extract'
|
||
State = $state
|
||
PortablePath = $portable.FullName
|
||
ExtractRoot = $extractRoot
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
# Unterbrochener oder scheinbar hängender Modell-Download.
|
||
$comfyReady = (Test-Path -LiteralPath $finalMain) -and (Test-Path -LiteralPath $embeddedPython)
|
||
$modelReady = $false
|
||
if (Test-Path -LiteralPath $modelPath) {
|
||
try { $modelReady = (Get-Item -LiteralPath $modelPath).Length -ge 1GB } catch {}
|
||
}
|
||
|
||
$partialBytes = 0L
|
||
if (Test-Path -LiteralPath $partialModel) {
|
||
try { $partialBytes = [long](Get-Item -LiteralPath $partialModel).Length } catch {}
|
||
}
|
||
|
||
$downloadState = ([string]$state.phase -eq 'download-model') -and ([string]$state.status -in @('running','failed'))
|
||
if ($comfyReady -and -not $modelReady -and ($downloadState -or $partialBytes -gt 0)) {
|
||
return @{
|
||
Mode = 'model-download'
|
||
State = $state
|
||
PartialPath = $partialModel
|
||
PartialBytes = $partialBytes
|
||
}
|
||
}
|
||
|
||
return $null
|
||
}
|
||
|
||
function Invoke-LocalFluxStartupRecovery {
|
||
$recovery = Get-LocalFluxStartupRecovery
|
||
if (-not $recovery) { return }
|
||
|
||
Write-Header
|
||
Write-Host ' Automatische FLUX-Wiederaufnahme' -ForegroundColor White
|
||
Write-Host ' ────────────────────────────────────' -ForegroundColor DarkGray
|
||
Write-Host ''
|
||
|
||
if ($recovery.Mode -eq 'extract') {
|
||
Write-OK 'Das ComfyUI-Archiv wurde bereits vollständig und fehlerfrei entpackt.'
|
||
Write-Warn 'Der frühere Installer hat den erfolgreichen 7-Zip-Lauf falsch bewertet.'
|
||
Write-Host ' Der vorhandene Entpackstand wird übernommen; nichts wird neu entpackt.' -ForegroundColor Green
|
||
} elseif ($recovery.Mode -eq 'model-download') {
|
||
$partialMb = [math]::Round(([long]$recovery.PartialBytes) / 1MB, 1)
|
||
Write-Warn 'Ein unterbrochener oder nicht sichtbar fortschreitender FLUX-Download wurde erkannt.'
|
||
if ($partialMb -gt 0) {
|
||
Write-OK "Bereits geladene Daten bleiben erhalten: $partialMb MB."
|
||
}
|
||
Write-Host ' Der Download wird mit sichtbarem Fortschritt automatisch fortgesetzt.' -ForegroundColor Green
|
||
Write-Host ' Anzeige: Prozent · geladene MB · Gesamtgröße · Geschwindigkeit · Restzeit' -ForegroundColor DarkGray
|
||
}
|
||
|
||
Write-Host ''
|
||
Write-Step 'AUTO' 'Fortsetzung startet automatisch...'
|
||
Start-Sleep -Seconds 2
|
||
|
||
$script = Get-LocalFluxScriptPath
|
||
if (-not (Test-Path -LiteralPath $script)) {
|
||
Write-Err 'FLUX-Installationsskript fehlt. Automatische Fortsetzung ist nicht möglich.'
|
||
return
|
||
}
|
||
|
||
$logDir = Join-Path $VendooDir 'logs'
|
||
if (-not (Test-Path -LiteralPath $logDir)) {
|
||
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
|
||
}
|
||
$selfHealLog = Join-Path $logDir 'setup-self-heal.log'
|
||
"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') [INFO] Automatische FLUX-Wiederaufnahme gestartet. Modus: $($recovery.Mode)." |
|
||
Add-Content -LiteralPath $selfHealLog -Encoding utf8
|
||
|
||
& powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File $script -InstallModel -StartNow
|
||
$exitCode = $LASTEXITCODE
|
||
|
||
if ($exitCode -eq 0) {
|
||
"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') [OK] Automatische FLUX-Wiederaufnahme erfolgreich abgeschlossen." |
|
||
Add-Content -LiteralPath $selfHealLog -Encoding utf8
|
||
Write-OK 'ComfyUI und FLUX wurden automatisch fertig eingerichtet.'
|
||
Write-Host ''
|
||
Show-LocalFluxStatus
|
||
} else {
|
||
"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') [ERROR] Automatische FLUX-Wiederaufnahme fehlgeschlagen. Exit-Code $exitCode." |
|
||
Add-Content -LiteralPath $selfHealLog -Encoding utf8
|
||
Write-Err "Automatische Fortsetzung ist fehlgeschlagen (Exit-Code $exitCode)."
|
||
Write-Host " Diagnose: $selfHealLog" -ForegroundColor Yellow
|
||
Write-Host " Installer-Log: $(Join-Path $VendooDir 'logs\local-flux-install.log')" -ForegroundColor Yellow
|
||
}
|
||
|
||
Write-Host ''
|
||
}
|
||
|
||
function Start-LocalFlux {
|
||
$script = Join-Path $VendooDir 'local-ai\start-local-flux.ps1'
|
||
if (-not (Test-Path $script)) {
|
||
Write-Warn 'Startskript fehlt. Bitte lokales FLUX zuerst installieren.'
|
||
return
|
||
}
|
||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $script
|
||
Start-Sleep -Seconds 3
|
||
Show-LocalFluxStatus
|
||
}
|
||
|
||
function Stop-LocalFlux {
|
||
$script = Join-Path $VendooDir 'local-ai\stop-local-flux.ps1'
|
||
if (-not (Test-Path $script)) {
|
||
Write-Warn 'Stoppskript fehlt.'
|
||
return
|
||
}
|
||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $script
|
||
Write-OK 'Lokaler FLUX / ComfyUI-Prozess wurde beendet.'
|
||
}
|
||
|
||
function Show-LocalFluxStatus {
|
||
Write-Header
|
||
Write-Host ' Lokales FLUX / ComfyUI Status' -ForegroundColor White
|
||
Write-Host ' ────────────────────────────────────' -ForegroundColor DarkGray
|
||
|
||
$localRoot = Join-Path $VendooDir 'local-ai'
|
||
$stateFile = Join-Path $localRoot 'install-state.json'
|
||
$logFile = Join-Path $VendooDir 'logs\local-flux-install.log'
|
||
$portableRoot = Join-Path $localRoot 'comfyui\ComfyUI_windows_portable'
|
||
$pythonPath = Join-Path $portableRoot 'python_embeded\python.exe'
|
||
$comfyMain = Join-Path $portableRoot 'ComfyUI\main.py'
|
||
$modelPath = Join-Path $portableRoot 'ComfyUI\models\checkpoints\flux1-schnell-fp8.safetensors'
|
||
$workflowPath = Join-Path $localRoot 'workflows\vendoo_flux_schnell_api.json'
|
||
$startScript = Join-Path $localRoot 'start-local-flux.ps1'
|
||
$stopScript = Join-Path $localRoot 'stop-local-flux.ps1'
|
||
|
||
$state = $null
|
||
if (Test-Path -LiteralPath $stateFile) {
|
||
try { $state = Get-Content -LiteralPath $stateFile -Raw | ConvertFrom-Json } catch {}
|
||
}
|
||
|
||
if ($state) {
|
||
$statusColor = 'Yellow'
|
||
if ($state.status -eq 'completed') { $statusColor = 'Green' }
|
||
elseif ($state.status -eq 'failed') { $statusColor = 'Red' }
|
||
$heartbeatAge = $null
|
||
try {
|
||
$heartbeatTime = [DateTime]::Parse([string]$(if ($state.heartbeat_at) { $state.heartbeat_at } else { $state.updated_at }))
|
||
$heartbeatAge = [math]::Round(((Get-Date) - $heartbeatTime).TotalSeconds)
|
||
} catch {}
|
||
$staleThreshold = if ($state.phase -eq 'extract-comfyui') { 20 } else { 120 }
|
||
if ($state.status -eq 'running' -and $heartbeatAge -ne $null -and $heartbeatAge -gt $staleThreshold) {
|
||
$statusColor = 'Red'
|
||
Write-Host ' Status: HÄNGT / keine Aktualisierung' -ForegroundColor $statusColor
|
||
} else {
|
||
Write-Host " Status: $($state.status)" -ForegroundColor $statusColor
|
||
}
|
||
Write-Host " Phase: $($state.phase)" -ForegroundColor DarkGray
|
||
Write-Host " Fortschritt: $($state.progress)%" -ForegroundColor DarkGray
|
||
Write-Host " Meldung: $($state.message)" -ForegroundColor DarkGray
|
||
if ($state.elapsed_seconds -ne $null) { Write-Host " Laufzeit: $($state.elapsed_seconds) Sekunden" -ForegroundColor DarkGray }
|
||
if ($state.extract_files -ne $null) { Write-Host " Entpackt: $($state.extract_files) Dateien / $($state.extract_mb) MB" -ForegroundColor DarkGray }
|
||
if ($heartbeatAge -ne $null) { Write-Host " Heartbeat: vor $heartbeatAge Sekunden" -ForegroundColor DarkGray }
|
||
Write-Host " Aktualisiert:$($state.updated_at)" -ForegroundColor DarkGray
|
||
} else {
|
||
Write-Warn 'Noch keine Installationsstatusdatei vorhanden.'
|
||
}
|
||
|
||
Write-Host ''
|
||
if (Test-Path -LiteralPath "$env:ProgramFiles\7-Zip\7z.exe") { Write-OK '7-Zip Vorhanden' }
|
||
else { Write-Warn '7-Zip Nicht im Standardpfad gefunden' }
|
||
if (Test-Path -LiteralPath $pythonPath) { Write-OK 'Python Vorhanden' }
|
||
else { Write-Warn 'Python Fehlt' }
|
||
if (Test-Path -LiteralPath $comfyMain) { Write-OK 'ComfyUI Installiert' }
|
||
else { Write-Warn 'ComfyUI Noch nicht vollständig installiert' }
|
||
if (Test-Path -LiteralPath $workflowPath) { Write-OK 'Workflow Vorhanden' }
|
||
else { Write-Warn 'Workflow Fehlt' }
|
||
if (Test-Path -LiteralPath $modelPath) {
|
||
$modelSize = [math]::Round((Get-Item -LiteralPath $modelPath).Length / 1GB, 2)
|
||
Write-OK "FLUX-Modell Vorhanden ($modelSize GB)"
|
||
} else { Write-Warn 'FLUX-Modell Fehlt' }
|
||
if (Test-Path -LiteralPath $startScript) { Write-OK 'Startskript Vorhanden' }
|
||
else { Write-Warn 'Startskript Fehlt' }
|
||
if (Test-Path -LiteralPath $stopScript) { Write-OK 'Stoppskript Vorhanden' }
|
||
else { Write-Warn 'Stoppskript Fehlt' }
|
||
|
||
$envFile = Join-Path $VendooDir '.env'
|
||
$url = 'http://127.0.0.1:8188'
|
||
if (Test-Path -LiteralPath $envFile) {
|
||
$envContent = Get-Content -LiteralPath $envFile -Raw
|
||
if ($envContent -match '(?m)^LOCAL_IMAGE_URL=(.+)$') { $url = $Matches[1].Trim() }
|
||
}
|
||
$serverReachable = $false
|
||
try {
|
||
$resp = Invoke-WebRequest -UseBasicParsing -Uri ($url.TrimEnd('/') + '/system_stats') -TimeoutSec 5
|
||
if ($resp.StatusCode -ge 200 -and $resp.StatusCode -lt 300) {
|
||
$serverReachable = $true
|
||
Write-OK 'Server Erreichbar'
|
||
} else { Write-Warn "Server HTTP $($resp.StatusCode)" }
|
||
} catch {
|
||
Write-Warn 'Server Nicht erreichbar'
|
||
}
|
||
|
||
if (-not $serverReachable) {
|
||
$runtimeStateFile = Join-Path $VendooDir 'logs\local-flux-runtime-state.json'
|
||
if (Test-Path -LiteralPath $runtimeStateFile) {
|
||
try {
|
||
$runtimeState = Get-Content -LiteralPath $runtimeStateFile -Raw | ConvertFrom-Json
|
||
Write-Host " Runtime: $($runtimeState.status)" -ForegroundColor $(if ($runtimeState.status -eq 'failed') { 'Red' } elseif ($runtimeState.status -eq 'starting') { 'Yellow' } else { 'DarkGray' })
|
||
Write-Host " Startinfo: $($runtimeState.message)" -ForegroundColor DarkGray
|
||
if ($runtimeState.error) { Write-Host " Fehler: $($runtimeState.error)" -ForegroundColor Red }
|
||
} catch {}
|
||
}
|
||
foreach ($runtimeLog in @(
|
||
(Join-Path $VendooDir 'logs\local-flux-runtime-error.log'),
|
||
(Join-Path $VendooDir 'logs\local-flux-runtime.log')
|
||
)) {
|
||
if (Test-Path -LiteralPath $runtimeLog) {
|
||
$runtimeLines = @(Get-Content -LiteralPath $runtimeLog -Tail 8 -ErrorAction SilentlyContinue)
|
||
if ($runtimeLines.Count -gt 0) {
|
||
Write-Host " Runtime-Log: $runtimeLog" -ForegroundColor White
|
||
$runtimeLines | ForEach-Object {
|
||
$clean = [regex]::Replace([string]$_, ([string][char]27) + '\[[0-?]*[ -/]*[@-~]', '')
|
||
Write-Host " $clean" -ForegroundColor DarkGray
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (Test-Path -LiteralPath $logFile) {
|
||
Write-Host ''
|
||
Write-Host ' Letzte Installationsmeldungen:' -ForegroundColor White
|
||
Get-Content -LiteralPath $logFile -Tail 12 | ForEach-Object { Write-Host " $_" -ForegroundColor DarkGray }
|
||
}
|
||
$sevenZipError = Join-Path $VendooDir 'logs\local-flux-7zip-error.log'
|
||
if (Test-Path -LiteralPath $sevenZipError) {
|
||
$sevenZipLines = @(Get-Content -LiteralPath $sevenZipError -Tail 8 -ErrorAction SilentlyContinue)
|
||
if ($sevenZipLines.Count -gt 0) {
|
||
Write-Host ''
|
||
Write-Host ' Letzte 7-Zip-Fehlerausgabe:' -ForegroundColor White
|
||
$sevenZipLines | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
|
||
}
|
||
}
|
||
Write-Host ''
|
||
}
|
||
|
||
|
||
function Show-StatusLocal {
|
||
Write-Header
|
||
Write-Host ' Systemstatus' -ForegroundColor White
|
||
Write-Host ' ────────────────────────────────────' -ForegroundColor DarkGray
|
||
|
||
$node = Test-NodeInstalled
|
||
if ($node.Installed) { Write-OK "Node.js $($node.Version)" }
|
||
else { Write-Err 'Node.js Nicht installiert' }
|
||
|
||
if (Test-NpmInstalled) {
|
||
$npmV = & npm --version 2>$null
|
||
Write-OK "npm v$npmV"
|
||
} else { Write-Err 'npm Nicht gefunden' }
|
||
|
||
$nm = Join-Path $VendooDir 'node_modules'
|
||
if (Test-Path $nm) { Write-OK 'Dependencies Installiert' }
|
||
else { Write-Warn 'Dependencies Nicht installiert' }
|
||
|
||
$envFile = Join-Path $VendooDir '.env'
|
||
if (Test-Path $envFile) { Write-OK '.env Vorhanden' }
|
||
else { Write-Warn '.env Fehlt' }
|
||
|
||
$dbFile = Join-Path $VendooDir 'db' 'vendoo.db'
|
||
if (Test-Path $dbFile) {
|
||
$size = (Get-Item $dbFile).Length
|
||
$sizeStr = if ($size -gt 1MB) { "{0:N1} MB" -f ($size / 1MB) } else { "{0:N0} KB" -f ($size / 1KB) }
|
||
Write-OK "Datenbank $sizeStr"
|
||
} else { Write-Warn 'Datenbank Noch nicht erstellt (wird beim ersten Start angelegt)' }
|
||
|
||
$uploadsDir = Join-Path $VendooDir 'uploads'
|
||
if (Test-Path $uploadsDir) {
|
||
$fileCount = (Get-ChildItem $uploadsDir -File -ErrorAction SilentlyContinue).Count
|
||
Write-OK "Uploads $fileCount Dateien"
|
||
}
|
||
|
||
Write-Host ''
|
||
Write-Host " Port: $Port" -ForegroundColor DarkGray
|
||
Write-Host " Verzeichnis: $VendooDir" -ForegroundColor DarkGray
|
||
Write-Host ''
|
||
}
|
||
|
||
|
||
function Show-Status {
|
||
if ((Get-VendooInstallMode) -eq 'docker') {
|
||
Write-Header
|
||
Write-Host ' Docker-Systemstatus' -ForegroundColor White
|
||
Write-Host ''
|
||
$docker = Test-DockerAvailable
|
||
if (-not $docker.Available) { Write-Err $docker.Reason; return }
|
||
Write-OK "Docker Engine $($docker.Version)"
|
||
Invoke-VendooDockerCompose @('ps') | Out-Null
|
||
try {
|
||
$response = Invoke-WebRequest -Uri "http://127.0.0.1:$Port/readyz" -UseBasicParsing -TimeoutSec 5
|
||
if ($response.StatusCode -eq 200) { Write-OK 'Readiness Bereit' }
|
||
else { Write-Warn "Readiness HTTP $($response.StatusCode)" }
|
||
} catch { Write-Warn 'Readiness Nicht erreichbar' }
|
||
Write-Host ''
|
||
Write-Host " Installationsmodus: Docker Desktop" -ForegroundColor DarkGray
|
||
return
|
||
}
|
||
Show-StatusLocal
|
||
}
|
||
|
||
function Uninstall-Vendoo {
|
||
if ((Get-VendooInstallMode) -eq 'docker') {
|
||
Write-Header
|
||
Write-Host ' Vendoo-Dockerbereitstellung entfernen' -ForegroundColor Red
|
||
Write-Host ''
|
||
Write-Warn 'Container werden gestoppt. Persistente Volumes bleiben standardmäßig erhalten.'
|
||
$confirm = Read-Host ' Container wirklich entfernen? (ja/nein)'
|
||
if ($confirm -ne 'ja') { Write-Host ' Abgebrochen.' -ForegroundColor Yellow; return }
|
||
$docker = Test-DockerAvailable
|
||
if (-not $docker.Available) { Write-Err $docker.Reason; return }
|
||
Invoke-VendooDockerCompose @('down') | Out-Null
|
||
Write-OK 'Container und Netzwerk entfernt. Datenvolumes wurden beibehalten.'
|
||
$deleteVolumes = Read-Host ' Persistente Docker-Volumes ebenfalls endgültig löschen? Tippe dafür exakt DATEN LOESCHEN'
|
||
if ($deleteVolumes -eq 'DATEN LOESCHEN') {
|
||
Invoke-VendooDockerCompose @('down', '-v') | Out-Null
|
||
Write-Warn 'Persistente Docker-Volumes wurden gelöscht.'
|
||
}
|
||
return
|
||
}
|
||
Uninstall-VendooLocal
|
||
}
|
||
|
||
|
||
# ── Vendoo Operations Center 1.30.0 ──
|
||
function Get-VendooOperationsDir { return (Join-Path $VendooDir 'operations') }
|
||
function Get-VendooPendingOperationFile { return (Join-Path (Get-VendooOperationsDir) 'pending-operation.json') }
|
||
function Get-VendooLastOperationFile { return (Join-Path (Get-VendooOperationsDir) 'last-operation.json') }
|
||
|
||
function Test-VendooServerStopped {
|
||
try {
|
||
$listeners = @(Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue)
|
||
if ($listeners.Count -gt 0) {
|
||
Write-Err "Vendoo lauscht noch auf Port $Port. Bitte das Serverfenster zuerst schliessen."
|
||
return $false
|
||
}
|
||
} catch {
|
||
try {
|
||
$client = New-Object System.Net.Sockets.TcpClient
|
||
$async = $client.BeginConnect('127.0.0.1', $Port, $null, $null)
|
||
if ($async.AsyncWaitHandle.WaitOne(350, $false) -and $client.Connected) {
|
||
$client.Close()
|
||
Write-Err "Vendoo ist noch aktiv. Bitte das Serverfenster zuerst schliessen."
|
||
return $false
|
||
}
|
||
$client.Close()
|
||
} catch {}
|
||
}
|
||
return $true
|
||
}
|
||
|
||
function Copy-VendooCodeSnapshot([string]$Target) {
|
||
New-Item -ItemType Directory -Path $Target -Force | Out-Null
|
||
$rootFiles = @('.env.example','.gitignore','.npmrc','CHANGELOG.md','CLAUDE.md','FEATURES.md','README.md','SECURITY.md','THIRD_PARTY_NOTICES.md','package.json','package-lock.json','server.mjs','setup.bat','setup.ps1','update-manifest.json')
|
||
foreach ($name in $rootFiles) {
|
||
$source = Join-Path $VendooDir $name
|
||
if (Test-Path -LiteralPath $source) { Copy-Item -LiteralPath $source -Destination (Join-Path $Target $name) -Force }
|
||
}
|
||
foreach ($name in @('public','lib','platforms','scripts','tools','docs','extensions','extension','MockupDesign')) {
|
||
$source = Join-Path $VendooDir $name
|
||
if (Test-Path -LiteralPath $source) { Copy-Item -LiteralPath $source -Destination (Join-Path $Target $name) -Recurse -Force }
|
||
}
|
||
$localAiTarget = Join-Path $Target 'local-ai'
|
||
New-Item -ItemType Directory -Path $localAiTarget -Force | Out-Null
|
||
foreach ($name in @('start-local-flux.ps1','stop-local-flux.ps1')) {
|
||
$source = Join-Path $VendooDir ('local-ai\' + $name)
|
||
if (Test-Path -LiteralPath $source) { Copy-Item -LiteralPath $source -Destination (Join-Path $localAiTarget $name) -Force }
|
||
}
|
||
$workflowSource = Join-Path $VendooDir 'local-ai\workflows'
|
||
if (Test-Path -LiteralPath $workflowSource) { Copy-Item -LiteralPath $workflowSource -Destination (Join-Path $localAiTarget 'workflows') -Recurse -Force }
|
||
$schemaSource = Join-Path $VendooDir 'db\schema.sql'
|
||
if (Test-Path -LiteralPath $schemaSource) {
|
||
New-Item -ItemType Directory -Path (Join-Path $Target 'db') -Force | Out-Null
|
||
Copy-Item -LiteralPath $schemaSource -Destination (Join-Path $Target 'db\schema.sql') -Force
|
||
}
|
||
}
|
||
|
||
function Copy-VendooRuntimeSnapshot([string]$Target) {
|
||
New-Item -ItemType Directory -Path $Target -Force | Out-Null
|
||
$dbFile = Join-Path $VendooDir 'db\vendoo.db'
|
||
if (Test-Path -LiteralPath $dbFile) {
|
||
New-Item -ItemType Directory -Path (Join-Path $Target 'db') -Force | Out-Null
|
||
Copy-Item -LiteralPath $dbFile -Destination (Join-Path $Target 'db\vendoo.db') -Force
|
||
}
|
||
$uploads = Join-Path $VendooDir 'uploads'
|
||
if (Test-Path -LiteralPath $uploads) { Copy-Item -LiteralPath $uploads -Destination (Join-Path $Target 'uploads') -Recurse -Force }
|
||
$envFile = Join-Path $VendooDir '.env'
|
||
if (Test-Path -LiteralPath $envFile) { Copy-Item -LiteralPath $envFile -Destination (Join-Path $Target '.env') -Force }
|
||
}
|
||
|
||
function Copy-VendooTree([string]$Source, [string]$Target) {
|
||
if (-not (Test-Path -LiteralPath $Source)) { throw "Quellverzeichnis fehlt: $Source" }
|
||
New-Item -ItemType Directory -Path $Target -Force | Out-Null
|
||
$null = & robocopy.exe $Source $Target /E /COPY:DAT /DCOPY:DAT /R:2 /W:1 /NFL /NDL /NJH /NJS /NP
|
||
$code = $LASTEXITCODE
|
||
if ($code -gt 7) { throw "Robocopy fehlgeschlagen (Exit-Code $code): $Source -> $Target" }
|
||
}
|
||
|
||
function Restore-VendooCodeSnapshot([string]$Source) {
|
||
if (-not (Test-Path -LiteralPath $Source)) { throw "Rollback-Snapshot fehlt: $Source" }
|
||
Copy-VendooTree -Source $Source -Target $VendooDir
|
||
}
|
||
|
||
function Restore-VendooRuntimeSnapshot([string]$Source) {
|
||
if (-not (Test-Path -LiteralPath $Source)) { return }
|
||
$dbSource = Join-Path $Source 'db\vendoo.db'
|
||
if (Test-Path -LiteralPath $dbSource) {
|
||
New-Item -ItemType Directory -Path (Join-Path $VendooDir 'db') -Force | Out-Null
|
||
Remove-Item -LiteralPath (Join-Path $VendooDir 'db\vendoo.db-wal') -Force -ErrorAction SilentlyContinue
|
||
Remove-Item -LiteralPath (Join-Path $VendooDir 'db\vendoo.db-shm') -Force -ErrorAction SilentlyContinue
|
||
Copy-Item -LiteralPath $dbSource -Destination (Join-Path $VendooDir 'db\vendoo.db') -Force
|
||
}
|
||
$uploadsSource = Join-Path $Source 'uploads'
|
||
if (Test-Path -LiteralPath $uploadsSource) {
|
||
$uploadsTarget = Join-Path $VendooDir 'uploads'
|
||
if (Test-Path -LiteralPath $uploadsTarget) { Remove-Item -LiteralPath $uploadsTarget -Recurse -Force }
|
||
Copy-Item -LiteralPath $uploadsSource -Destination $uploadsTarget -Recurse -Force
|
||
}
|
||
$envSource = Join-Path $Source '.env'
|
||
if (Test-Path -LiteralPath $envSource) { Copy-Item -LiteralPath $envSource -Destination (Join-Path $VendooDir '.env') -Force }
|
||
}
|
||
|
||
function Find-VendooUpdateRoot([string]$ExtractRoot) {
|
||
$packages = @(Get-ChildItem -LiteralPath $ExtractRoot -Filter 'package.json' -File -Recurse -ErrorAction SilentlyContinue)
|
||
foreach ($package in $packages) {
|
||
try {
|
||
$json = Get-Content -LiteralPath $package.FullName -Raw | ConvertFrom-Json
|
||
if ($json.name -eq 'vendoo' -and (Test-Path -LiteralPath (Join-Path $package.Directory.FullName 'server.mjs'))) { return $package.Directory.FullName }
|
||
} catch {}
|
||
}
|
||
return $null
|
||
}
|
||
|
||
function Apply-VendooPendingOperation {
|
||
Write-Header
|
||
Write-Host ' Vorbereitete Systemoperation anwenden' -ForegroundColor White
|
||
Write-Host ''
|
||
$pendingFile = Get-VendooPendingOperationFile
|
||
if (-not (Test-Path -LiteralPath $pendingFile)) { Write-Warn 'Keine vorbereitete Operation vorhanden.'; return }
|
||
if (-not (Test-VendooServerStopped)) { return }
|
||
|
||
try { $operation = Get-Content -LiteralPath $pendingFile -Raw | ConvertFrom-Json }
|
||
catch { Write-Err 'pending-operation.json ist ungueltig.'; return }
|
||
if (-not $operation.verified) { Write-Err 'Die Operation wurde nicht als verifiziert markiert.'; return }
|
||
if (-not (Test-Path -LiteralPath $operation.archive_path)) { Write-Err "Archiv fehlt: $($operation.archive_path)"; return }
|
||
|
||
Write-Host " Typ: $($operation.type)" -ForegroundColor DarkGray
|
||
if ($operation.version_to) { Write-Host " Version: $($operation.version_from) -> $($operation.version_to)" -ForegroundColor DarkGray }
|
||
Write-Host " Archiv: $($operation.archive_path)" -ForegroundColor DarkGray
|
||
Write-Host ''
|
||
$confirm = Read-Host ' Jetzt anwenden? (ja/nein)'
|
||
if ($confirm -ne 'ja') { Write-Warn 'Operation abgebrochen.'; return }
|
||
|
||
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
|
||
$rollbackRoot = Join-Path (Get-VendooOperationsDir) ("rollback\" + $stamp)
|
||
$codeRollback = Join-Path $rollbackRoot 'code'
|
||
$runtimeRollback = Join-Path $rollbackRoot 'runtime'
|
||
$extractRoot = Join-Path (Get-VendooOperationsDir) ("apply-" + $stamp)
|
||
New-Item -ItemType Directory -Path $extractRoot -Force | Out-Null
|
||
|
||
try {
|
||
Write-Step '1/5' 'Rollback-Snapshot erstellen...'
|
||
Copy-VendooCodeSnapshot -Target $codeRollback
|
||
Copy-VendooRuntimeSnapshot -Target $runtimeRollback
|
||
Write-OK 'Rollback-Snapshot erstellt'
|
||
|
||
Write-Step '2/5' 'Archiv entpacken...'
|
||
Expand-Archive -LiteralPath $operation.archive_path -DestinationPath $extractRoot -Force
|
||
Write-OK 'Archiv entpackt'
|
||
|
||
if ($operation.type -eq 'update') {
|
||
Write-Step '3/5' 'Vendoo-Programmdateien aktualisieren...'
|
||
$sourceRoot = Find-VendooUpdateRoot -ExtractRoot $extractRoot
|
||
if (-not $sourceRoot) { throw 'Vendoo-Quellordner wurde im Update nicht gefunden.' }
|
||
Copy-VendooTree -Source $sourceRoot -Target $VendooDir
|
||
Write-OK 'Programmdateien aktualisiert'
|
||
Write-Step '4/5' 'Dependencies und Konfiguration aktualisieren...'
|
||
Update-Vendoo
|
||
& node --check (Join-Path $VendooDir 'server.mjs')
|
||
if ($LASTEXITCODE -ne 0) { throw 'server.mjs ist nach dem Update syntaktisch ungueltig.' }
|
||
if (-not (Test-Path -LiteralPath (Join-Path $VendooDir 'node_modules'))) { throw 'node_modules fehlt nach dem Update.' }
|
||
}
|
||
elseif ($operation.type -eq 'restore') {
|
||
Write-Step '3/5' 'Datenbank wiederherstellen...'
|
||
$dbSource = Join-Path $extractRoot 'db\vendoo.db'
|
||
if (-not (Test-Path -LiteralPath $dbSource)) { throw 'Datenbank fehlt im Backup.' }
|
||
New-Item -ItemType Directory -Path (Join-Path $VendooDir 'db') -Force | Out-Null
|
||
Remove-Item -LiteralPath (Join-Path $VendooDir 'db\vendoo.db-wal') -Force -ErrorAction SilentlyContinue
|
||
Remove-Item -LiteralPath (Join-Path $VendooDir 'db\vendoo.db-shm') -Force -ErrorAction SilentlyContinue
|
||
Copy-Item -LiteralPath $dbSource -Destination (Join-Path $VendooDir 'db\vendoo.db') -Force
|
||
Write-OK 'Datenbank wiederhergestellt'
|
||
Write-Step '4/5' 'Uploads und Konfiguration wiederherstellen...'
|
||
$backupManifest = $null
|
||
$manifestPath = Join-Path $extractRoot 'backup-manifest.json'
|
||
if (Test-Path -LiteralPath $manifestPath) {
|
||
try { $backupManifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json } catch {}
|
||
}
|
||
$uploadsSource = Join-Path $extractRoot 'uploads'
|
||
if ($backupManifest -and $backupManifest.include_uploads) {
|
||
$uploadsTarget = Join-Path $VendooDir 'uploads'
|
||
if (Test-Path -LiteralPath $uploadsTarget) { Remove-Item -LiteralPath $uploadsTarget -Recurse -Force }
|
||
if (Test-Path -LiteralPath $uploadsSource) { Copy-Item -LiteralPath $uploadsSource -Destination $uploadsTarget -Recurse -Force }
|
||
else { New-Item -ItemType Directory -Path $uploadsTarget -Force | Out-Null }
|
||
}
|
||
$envSource = Join-Path $extractRoot 'config\.env'
|
||
if (Test-Path -LiteralPath $envSource) { Copy-Item -LiteralPath $envSource -Destination (Join-Path $VendooDir '.env') -Force }
|
||
Write-OK 'Enthaltene Laufzeitdaten wiederhergestellt'
|
||
}
|
||
else { throw "Unbekannter Operationstyp: $($operation.type)" }
|
||
|
||
Write-Step '5/5' 'Operation abschliessen...'
|
||
$last = [ordered]@{
|
||
id = [string]$operation.id
|
||
type = [string]$operation.type
|
||
status = 'applied'
|
||
version_from = [string]$operation.version_from
|
||
version_to = [string]$operation.version_to
|
||
rollback_path = $rollbackRoot
|
||
applied_at = (Get-Date).ToString('o')
|
||
}
|
||
$last | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath (Get-VendooLastOperationFile) -Encoding UTF8
|
||
Remove-Item -LiteralPath $pendingFile -Force
|
||
Remove-Item -LiteralPath $extractRoot -Recurse -Force -ErrorAction SilentlyContinue
|
||
Write-OK 'Operation erfolgreich abgeschlossen'
|
||
Write-Host ''
|
||
Write-Host ' Starte Vendoo jetzt ueber Menuepunkt 2 und pruefe Admin > System.' -ForegroundColor Green
|
||
} catch {
|
||
Write-Err ("Operation fehlgeschlagen: " + $_.Exception.Message)
|
||
Write-Warn 'Der vorherige Stand wird aus dem gerade erstellten Snapshot wiederhergestellt.'
|
||
try {
|
||
Restore-VendooCodeSnapshot -Source $codeRollback
|
||
Restore-VendooRuntimeSnapshot -Source $runtimeRollback
|
||
Write-OK 'Vorheriger Stand wiederhergestellt'
|
||
} catch { Write-Err ("Automatischer Rollback fehlgeschlagen: " + $_.Exception.Message) }
|
||
} finally {
|
||
Remove-Item -LiteralPath $extractRoot -Recurse -Force -ErrorAction SilentlyContinue
|
||
}
|
||
}
|
||
|
||
function Invoke-VendooRollback {
|
||
Write-Header
|
||
Write-Host ' Letzte Systemoperation zuruecksetzen' -ForegroundColor White
|
||
Write-Host ''
|
||
if (-not (Test-VendooServerStopped)) { return }
|
||
$lastFile = Get-VendooLastOperationFile
|
||
if (-not (Test-Path -LiteralPath $lastFile)) { Write-Warn 'Kein Rollback-Snapshot vorhanden.'; return }
|
||
try { $last = Get-Content -LiteralPath $lastFile -Raw | ConvertFrom-Json }
|
||
catch { Write-Err 'last-operation.json ist ungueltig.'; return }
|
||
if (-not $last.rollback_path -or -not (Test-Path -LiteralPath $last.rollback_path)) { Write-Err 'Rollback-Verzeichnis wurde nicht gefunden.'; return }
|
||
Write-Host " Letzte Operation: $($last.type)" -ForegroundColor DarkGray
|
||
Write-Host " Zeitpunkt: $($last.applied_at)" -ForegroundColor DarkGray
|
||
Write-Host ''
|
||
if ((Read-Host ' Vorherigen Stand wiederherstellen? (ja/nein)') -ne 'ja') { Write-Warn 'Rollback abgebrochen.'; return }
|
||
try {
|
||
Restore-VendooCodeSnapshot -Source (Join-Path $last.rollback_path 'code')
|
||
Restore-VendooRuntimeSnapshot -Source (Join-Path $last.rollback_path 'runtime')
|
||
$last.status = 'rolled_back'
|
||
$last | Add-Member -NotePropertyName rolled_back_at -NotePropertyValue (Get-Date).ToString('o') -Force
|
||
$last | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $lastFile -Encoding UTF8
|
||
Write-OK 'Vorheriger Vendoo-Stand wurde wiederhergestellt.'
|
||
Write-Warn 'Fuehre bei Bedarf danach Menuepunkt 3 – Update aus.'
|
||
} catch { Write-Err ("Rollback fehlgeschlagen: " + $_.Exception.Message) }
|
||
}
|
||
|
||
# ── Main Menu ──
|
||
function Show-Menu {
|
||
while ($true) {
|
||
Write-Header
|
||
Write-Host ' Waehle eine Option:' -ForegroundColor White
|
||
Write-Host ''
|
||
Write-Host ' 1 Installieren / Installationsziel wählen' -ForegroundColor Cyan
|
||
Write-Host ' 2 Starten' -ForegroundColor Cyan
|
||
Write-Host ' 3 Update' -ForegroundColor Cyan
|
||
Write-Host ' 4 Reparieren' -ForegroundColor Cyan
|
||
Write-Host ' 5 Deinstallieren' -ForegroundColor Cyan
|
||
Write-Host ' 6 Status anzeigen' -ForegroundColor Cyan
|
||
Write-Host ' 13 Konfiguration ändern (Assistent)' -ForegroundColor Cyan
|
||
Write-Host ' 7 Lokales FLUX / ComfyUI installieren oder aktualisieren' -ForegroundColor Cyan
|
||
Write-Host ' 8 Lokales FLUX / ComfyUI Status' -ForegroundColor Cyan
|
||
Write-Host ' 9 Lokales FLUX / ComfyUI starten' -ForegroundColor Cyan
|
||
Write-Host ' 10 Lokales FLUX / ComfyUI stoppen' -ForegroundColor Cyan
|
||
Write-Host ' 11 Vorbereitete Operation anwenden' -ForegroundColor Cyan
|
||
Write-Host ' 12 Letzte Operation zuruecksetzen' -ForegroundColor Cyan
|
||
Write-Host ' 0 Beenden' -ForegroundColor DarkGray
|
||
Write-Host ''
|
||
$choice = Read-Host ' Auswahl'
|
||
|
||
switch ($choice) {
|
||
'1' { Install-Vendoo }
|
||
'2' { Start-Vendoo }
|
||
'3' { Update-Vendoo }
|
||
'4' { Repair-Vendoo }
|
||
'5' { Uninstall-Vendoo }
|
||
'6' { Show-Status }
|
||
'7' { Install-LocalFlux }
|
||
'8' { Show-LocalFluxStatus }
|
||
'9' { Start-LocalFlux }
|
||
'10' { Stop-LocalFlux }
|
||
'11' { Apply-VendooPendingOperation }
|
||
'12' { Invoke-VendooRollback }
|
||
'13' { Configure-VendooConsole }
|
||
'0' { Write-Host ' Auf Wiedersehen!' -ForegroundColor Green; return }
|
||
default { Write-Warn 'Ungueltige Auswahl' }
|
||
}
|
||
Write-Host ''
|
||
Read-Host ' Druecke Enter um fortzufahren'
|
||
}
|
||
}
|
||
|
||
Remove-LegacyTryOnComponents
|
||
|
||
if (-not (Test-VendooPowerShellFiles)) {
|
||
Write-Host ''
|
||
Write-Err 'Vendoo Setup wurde wegen fehlerhafter oder fehlender Pflichtskripte beendet.'
|
||
exit 12
|
||
}
|
||
|
||
# Automatische Wiederaufnahme des exakt bestätigten Slice-22-Fehlzustands.
|
||
# Direkte CLI-Kommandos bleiben davon unberührt.
|
||
if ($args.Count -eq 0) {
|
||
Invoke-LocalFluxStartupRecovery
|
||
}
|
||
|
||
# Handle CLI arguments for direct commands
|
||
if ($args.Count -gt 0) {
|
||
switch ($args[0]) {
|
||
'install' { Install-Vendoo }
|
||
'install-local' { $script:AutoStartAfterInstall = $true; Set-VendooInstallMode 'windows-local'; Install-VendooLocal }
|
||
'install-docker' { Install-VendooDocker }
|
||
'configure' { Configure-VendooConsole }
|
||
'menu' { Show-Menu }
|
||
'start' { Start-Vendoo }
|
||
'update' { Update-Vendoo }
|
||
'repair' { Repair-Vendoo }
|
||
'uninstall' { Uninstall-Vendoo }
|
||
'status' { Show-Status }
|
||
'install-local-flux' { Install-LocalFlux }
|
||
'flux-status' { Show-LocalFluxStatus }
|
||
'flux-start' { Start-LocalFlux }
|
||
'flux-stop' { Stop-LocalFlux }
|
||
'apply-operation' { Apply-VendooPendingOperation }
|
||
'rollback' { Invoke-VendooRollback }
|
||
default { Show-Menu }
|
||
}
|
||
} else {
|
||
Show-Menu
|
||
}
|