|
|
@@ -0,0 +1,198 @@
|
|
|
+# ==============================================================================
|
|
|
+# lf_mri_platform -- Full launch: install (if needed) -> containers -> GUI
|
|
|
+# Usage:
|
|
|
+# .\launch.ps1 -- install if needed, start services + GUI
|
|
|
+# .\launch.ps1 -Mode real -- hardware mode
|
|
|
+# .\launch.ps1 -SkipInstall -- skip dependency check
|
|
|
+# .\launch.ps1 -ServicesOnly -- do not open GUI
|
|
|
+# ==============================================================================
|
|
|
+param(
|
|
|
+ [ValidateSet("plug", "real")]
|
|
|
+ [string]$Mode = "plug",
|
|
|
+ [switch]$SkipInstall,
|
|
|
+ [switch]$ServicesOnly
|
|
|
+)
|
|
|
+
|
|
|
+$ErrorActionPreference = "Stop"
|
|
|
+$Root = $PSScriptRoot
|
|
|
+$GuiDir = Join-Path $Root "apps\gui"
|
|
|
+$LibsDir = Join-Path $Root "libs\lf-scanner"
|
|
|
+$VenvPython = Join-Path $GuiDir ".venv\Scripts\python.exe"
|
|
|
+$VenvPip = Join-Path $GuiDir ".venv\Scripts\pip.exe"
|
|
|
+$AppScript = Join-Path $GuiDir "app.py"
|
|
|
+$EnvFile = Join-Path $Root ".env"
|
|
|
+$EnvExample = Join-Path $Root ".env.example"
|
|
|
+
|
|
|
+function Write-Step($msg) { Write-Host "`n==> $msg" -ForegroundColor Cyan }
|
|
|
+function Write-OK($msg) { Write-Host " [OK] $msg" -ForegroundColor Green }
|
|
|
+function Write-Warn($msg) { Write-Host " [!!] $msg" -ForegroundColor Yellow }
|
|
|
+function Write-Fail($msg) { Write-Host "`n[FAIL] $msg" -ForegroundColor Red; exit 1 }
|
|
|
+
|
|
|
+# -- 1. Install dependencies if venv is missing --------------------------------
|
|
|
+if (-not $SkipInstall) {
|
|
|
+ Write-Step "Checking GUI environment"
|
|
|
+
|
|
|
+ $needsInstall = $false
|
|
|
+
|
|
|
+ if (-not (Test-Path $VenvPython)) {
|
|
|
+ Write-Warn "Virtual environment not found -- running install"
|
|
|
+ $needsInstall = $true
|
|
|
+ } else {
|
|
|
+ # Check that key packages are present
|
|
|
+ $checkPkg = & $VenvPython -c "import PyQt6" 2>&1
|
|
|
+ if ($LASTEXITCODE -ne 0) {
|
|
|
+ Write-Warn "GUI packages missing -- running install"
|
|
|
+ $needsInstall = $true
|
|
|
+ } else {
|
|
|
+ Write-OK "Virtual environment ready"
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($needsInstall) {
|
|
|
+ $installScript = Join-Path $Root "install.ps1"
|
|
|
+ if (-not (Test-Path $installScript)) {
|
|
|
+ Write-Fail "install.ps1 not found at $installScript"
|
|
|
+ }
|
|
|
+ & powershell.exe -ExecutionPolicy Bypass -File $installScript
|
|
|
+ if ($LASTEXITCODE -ne 0) { Write-Fail "install.ps1 failed" }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+# -- 2. Ensure Docker is running -----------------------------------------------
|
|
|
+Write-Step "Checking Docker"
|
|
|
+
|
|
|
+$dockerOk = $false
|
|
|
+try { & docker info *>$null; $dockerOk = $true } catch {}
|
|
|
+
|
|
|
+if (-not $dockerOk) {
|
|
|
+ Write-Warn "Docker not responding -- attempting to start Docker Desktop..."
|
|
|
+ $dockerExe = "C:\Program Files\Docker\Docker\Docker Desktop.exe"
|
|
|
+ if (Test-Path $dockerExe) {
|
|
|
+ Start-Process $dockerExe
|
|
|
+ } else {
|
|
|
+ Write-Fail "Docker Desktop not found. Install it from https://www.docker.com/products/docker-desktop/"
|
|
|
+ }
|
|
|
+
|
|
|
+ Write-Host " Waiting up to 60 s for Docker to start..." -ForegroundColor DarkGray
|
|
|
+ $waited = 0
|
|
|
+ while ($waited -lt 60) {
|
|
|
+ Start-Sleep 5
|
|
|
+ $waited += 5
|
|
|
+ try { & docker info *>$null; $dockerOk = $true; break } catch {}
|
|
|
+ Write-Host " ... $waited/60 s" -ForegroundColor DarkGray
|
|
|
+ }
|
|
|
+ if (-not $dockerOk) { Write-Fail "Docker did not start in time. Start Docker Desktop manually and re-run." }
|
|
|
+}
|
|
|
+Write-OK "Docker is running"
|
|
|
+
|
|
|
+# -- 3. Prepare .env -----------------------------------------------------------
|
|
|
+if (-not (Test-Path $EnvFile)) {
|
|
|
+ if (Test-Path $EnvExample) {
|
|
|
+ Copy-Item $EnvExample $EnvFile
|
|
|
+ Write-Warn ".env not found -- created from .env.example"
|
|
|
+ } else {
|
|
|
+ Write-Warn ".env and .env.example both missing -- using Docker Compose defaults"
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+# -- 4. Build and start containers ---------------------------------------------
|
|
|
+Write-Step "Starting Docker services (mode: $Mode)"
|
|
|
+
|
|
|
+$env:ORCHESTRATOR_MODE = $Mode
|
|
|
+
|
|
|
+$envArgs = if (Test-Path $EnvFile) { @("--env-file", $EnvFile) } else { @() }
|
|
|
+& docker compose @envArgs up --build -d
|
|
|
+if ($LASTEXITCODE -ne 0) { Write-Fail "docker compose up failed" }
|
|
|
+Write-OK "Containers started"
|
|
|
+
|
|
|
+# -- 5. Wait for all services to become healthy --------------------------------
|
|
|
+Write-Step "Waiting for services to become healthy"
|
|
|
+
|
|
|
+# Read ports from .env if available; fall back to compose defaults
|
|
|
+function Get-EnvPort($key, $default) {
|
|
|
+ if (Test-Path $EnvFile) {
|
|
|
+ $line = Get-Content $EnvFile | Select-String "^$key=(\d+)"
|
|
|
+ if ($line) { return $line.Matches[0].Groups[1].Value }
|
|
|
+ }
|
|
|
+ return $default
|
|
|
+}
|
|
|
+
|
|
|
+$services = @(
|
|
|
+ @{ Name = "Orchestrator"; Url = "http://localhost:$(Get-EnvPort 'ORCHESTRATOR_PORT' '1717')/health" },
|
|
|
+ @{ Name = "Seq-Interp"; Url = "http://localhost:$(Get-EnvPort 'SEQ_INTERP_PORT' '7475')/health" },
|
|
|
+ @{ Name = "Reconstructor"; Url = "http://localhost:$(Get-EnvPort 'RECONSTRUCTOR_PORT' '8081')/health" },
|
|
|
+ @{ Name = "Spectroscopy"; Url = "http://localhost:$(Get-EnvPort 'SPECTROSCOPY_PORT' '8002')/health" },
|
|
|
+ @{ Name = "Spectrometer"; Url = "http://localhost:$(Get-EnvPort 'SPECTROMETER_PORT' '8000')/api/" }
|
|
|
+)
|
|
|
+
|
|
|
+$maxWait = 120
|
|
|
+$interval = 3
|
|
|
+$elapsed = 0
|
|
|
+
|
|
|
+while ($elapsed -lt $maxWait) {
|
|
|
+ $pending = @()
|
|
|
+ foreach ($svc in $services) {
|
|
|
+ try {
|
|
|
+ $r = Invoke-WebRequest $svc.Url -UseBasicParsing -TimeoutSec 2 -ErrorAction Stop
|
|
|
+ if ($r.StatusCode -lt 400) { continue }
|
|
|
+ } catch {}
|
|
|
+ $pending += $svc.Name
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($pending.Count -eq 0) {
|
|
|
+ Write-OK "All services healthy"
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ Write-Host (" ... {0}/{1} s waiting: {2}" -f $elapsed, $maxWait, ($pending -join ", ")) -ForegroundColor DarkGray
|
|
|
+ Start-Sleep $interval
|
|
|
+ $elapsed += $interval
|
|
|
+}
|
|
|
+
|
|
|
+if ($elapsed -ge $maxWait) {
|
|
|
+ Write-Warn "Some services did not respond within ${maxWait}s -- GUI will start anyway"
|
|
|
+
|
|
|
+ foreach ($svc in $services) {
|
|
|
+ try {
|
|
|
+ $r = Invoke-WebRequest $svc.Url -UseBasicParsing -TimeoutSec 2 -ErrorAction Stop
|
|
|
+ $icon = if ($r.StatusCode -lt 400) { "[OK]" } else { "[--]" }
|
|
|
+ } catch {
|
|
|
+ $icon = "[--]"
|
|
|
+ }
|
|
|
+ Write-Host (" {0,-6} {1,-15} {2}" -f $icon, $svc.Name, $svc.Url) -ForegroundColor $(if ($icon -eq "[OK]") {"Green"} else {"Yellow"})
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+# -- 6. Launch GUI in a separate window ----------------------------------------
|
|
|
+if (-not $ServicesOnly) {
|
|
|
+ Write-Step "Launching GUI"
|
|
|
+
|
|
|
+ if (-not (Test-Path $VenvPython)) {
|
|
|
+ Write-Warn "Venv not found -- falling back to system Python"
|
|
|
+ $VenvPython = "python"
|
|
|
+ }
|
|
|
+ if (-not (Test-Path $AppScript)) {
|
|
|
+ Write-Fail "GUI entry point not found: $AppScript"
|
|
|
+ }
|
|
|
+
|
|
|
+ Start-Process $VenvPython `
|
|
|
+ -ArgumentList "`"$AppScript`"" `
|
|
|
+ -WorkingDirectory $Root `
|
|
|
+ -WindowStyle Normal
|
|
|
+
|
|
|
+ Write-OK "GUI launched"
|
|
|
+}
|
|
|
+
|
|
|
+# -- 7. Summary ----------------------------------------------------------------
|
|
|
+Write-Host ""
|
|
|
+Write-Host "============================================================" -ForegroundColor Green
|
|
|
+Write-Host " LF-MRI platform is running" -ForegroundColor Green
|
|
|
+Write-Host "============================================================" -ForegroundColor Green
|
|
|
+Write-Host ""
|
|
|
+Write-Host " Service endpoints:"
|
|
|
+foreach ($svc in $services) {
|
|
|
+ Write-Host (" {0,-15} {1}" -f $svc.Name, $svc.Url)
|
|
|
+}
|
|
|
+Write-Host ""
|
|
|
+Write-Host " Stop services: .\stop.ps1"
|
|
|
+Write-Host ""
|