# Vendoo 1.42.0 – grafischer Zero-Edit-Installationsassistent $ErrorActionPreference = 'Stop' $VendooDir = Split-Path -Parent $MyInvocation.MyCommand.Path $SetupScript = Join-Path $VendooDir 'setup.ps1' $EnvFile = Join-Path $VendooDir '.env' $EnvExample = Join-Path $VendooDir '.env.example' $ModeFile = Join-Path $VendooDir '.vendoo-install-mode' Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing [System.Windows.Forms.Application]::EnableVisualStyles() function Write-Utf8NoBom([string]$Path, [string]$Content) { $utf8 = New-Object System.Text.UTF8Encoding($false) [System.IO.File]::WriteAllText($Path, $Content, $utf8) } function Ensure-EnvFile { if (-not (Test-Path -LiteralPath $EnvFile)) { if (-not (Test-Path -LiteralPath $EnvExample)) { throw '.env.example fehlt.' } Copy-Item -LiteralPath $EnvExample -Destination $EnvFile } } function Get-EnvValue([string]$Key) { 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 Set-EnvValue([string]$Key, [string]$Value) { if ($Value -match "[\r\n]") { throw "Ungültiger mehrzeiliger Wert für $Key." } Ensure-EnvFile $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-Utf8NoBom -Path $EnvFile -Content $content } function New-SecureToken { $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 Test-Port([string]$Text) { $port = 0 return [int]::TryParse($Text, [ref]$port) -and $port -ge 1024 -and $port -le 65535 } function Set-Configuration([string]$Target, [string]$Access, [int]$Port, [string]$PublicUrl, [bool]$FluxEnabled) { Ensure-EnvFile $token = Get-EnvValue 'VENDOO_SETUP_TOKEN' if ([string]::IsNullOrWhiteSpace($token)) { $token = New-SecureToken Set-EnvValue 'VENDOO_SETUP_TOKEN' $token } Set-EnvValue 'PORT' ([string]$Port) Set-EnvValue 'VENDOO_HOST_PORT' ([string]$Port) Set-EnvValue 'VENDOO_REQUIRE_SETUP_TOKEN' 'true' Set-EnvValue 'VENDOO_COOKIE_SAMESITE' 'lax' Set-EnvValue 'LOCAL_IMAGE_ENABLED' ($(if ($FluxEnabled) { 'true' } else { 'false' })) if ($Target -eq 'windows-local') { Set-EnvValue 'VENDOO_BIND_HOST' ($(if ($Access -eq 'LAN') { '0.0.0.0' } else { '127.0.0.1' })) Set-EnvValue 'VENDOO_PUBLISH_ADDRESS' '127.0.0.1' Set-EnvValue 'VENDOO_COOKIE_SECURE' 'auto' Set-EnvValue 'VENDOO_TRUST_PROXY' 'false' Set-EnvValue 'LOCAL_IMAGE_URL' 'http://127.0.0.1:8188' } else { Set-EnvValue 'VENDOO_BIND_HOST' '0.0.0.0' Set-EnvValue 'VENDOO_PUBLISH_ADDRESS' ($(if ($Access -eq 'LAN') { '0.0.0.0' } else { '127.0.0.1' })) Set-EnvValue 'VENDOO_COOKIE_SECURE' 'auto' Set-EnvValue 'VENDOO_TRUST_PROXY' 'false' Set-EnvValue 'LOCAL_IMAGE_URL' 'http://host.docker.internal:8188' } Set-EnvValue 'PUBLIC_BASE_URL' ($(if ([string]::IsNullOrWhiteSpace($PublicUrl)) { '' } else { $PublicUrl.Trim().TrimEnd('/') })) Write-Utf8NoBom -Path $ModeFile -Content ($Target + "`n") try { [System.Windows.Forms.Clipboard]::SetText($token) } catch {} return $token } $form = New-Object System.Windows.Forms.Form $form.Text = 'Vendoo 1.42.0 – Installation' $form.StartPosition = 'CenterScreen' $form.Size = New-Object System.Drawing.Size(720, 620) $form.MinimumSize = New-Object System.Drawing.Size(720, 620) $form.Font = New-Object System.Drawing.Font('Segoe UI', 10) $form.BackColor = [System.Drawing.Color]::FromArgb(246, 248, 247) $title = New-Object System.Windows.Forms.Label $title.Text = 'Vendoo installieren' $title.Font = New-Object System.Drawing.Font('Segoe UI Semibold', 22) $title.AutoSize = $true $title.Location = New-Object System.Drawing.Point(34, 24) $form.Controls.Add($title) $sub = New-Object System.Windows.Forms.Label $sub.Text = 'Alle Pflichtwerte werden geprüft und automatisch gespeichert. Es müssen keine Dateien bearbeitet werden.' $sub.AutoSize = $false $sub.Size = New-Object System.Drawing.Size(640, 48) $sub.Location = New-Object System.Drawing.Point(38, 72) $sub.ForeColor = [System.Drawing.Color]::FromArgb(70, 80, 76) $form.Controls.Add($sub) function Add-Label([string]$Text, [int]$Y) { $l = New-Object System.Windows.Forms.Label $l.Text = $Text $l.AutoSize = $true $l.Location = New-Object System.Drawing.Point(40, $Y) $form.Controls.Add($l) return $l } Add-Label 'Installationsart' 132 | Out-Null $target = New-Object System.Windows.Forms.ComboBox $target.DropDownStyle = 'DropDownList' $target.Items.AddRange(@('Lokal auf diesem Windows-PC', 'Docker Desktop auf diesem Windows-PC')) $target.SelectedIndex = 0 $target.Location = New-Object System.Drawing.Point(40, 158) $target.Size = New-Object System.Drawing.Size(620, 34) $form.Controls.Add($target) Add-Label 'Erreichbarkeit' 208 | Out-Null $access = New-Object System.Windows.Forms.ComboBox $access.DropDownStyle = 'DropDownList' $access.Items.AddRange(@('Nur auf diesem PC (empfohlen)', 'Im privaten LAN erreichbar')) $access.SelectedIndex = 0 $access.Location = New-Object System.Drawing.Point(40, 234) $access.Size = New-Object System.Drawing.Size(620, 34) $form.Controls.Add($access) Add-Label 'Port' 284 | Out-Null $portBox = New-Object System.Windows.Forms.NumericUpDown $portBox.Minimum = 1024 $portBox.Maximum = 65535 $portBox.Value = 8124 $portBox.Location = New-Object System.Drawing.Point(40, 310) $portBox.Size = New-Object System.Drawing.Size(160, 34) $form.Controls.Add($portBox) Add-Label 'Öffentliche/LAN-Basisadresse (optional)' 358 | Out-Null $publicUrl = New-Object System.Windows.Forms.TextBox $publicUrl.Location = New-Object System.Drawing.Point(40, 384) $publicUrl.Size = New-Object System.Drawing.Size(620, 34) $form.Controls.Add($publicUrl) $flux = New-Object System.Windows.Forms.CheckBox $flux.Text = 'Lokales FLUX Studio aktivieren (ComfyUI bleibt ein getrennter Dienst)' $flux.AutoSize = $true $flux.Location = New-Object System.Drawing.Point(40, 438) $form.Controls.Add($flux) $hint = New-Object System.Windows.Forms.Label $hint.Text = 'AI- und Marktplatz-Schlüssel werden später bequem unter Vendoo → Einstellungen eingetragen.' $hint.AutoSize = $false $hint.Size = New-Object System.Drawing.Size(620, 42) $hint.Location = New-Object System.Drawing.Point(40, 474) $hint.ForeColor = [System.Drawing.Color]::FromArgb(70, 80, 76) $form.Controls.Add($hint) $install = New-Object System.Windows.Forms.Button $install.Text = 'Installieren' $install.Size = New-Object System.Drawing.Size(180, 44) $install.Location = New-Object System.Drawing.Point(480, 520) $install.BackColor = [System.Drawing.Color]::FromArgb(32, 112, 78) $install.ForeColor = [System.Drawing.Color]::White $install.FlatStyle = 'Flat' $form.Controls.Add($install) $advanced = New-Object System.Windows.Forms.Button $advanced.Text = 'Konsolenmenü' $advanced.Size = New-Object System.Drawing.Size(150, 44) $advanced.Location = New-Object System.Drawing.Point(40, 520) $form.Controls.Add($advanced) $cancel = New-Object System.Windows.Forms.Button $cancel.Text = 'Abbrechen' $cancel.Size = New-Object System.Drawing.Size(130, 44) $cancel.Location = New-Object System.Drawing.Point(330, 520) $form.Controls.Add($cancel) $target.Add_SelectedIndexChanged({ if ($target.SelectedIndex -eq 1) { $hint.Text = 'Docker Desktop muss gestartet sein. AI- und Marktplatz-Schlüssel werden später in Vendoo gepflegt.' } else { $hint.Text = 'AI- und Marktplatz-Schlüssel werden später bequem unter Vendoo → Einstellungen eingetragen.' } }) $advanced.Add_Click({ $form.Hide() Start-Process powershell.exe -ArgumentList @('-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',('"' + $SetupScript + '"'),'menu') -Wait $form.Close() }) $cancel.Add_Click({ $form.Close() }) $install.Add_Click({ try { $install.Enabled = $false $targetMode = if ($target.SelectedIndex -eq 1) { 'docker' } else { 'windows-local' } $accessMode = if ($access.SelectedIndex -eq 1) { 'LAN' } else { 'LOCAL' } $url = $publicUrl.Text.Trim() if ($url -and $url -notmatch '^https?://') { throw 'Die Basisadresse muss mit http:// oder https:// beginnen.' } if ($accessMode -eq 'LAN' -and [string]::IsNullOrWhiteSpace($url)) { $answer = [System.Windows.Forms.MessageBox]::Show('Für QR-Uploads ist später eine LAN-Adresse sinnvoll. Trotzdem ohne Basisadresse fortfahren?', 'LAN-Adresse fehlt', 'YesNo', 'Warning') if ($answer -ne 'Yes') { return } } $token = Set-Configuration -Target $targetMode -Access $accessMode -Port ([int]$portBox.Value) -PublicUrl $url -FluxEnabled $flux.Checked [System.Windows.Forms.MessageBox]::Show("Die Konfiguration ist gespeichert. Der Erst-Admin-Code wurde in die Zwischenablage kopiert.`r`n`r`n$token`r`n`r`nBewahre ihn bis zur ersten Anmeldung sicher auf.", 'Vendoo vorbereitet', 'OK', 'Information') | Out-Null $form.Hide() $command = if ($targetMode -eq 'docker') { 'install-docker' } else { 'install-local' } $proc = Start-Process powershell.exe -ArgumentList @('-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',('"' + $SetupScript + '"'),$command) -Wait -PassThru if ($proc.ExitCode -eq 0) { Start-Process ("http://127.0.0.1:{0}/login.html" -f [int]$portBox.Value) } else { [System.Windows.Forms.MessageBox]::Show("Die Installation wurde mit Exit-Code $($proc.ExitCode) beendet. Das Konsolenfenster enthält die Details.", 'Installation nicht abgeschlossen', 'OK', 'Error') | Out-Null } $form.Close() } catch { [System.Windows.Forms.MessageBox]::Show($_.Exception.Message, 'Eingabe prüfen', 'OK', 'Warning') | Out-Null } finally { $install.Enabled = $true } }) [void]$form.ShowDialog()