param([switch]$SkipInstall, [int]$ApiPort=8088, [int]$ConsolePort=5180) $ErrorActionPreference = 'Stop' $vdiWorkspace = Split-Path -Parent $PSScriptRoot $vdiLocalDir = Join-Path $vdiWorkspace '.local' New-Item -ItemType Directory -Force -Path $vdiLocalDir | Out-Null $vdiGoCommand = Get-Command go -ErrorAction SilentlyContinue $vdiGoPath = if ($vdiGoCommand) { $vdiGoCommand.Source } else { Join-Path $env:LOCALAPPDATA 'vdi-cloud-dev\go\bin\go.exe' } if (!(Test-Path -LiteralPath $vdiGoPath)) { throw 'Go 1.25+ is required. Install Go and add it to PATH.' } foreach ($vdiPort in @($ApiPort,$ConsolePort)) { if (Get-NetTCPConnection -State Listen -LocalPort $vdiPort -ErrorAction SilentlyContinue) { throw "Port $vdiPort is already in use. Stop the existing development instance before starting another." } } Push-Location $vdiWorkspace try { docker compose -f deploy/compose.dev.yml up -d postgres if ($LASTEXITCODE -ne 0) { throw 'PostgreSQL startup failed' } $vdiDatabaseReady = $false for ($vdiAttempt = 0; $vdiAttempt -lt 40; $vdiAttempt++) { docker exec vdi-cloud-dev-postgres-1 pg_isready -U vdi -d vdi 2>$null | Out-Null if ($LASTEXITCODE -eq 0) { $vdiDatabaseReady = $true; break } Start-Sleep -Milliseconds 500 } if (!$vdiDatabaseReady) { throw 'PostgreSQL did not become ready' } Push-Location platform try { & $vdiGoPath build -o (Join-Path $vdiLocalDir 'vdi-api.exe') ./cmd/api-gateway; if ($LASTEXITCODE -ne 0) { throw 'API build failed' } } finally { Pop-Location } if (!$SkipInstall) { Push-Location console; try { npm.cmd ci; if ($LASTEXITCODE -ne 0) { throw 'Console install failed' } } finally { Pop-Location } } $vdiEnvironment = @{ VDI_MODE = 'demo'; VDI_LISTEN = "127.0.0.1:$ApiPort"; DATABASE_URL = 'postgres://vdi:vdi-local-demo@127.0.0.1:55438/vdi?sslmode=disable'; VDI_JWT_SECRET = 'local-demo-only-change-before-any-production-use-2026'; PVE_URL = ''; PVE_TOKEN_ID = ''; PVE_TOKEN_SECRET = ''; PVE_CA_FILE = ''; VITE_API_MODE = 'api'; VDI_API_PROXY = "http://127.0.0.1:$ApiPort" } $vdiOriginalEnvironment = @{} try { foreach ($vdiKey in $vdiEnvironment.Keys) { $vdiOriginalEnvironment[$vdiKey] = [Environment]::GetEnvironmentVariable($vdiKey,'Process'); [Environment]::SetEnvironmentVariable($vdiKey,$vdiEnvironment[$vdiKey],'Process') } $vdiAPIProcess = Start-Process -FilePath (Join-Path $vdiLocalDir 'vdi-api.exe') -WorkingDirectory $vdiWorkspace -WindowStyle Hidden -RedirectStandardOutput (Join-Path $vdiLocalDir 'api.stdout.log') -RedirectStandardError (Join-Path $vdiLocalDir 'api.stderr.log') -PassThru $vdiConsoleProcess = Start-Process -FilePath 'cmd.exe' -ArgumentList '/d','/c',"npm.cmd run dev -- --port $ConsolePort" -WorkingDirectory (Join-Path $vdiWorkspace 'console') -WindowStyle Hidden -RedirectStandardOutput (Join-Path $vdiLocalDir 'console.stdout.log') -RedirectStandardError (Join-Path $vdiLocalDir 'console.stderr.log') -PassThru @{ apiPid = $vdiAPIProcess.Id; consolePid = $vdiConsoleProcess.Id; startedAt = (Get-Date).ToString('o') } | ConvertTo-Json | Set-Content (Join-Path $vdiLocalDir 'cloud-dev.json') } finally { foreach ($vdiKey in $vdiOriginalEnvironment.Keys) { [Environment]::SetEnvironmentVariable($vdiKey,$vdiOriginalEnvironment[$vdiKey],'Process') } } Write-Host "Console: http://127.0.0.1:$ConsolePort" Write-Host 'Demo users: admin / operator / viewer; password: Demo@2026' Write-Host 'Development process IDs and logs: .local/' } finally { Pop-Location }