Files
vendoo/setup.ps1
T
Masterluke77andGitHub 40071f718e Vendoo 1.43.0 – Production Update
Manifestbasierter Production-Updater 3.3, Modulnavigation, optionales Produktbild Studio, Listing-Studio-Aktionsleiste sowie plattformübergreifende Windows-/Linux-Release-Gates.
2026-07-10 13:16:21 +02:00

1763 lines
81 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
$ErrorActionPreference = 'Stop'
$VendooDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$Version = '1.42.0'
$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.42.0"
& 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
}
foreach ($name in @('uploads','config','modules')) {
$source = Join-Path $VendooDir $name
if (Test-Path -LiteralPath $source) {
Copy-Item -LiteralPath $source -Destination (Join-Path $Target $name) -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
}
foreach ($name in @('uploads','config','modules')) {
$sourcePath = Join-Path $Source $name
if (Test-Path -LiteralPath $sourcePath) {
$targetPath = Join-Path $VendooDir $name
if (Test-Path -LiteralPath $targetPath) { Remove-Item -LiteralPath $targetPath -Recurse -Force }
Copy-Item -LiteralPath $sourcePath -Destination $targetPath -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 }
}
$configSource = Join-Path $extractRoot 'config'
if (Test-Path -LiteralPath $configSource) {
$configTarget = Join-Path $VendooDir 'config'
if (Test-Path -LiteralPath $configTarget) { Remove-Item -LiteralPath $configTarget -Recurse -Force }
Copy-Item -LiteralPath $configSource -Destination $configTarget -Recurse -Force
}
$modulesSource = Join-Path $extractRoot 'modules'
if (Test-Path -LiteralPath $modulesSource) {
$modulesTarget = Join-Path $VendooDir 'modules'
if (Test-Path -LiteralPath $modulesTarget) { Remove-Item -LiteralPath $modulesTarget -Recurse -Force }
Copy-Item -LiteralPath $modulesSource -Destination $modulesTarget -Recurse -Force
}
$moduleStateSource = Join-Path $extractRoot 'module-state\modules-state.json'
if (Test-Path -LiteralPath $moduleStateSource) {
$configTarget = Join-Path $VendooDir 'config'
New-Item -ItemType Directory -Path $configTarget -Force | Out-Null
Copy-Item -LiteralPath $moduleStateSource -Destination (Join-Path $configTarget 'modules-state.json') -Force
}
$envSource = Join-Path $extractRoot 'config\.env'
if (Test-Path -LiteralPath $envSource) { Copy-Item -LiteralPath $envSource -Destination (Join-Path $VendooDir '.env') -Force }
Write-OK 'Uploads, Konfiguration, Modulpakete und Modulzustand 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
}