$ErrorActionPreference = 'Stop' $VendooDir = Split-Path -Parent $MyInvocation.MyCommand.Path $Version = '1.41.2' $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.41.2" & 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 }