#!/usr/bin/env pwsh <# .SYNOPSIS Automated developer environment setup for Stella Ops (Windows). .DESCRIPTION Validates prerequisites, starts infrastructure, builds solutions and Docker images, and launches the full platform. .PARAMETER SkipBuild Skip .NET solution builds. .PARAMETER InfraOnly Only start infrastructure containers (PostgreSQL, Valkey, SeaweedFS, Rekor, Zot). .PARAMETER ImagesOnly Only build Docker images (skip infra start and .NET build). .PARAMETER SkipImages Skip Docker image builds. #> [CmdletBinding()] param( [switch]$SkipBuild, [switch]$InfraOnly, [switch]$ImagesOnly, [switch]$SkipImages ) $ErrorActionPreference = 'Stop' $Root = git rev-parse --show-toplevel 2>$null if (-not $Root) { Write-Error 'Not inside a git repository. Run this script from within the Stella Ops repo.' exit 1 } $Root = $Root.Trim() $ComposeDir = Join-Path $Root 'devops/compose' # ─── Helpers ──────────────────────────────────────────────────────────────── function Write-Step([string]$msg) { Write-Host "`n>> $msg" -ForegroundColor Cyan } function Write-Ok([string]$msg) { Write-Host " [OK] $msg" -ForegroundColor Green } function Write-Warn([string]$msg) { Write-Host " [WARN] $msg" -ForegroundColor Yellow } function Write-Fail([string]$msg) { Write-Host " [FAIL] $msg" -ForegroundColor Red } function Test-Command([string]$cmd) { return [bool](Get-Command $cmd -ErrorAction SilentlyContinue) } # ─── 1. Check prerequisites ──────────────────────────────────────────────── function Test-Prerequisites { Write-Step 'Checking prerequisites' $allGood = $true # dotnet if (Test-Command 'dotnet') { $v = (dotnet --version 2>$null) if ($v -match '^10\.') { Write-Ok "dotnet $v" } else { Write-Fail "dotnet $v found, but 10.x is required" $allGood = $false } } else { Write-Fail 'dotnet SDK not found. Install .NET 10 SDK.' $allGood = $false } # node if (Test-Command 'node') { $v = (node --version 2>$null).TrimStart('v') $major = [int]($v -split '\.')[0] if ($major -ge 20) { Write-Ok "node $v" } else { Write-Fail "node $v found, but 20+ is required" $allGood = $false } } else { Write-Fail 'node not found. Install Node.js 20+.' $allGood = $false } # npm if (Test-Command 'npm') { $v = (npm --version 2>$null) $major = [int]($v -split '\.')[0] if ($major -ge 10) { Write-Ok "npm $v" } else { Write-Fail "npm $v found, but 10+ is required" $allGood = $false } } else { Write-Fail 'npm not found.' $allGood = $false } # docker if (Test-Command 'docker') { $v = (docker --version 2>$null) Write-Ok "docker: $v" } else { Write-Fail 'docker not found. Install Docker Desktop.' $allGood = $false } # docker compose $composeOk = $false try { $null = docker compose version 2>$null if ($LASTEXITCODE -eq 0) { $composeOk = $true } } catch {} if ($composeOk) { Write-Ok 'docker compose available' } else { Write-Fail 'docker compose not available. Ensure Docker Desktop includes Compose V2.' $allGood = $false } # git if (Test-Command 'git') { Write-Ok "git $(git --version 2>$null)" } else { Write-Fail 'git not found.' $allGood = $false } if (-not $allGood) { Write-Error 'Prerequisites not met. Install missing tools and re-run.' exit 1 } } # ─── 2. Check and install hosts file ───────────────────────────────────── function Test-HostsFile { Write-Step 'Checking hosts file for stella-ops.local entries' $hostsPath = 'C:\Windows\System32\drivers\etc\hosts' $hostsSource = Join-Path $Root 'devops/compose/hosts.stellaops.local' if (-not (Test-Path $hostsPath)) { Write-Warn "Cannot read hosts file at $hostsPath" return } $content = Get-Content $hostsPath -Raw if ($content -match 'stella-ops\.local') { Write-Ok 'stella-ops.local entries found in hosts file' return } Write-Warn 'stella-ops.local entries NOT found in hosts file.' if (-not (Test-Path $hostsSource)) { Write-Warn "Hosts source file not found at $hostsSource" Write-Host ' Add the hosts block from docs/dev/DEV_ENVIRONMENT_SETUP.md section 2' -ForegroundColor Yellow return } # Check if running as Administrator $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if ($isAdmin) { Write-Host '' Write-Host ' Stella Ops needs ~50 hosts file entries for local development.' -ForegroundColor Yellow Write-Host ' Source: devops/compose/hosts.stellaops.local' -ForegroundColor Yellow Write-Host '' $answer = Read-Host ' Add entries to hosts file now? (Y/n)' if ($answer -eq '' -or $answer -match '^[Yy]') { $hostsBlock = Get-Content $hostsSource -Raw Add-Content -Path $hostsPath -Value "`n$hostsBlock" Write-Ok 'Hosts entries added successfully' } else { Write-Warn 'Skipped. Add them manually before accessing the platform.' Write-Host " Copy from: $hostsSource" -ForegroundColor Yellow } } else { Write-Host '' Write-Host ' Stella Ops needs ~50 hosts file entries for local development.' -ForegroundColor Yellow Write-Host ' To install them, run this command in an elevated (Administrator) PowerShell:' -ForegroundColor Yellow Write-Host '' Write-Host " Get-Content '$hostsSource' | Add-Content '$hostsPath'" -ForegroundColor White Write-Host '' Write-Host ' Or re-run this script as Administrator to install them automatically.' -ForegroundColor Yellow } } # ─── 3. Ensure .env ──────────────────────────────────────────────────────── function Initialize-EnvFile { Write-Step 'Ensuring .env file exists' $envFile = Join-Path $ComposeDir '.env' $envExample = Join-Path $ComposeDir 'env/stellaops.env.example' if (Test-Path $envFile) { Write-Ok ".env already exists at $envFile" } elseif (Test-Path $envExample) { Copy-Item $envExample $envFile Write-Ok "Copied $envExample -> $envFile" Write-Warn 'For production, change POSTGRES_PASSWORD in .env.' } else { Write-Fail "Neither .env nor env/stellaops.env.example found in $ComposeDir" exit 1 } } # ─── 4. Start infrastructure ─────────────────────────────────────────────── function Start-Infrastructure { Write-Step 'Starting infrastructure containers (docker-compose.dev.yml)' Push-Location $ComposeDir try { docker compose -f docker-compose.dev.yml up -d if ($LASTEXITCODE -ne 0) { Write-Fail 'Failed to start infrastructure containers.' exit 1 } Write-Host ' Waiting for containers to become healthy...' -ForegroundColor Gray $maxWait = 120 $elapsed = 0 while ($elapsed -lt $maxWait) { $ps = docker compose -f docker-compose.dev.yml ps --format json 2>$null if ($ps) { $allHealthy = $true # docker compose ps --format json outputs one JSON object per line foreach ($line in $ps -split "`n") { $line = $line.Trim() if (-not $line) { continue } try { $svc = $line | ConvertFrom-Json if ($svc.Health -and $svc.Health -ne 'healthy') { $allHealthy = $false } } catch {} } if ($allHealthy -and $elapsed -gt 5) { Write-Ok 'All infrastructure containers healthy' return } } Start-Sleep -Seconds 5 $elapsed += 5 } Write-Warn "Timed out waiting for healthy status after ${maxWait}s. Check with: docker compose -f docker-compose.dev.yml ps" } finally { Pop-Location } } # ─── 5. Build .NET solutions ─────────────────────────────────────────────── function Build-Solutions { Write-Step 'Building all .NET solutions' $buildScript = Join-Path $Root 'scripts/build-all-solutions.ps1' if (Test-Path $buildScript) { & $buildScript if ($LASTEXITCODE -ne 0) { Write-Fail '.NET solution build failed.' exit 1 } Write-Ok '.NET solutions built successfully' } else { Write-Warn "Build script not found at $buildScript. Skipping .NET build." } } # ─── 6. Build Docker images ──────────────────────────────────────────────── function Build-Images { Write-Step 'Building Docker images' $buildScript = Join-Path $Root 'devops/docker/build-all.ps1' if (Test-Path $buildScript) { & $buildScript if ($LASTEXITCODE -ne 0) { Write-Fail 'Docker image build failed.' exit 1 } Write-Ok 'Docker images built successfully' } else { Write-Warn "Build script not found at $buildScript. Skipping image build." } } # ─── 7. Start full platform ──────────────────────────────────────────────── function Start-Platform { Write-Step 'Starting full Stella Ops platform' Push-Location $ComposeDir try { docker compose -f docker-compose.stella-ops.yml up -d if ($LASTEXITCODE -ne 0) { Write-Fail 'Failed to start platform services.' exit 1 } Write-Ok 'Platform services started' } finally { Pop-Location } } # ─── 8. Smoke test ───────────────────────────────────────────────────────── function Test-Smoke { Write-Step 'Running smoke tests' # Infrastructure checks $endpoints = @( @{ Name = 'PostgreSQL'; Cmd = { docker exec stellaops-dev-postgres pg_isready -U stellaops 2>$null; $LASTEXITCODE -eq 0 } }, @{ Name = 'Valkey'; Cmd = { $r = docker exec stellaops-dev-valkey valkey-cli ping 2>$null; $r -eq 'PONG' } } ) foreach ($ep in $endpoints) { try { $ok = & $ep.Cmd if ($ok) { Write-Ok $ep.Name } else { Write-Warn "$($ep.Name) not responding" } } catch { Write-Warn "$($ep.Name) check failed: $_" } } # Platform container health summary Write-Step 'Container health summary' Push-Location $ComposeDir try { $composeFiles = @('docker-compose.dev.yml', 'docker-compose.stella-ops.yml') $totalContainers = 0 $healthyContainers = 0 $unhealthyNames = @() foreach ($cf in $composeFiles) { if (-not (Test-Path $cf)) { continue } $ps = docker compose -f $cf ps --format json 2>$null if (-not $ps) { continue } foreach ($line in $ps -split "`n") { $line = $line.Trim() if (-not $line) { continue } try { $svc = $line | ConvertFrom-Json $totalContainers++ if (-not $svc.Health -or $svc.Health -eq 'healthy') { $healthyContainers++ } else { $unhealthyNames += $svc.Name } } catch {} } } if ($totalContainers -gt 0) { if ($healthyContainers -eq $totalContainers) { Write-Ok "$healthyContainers/$totalContainers containers healthy" } else { Write-Warn "$healthyContainers/$totalContainers containers healthy" foreach ($name in $unhealthyNames) { Write-Warn " Unhealthy: $name" } } } # Platform endpoint check try { $tcp = New-Object System.Net.Sockets.TcpClient $tcp.Connect('stella-ops.local', 443) $tcp.Close() Write-Ok 'Platform accessible at https://stella-ops.local' } catch { Write-Warn 'Platform not yet accessible at https://stella-ops.local (may still be starting)' } } finally { Pop-Location } } # ─── Main ─────────────────────────────────────────────────────────────────── Write-Host '=============================================' -ForegroundColor Cyan Write-Host ' Stella Ops Developer Environment Setup' -ForegroundColor Cyan Write-Host '=============================================' -ForegroundColor Cyan Test-Prerequisites Test-HostsFile if ($ImagesOnly) { Build-Images Write-Host "`nDone (images only)." -ForegroundColor Green exit 0 } Initialize-EnvFile Start-Infrastructure if ($InfraOnly) { Test-Smoke Write-Host "`nDone (infra only). Infrastructure is running." -ForegroundColor Green exit 0 } if (-not $SkipBuild) { Build-Solutions } if (-not $SkipImages) { Build-Images } Start-Platform Test-Smoke Write-Host "`n=============================================" -ForegroundColor Green Write-Host ' Setup complete!' -ForegroundColor Green Write-Host ' Platform: https://stella-ops.local' -ForegroundColor Green Write-Host ' Docs: docs/dev/DEV_ENVIRONMENT_SETUP.md' -ForegroundColor Green Write-Host '=============================================' -ForegroundColor Green