start.ps1 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. # ==============================================================================
  2. # lf_mri_platform -- Start services and GUI
  3. # Usage:
  4. # .\start.ps1 -- plug mode: Docker + GUI
  5. # .\start.ps1 -Mode real -- real mode: Docker + spectrometer + GUI
  6. # .\start.ps1 -GuiOnly -- GUI only (services already running)
  7. # .\start.ps1 -ServicesOnly -- Docker + spectrometer, no GUI
  8. # .\start.ps1 -SkipInstall -- skip venv check
  9. # .\start.ps1 -SkipSpectrometer -- skip native spectrometer even in real mode
  10. # ==============================================================================
  11. param(
  12. [ValidateSet("plug", "real")]
  13. [string]$Mode = "plug",
  14. [switch]$GuiOnly,
  15. [switch]$ServicesOnly,
  16. [switch]$SkipInstall,
  17. [switch]$SkipSpectrometer
  18. )
  19. $ErrorActionPreference = "Stop"
  20. $Root = $PSScriptRoot
  21. $GuiDir = Join-Path $Root "apps\gui"
  22. $VenvPython = Join-Path $GuiDir ".venv\Scripts\python.exe"
  23. $AppScript = Join-Path $Root "apps\gui\app.py"
  24. $EnvFile = Join-Path $Root ".env"
  25. $EnvExample = Join-Path $Root ".env.example"
  26. function Write-Step($msg) { Write-Host "`n==> $msg" -ForegroundColor Cyan }
  27. function Write-OK($msg) { Write-Host " [OK] $msg" -ForegroundColor Green }
  28. function Write-Warn($msg) { Write-Host " [!!] $msg" -ForegroundColor Yellow }
  29. function Write-Fail($msg) {
  30. Write-Host "`n[FAIL] $msg" -ForegroundColor Red
  31. Write-Host "`n Press Enter to close..." -ForegroundColor DarkGray
  32. $null = Read-Host
  33. exit 1
  34. }
  35. # Catch any unhandled terminating error — keep window open so user can read it
  36. trap {
  37. Write-Host "`n[ERROR] $_" -ForegroundColor Red
  38. Write-Host "`n Press Enter to close..." -ForegroundColor DarkGray
  39. $null = Read-Host
  40. exit 1
  41. }
  42. function Get-EnvPort($key, $default) {
  43. if (Test-Path $EnvFile) {
  44. $line = Get-Content $EnvFile | Select-String "^$key=(\d+)"
  45. if ($line) { return $line.Matches[0].Groups[1].Value }
  46. }
  47. return $default
  48. }
  49. # -- 1. Check / install GUI venv -----------------------------------------------
  50. if (-not $GuiOnly -and -not $SkipInstall) {
  51. Write-Step "Checking GUI environment"
  52. $needsInstall = $false
  53. if (-not (Test-Path $VenvPython)) {
  54. Write-Warn "Virtual environment not found -- running install"
  55. $needsInstall = $true
  56. } else {
  57. & $VenvPython -c "import PySide6" 2>$null
  58. if ($LASTEXITCODE -ne 0) {
  59. Write-Warn "GUI packages missing -- running install"
  60. $needsInstall = $true
  61. } else {
  62. Write-OK "Virtual environment ready"
  63. }
  64. }
  65. if ($needsInstall) {
  66. $installScript = Join-Path $Root "install.ps1"
  67. if (-not (Test-Path $installScript)) { Write-Fail "install.ps1 not found" }
  68. & powershell.exe -ExecutionPolicy Bypass -File $installScript
  69. if ($LASTEXITCODE -ne 0) { Write-Fail "install.ps1 failed" }
  70. }
  71. }
  72. # -- 2. Docker -----------------------------------------------------------------
  73. if (-not $GuiOnly) {
  74. Write-Step "Checking Docker"
  75. $dockerOk = $false
  76. try { & docker info *>$null; $dockerOk = $true } catch {}
  77. if (-not $dockerOk) {
  78. Write-Warn "Docker not responding -- starting Docker Desktop..."
  79. $dockerExe = "C:\Program Files\Docker\Docker\Docker Desktop.exe"
  80. if (Test-Path $dockerExe) { Start-Process $dockerExe }
  81. else { Write-Fail "Docker Desktop not found -- install from https://docker.com" }
  82. $waited = 0
  83. while ($waited -lt 60) {
  84. Start-Sleep 5; $waited += 5
  85. try { & docker info *>$null; $dockerOk = $true; break } catch {}
  86. Write-Host " ... $waited/60 s" -ForegroundColor DarkGray
  87. }
  88. if (-not $dockerOk) { Write-Fail "Docker did not start in time" }
  89. }
  90. Write-OK "Docker is running"
  91. # -- 3. .env ---------------------------------------------------------------
  92. if (-not (Test-Path $EnvFile)) {
  93. if (Test-Path $EnvExample) { Copy-Item $EnvExample $EnvFile; Write-Warn ".env created from .env.example" }
  94. else { Write-Warn ".env missing -- using Compose defaults" }
  95. }
  96. # -- 4. Start containers ---------------------------------------------------
  97. Write-Step "Starting Docker services (mode: $Mode)"
  98. $env:ORCHESTRATOR_MODE = $Mode
  99. $envArgs = if (Test-Path $EnvFile) { @("--env-file", $EnvFile) } else { @() }
  100. & docker compose @envArgs up -d
  101. if ($LASTEXITCODE -ne 0) { Write-Fail "docker compose up failed" }
  102. Write-OK "Containers started"
  103. # -- 5. Native spectrometer (real mode only) --------------------------------
  104. if ($Mode -eq "real" -and -not $SkipSpectrometer) {
  105. Write-Step "Starting native spectrometer"
  106. $SpecDir = Join-Path $Root "services\spectrometer"
  107. $SpecVenv = Join-Path $SpecDir "mvenv\Scripts\python.exe"
  108. $PicoExe = Join-Path $SpecDir "bin\pico-tcp.exe"
  109. # Kill stale pico-tcp
  110. $oldPico = Get-Process -Name "pico-tcp" -ErrorAction SilentlyContinue
  111. if ($oldPico) { $oldPico | Stop-Process -Force; Write-Warn "Killed stale pico-tcp.exe" }
  112. # Create venv on first run
  113. if (-not (Test-Path $SpecVenv)) {
  114. Write-Warn "Spectrometer venv not found -- creating..."
  115. Push-Location $SpecDir
  116. & python -m venv mvenv
  117. if ($LASTEXITCODE -ne 0) { Write-Fail "Failed to create spectrometer venv" }
  118. & $SpecVenv -m pip install -q --upgrade pip
  119. & $SpecVenv -m pip install -q -r requirements.txt
  120. if ($LASTEXITCODE -ne 0) { Write-Fail "Failed to install spectrometer packages" }
  121. Pop-Location
  122. Write-OK "Spectrometer venv created"
  123. } else {
  124. Write-OK "Spectrometer venv found"
  125. }
  126. # Migrations
  127. Push-Location $SpecDir
  128. & $SpecVenv manage.py migrate --noinput 2>&1 | Out-Null
  129. Pop-Location
  130. Write-OK "Migrations applied"
  131. # pico-tcp.exe
  132. if (Test-Path $PicoExe) { Start-Process $PicoExe -WindowStyle Hidden; Write-OK "pico-tcp.exe started" }
  133. else { Write-Warn "bin\pico-tcp.exe not found -- ADC proxy not started" }
  134. # Django runserver
  135. $running = Get-Process -Name python* -ErrorAction SilentlyContinue |
  136. Where-Object { $_.CommandLine -like "*manage.py*runserver*" }
  137. if ($running) {
  138. Write-OK "Spectrometer already running (PID $($running.Id))"
  139. } else {
  140. Start-Process $SpecVenv -ArgumentList "manage.py runserver 0.0.0.0:8000" `
  141. -WorkingDirectory $SpecDir -WindowStyle Normal
  142. Write-OK "Spectrometer started in new window"
  143. }
  144. }
  145. # -- 6. Health check -------------------------------------------------------
  146. Write-Step "Waiting for services to become healthy"
  147. $checkSpec = ($Mode -eq "real") -and (-not $SkipSpectrometer)
  148. $services = @(
  149. @{ Name = "Orchestrator"; Url = "http://localhost:$(Get-EnvPort 'ORCHESTRATOR_PORT' '1717')/health"; Required = $true },
  150. @{ Name = "Seq-Interp"; Url = "http://localhost:$(Get-EnvPort 'SEQ_INTERP_PORT' '7475')/health"; Required = $true },
  151. @{ Name = "Reconstructor"; Url = "http://localhost:$(Get-EnvPort 'RECONSTRUCTOR_PORT' '8081')/health"; Required = $true },
  152. @{ Name = "Spectroscopy"; Url = "http://localhost:$(Get-EnvPort 'SPECTROSCOPY_PORT' '8002')/health"; Required = $true },
  153. @{ Name = "Spectrometer*"; Url = "http://localhost:$(Get-EnvPort 'SPECTROMETER_PORT' '8000')/api/"; Required = $checkSpec }
  154. )
  155. $maxWait = 120; $interval = 3; $elapsed = 0
  156. while ($elapsed -lt $maxWait) {
  157. $pending = $services | Where-Object { $_.Required } | Where-Object {
  158. try { (Invoke-WebRequest $_.Url -UseBasicParsing -TimeoutSec 2 -ErrorAction Stop).StatusCode -ge 400 }
  159. catch { $true }
  160. }
  161. if (-not $pending) { Write-OK "All required services healthy"; break }
  162. Write-Host (" ... {0}/{1} s waiting: {2}" -f $elapsed, $maxWait, ($pending.Name -join ", ")) -ForegroundColor DarkGray
  163. Start-Sleep $interval; $elapsed += $interval
  164. }
  165. if ($elapsed -ge $maxWait) { Write-Warn "Some services did not respond in time -- continuing anyway" }
  166. Write-Host ""
  167. foreach ($svc in $services) {
  168. $ok = try { (Invoke-WebRequest $svc.Url -UseBasicParsing -TimeoutSec 2 -ErrorAction Stop).StatusCode -lt 400 } catch { $false }
  169. $icon = if ($ok) { "[OK]" } else { "[--]" }
  170. $color = if ($ok) { "Green" } elseif ($svc.Required) { "Yellow" } else { "DarkGray" }
  171. $suffix = if (-not $svc.Required -and -not $ok) { " (native — start manually if needed)" } else { "" }
  172. Write-Host (" {0,-6} {1,-16} {2}{3}" -f $icon, $svc.Name, $svc.Url, $suffix) -ForegroundColor $color
  173. }
  174. # -- 7. Write server_config.json -------------------------------------------
  175. Write-Step "Configuring GUI"
  176. $orchPort = Get-EnvPort "ORCHESTRATOR_PORT" "1717"
  177. $seqPort = Get-EnvPort "SEQ_INTERP_PORT" "7475"
  178. $spectPort = Get-EnvPort "SPECTROSCOPY_PORT" "8002"
  179. $cfgPath = Join-Path $Root "apps\gui\cfg\server_config.json"
  180. @{
  181. srv_name = "srv_interp"
  182. log_dir = "log"
  183. upload_dir = "data/input"
  184. output_dir = "data/output"
  185. server_host = "0.0.0.0"
  186. server_port = [int]$seqPort
  187. orchestrator_url = "http://localhost:$orchPort"
  188. seq_interp_url = "http://localhost:$seqPort"
  189. spectroscopy_url = "http://localhost:$spectPort"
  190. mode = $Mode
  191. } | ConvertTo-Json -Depth 3 | Set-Content $cfgPath -Encoding utf8
  192. Write-OK "server_config.json updated (mode=$Mode, ports: orch=$orchPort seq=$seqPort spectro=$spectPort)"
  193. }
  194. # -- 8. Launch GUI -------------------------------------------------------------
  195. if (-not $ServicesOnly) {
  196. Write-Step "Launching GUI"
  197. if (-not (Test-Path $VenvPython)) {
  198. Write-Warn "Venv not found -- falling back to system Python"
  199. $VenvPython = "python"
  200. }
  201. if (-not (Test-Path $AppScript)) { Write-Fail "GUI entry point not found: $AppScript" }
  202. Start-Process $VenvPython -ArgumentList "`"$AppScript`"" -WorkingDirectory $Root -WindowStyle Normal
  203. Write-OK "GUI launched"
  204. }
  205. # -- Summary -------------------------------------------------------------------
  206. Write-Host ""
  207. Write-Host "============================================================" -ForegroundColor Green
  208. Write-Host (" LF-MRI platform is running [{0} mode]" -f $Mode.ToUpper()) -ForegroundColor Green
  209. Write-Host "============================================================" -ForegroundColor Green
  210. Write-Host ""
  211. if ($Mode -eq "real") {
  212. Write-Host " Stop spectrometer: close the spectrometer terminal window"
  213. Write-Host " Stop pico-tcp: services\spectrometer\autokill.bat"
  214. }
  215. Write-Host " Stop all: .\stop.ps1"
  216. Write-Host " Update code: .\update.bat"
  217. Write-Host ""
  218. Write-Host " Press Enter to close this window..." -ForegroundColor DarkGray
  219. $null = Read-Host