524 lines
16 KiB
Batchfile
524 lines
16 KiB
Batchfile
@echo off
|
|
setlocal
|
|
|
|
set "AI_RESTART_ROOT=%~dp0"
|
|
|
|
powershell -NoProfile -ExecutionPolicy Bypass -Command "$p='%~f0'; $s=Get-Content -LiteralPath $p -Raw; $m=':ps1'; $i=$s.LastIndexOf($m); if ($i -lt 0) { throw 'PowerShell payload marker not found.' }; Invoke-Expression $s.Substring($i + $m.Length)"
|
|
set "EXIT_CODE=%ERRORLEVEL%"
|
|
|
|
if "%~1"=="--no-pause" exit /b %EXIT_CODE%
|
|
echo.
|
|
if not "%EXIT_CODE%"=="0" echo [servers] Failed with exit code %EXIT_CODE%.
|
|
pause
|
|
exit /b %EXIT_CODE%
|
|
|
|
:ps1
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
$root = (Resolve-Path -LiteralPath $env:AI_RESTART_ROOT).Path
|
|
$logDir = Join-Path $root ".server-logs"
|
|
New-Item -ItemType Directory -Force -Path $logDir | Out-Null
|
|
|
|
$ports = @(8000, 8001, 8002, 8003, 5173, 5174)
|
|
|
|
function Write-Step {
|
|
param([string] $Message)
|
|
Write-Host "[servers] $Message"
|
|
}
|
|
|
|
function Get-CommandPath {
|
|
param([string[]] $Candidates)
|
|
foreach ($candidate in $Candidates) {
|
|
if ([string]::IsNullOrWhiteSpace($candidate)) {
|
|
continue
|
|
}
|
|
if (Test-Path -LiteralPath $candidate) {
|
|
return (Resolve-Path -LiteralPath $candidate).Path
|
|
}
|
|
$cmd = Get-Command $candidate -ErrorAction SilentlyContinue
|
|
if ($cmd) {
|
|
return $cmd.Source
|
|
}
|
|
}
|
|
return $null
|
|
}
|
|
|
|
function Test-PythonModule {
|
|
param(
|
|
[string] $PythonPath,
|
|
[string] $ModuleName
|
|
)
|
|
|
|
if ([string]::IsNullOrWhiteSpace($PythonPath)) {
|
|
return $false
|
|
}
|
|
|
|
try {
|
|
$proc = Start-Process `
|
|
-FilePath $PythonPath `
|
|
-ArgumentList @("-c", "import $ModuleName") `
|
|
-WindowStyle Hidden `
|
|
-Wait `
|
|
-PassThru
|
|
return $proc.ExitCode -eq 0
|
|
} catch {
|
|
return $false
|
|
}
|
|
}
|
|
|
|
function Resolve-ProjectPython {
|
|
param([string] $ProjectRoot)
|
|
|
|
$projectVenvPython = Join-Path $ProjectRoot ".venv\Scripts\python.exe"
|
|
if (Test-Path -LiteralPath $projectVenvPython) {
|
|
return (Resolve-Path -LiteralPath $projectVenvPython).Path
|
|
}
|
|
|
|
$rootVenvPython = Join-Path $root ".venv\Scripts\python.exe"
|
|
if (Test-Path -LiteralPath $rootVenvPython) {
|
|
return (Resolve-Path -LiteralPath $rootVenvPython).Path
|
|
}
|
|
|
|
$candidates = @()
|
|
|
|
if ($env:VIRTUAL_ENV) {
|
|
$candidates += Join-Path $env:VIRTUAL_ENV "Scripts\python.exe"
|
|
}
|
|
|
|
$pyLauncher = Get-CommandPath @("py.exe", "py")
|
|
if ($pyLauncher) {
|
|
try {
|
|
$resolvedFromPy = & $pyLauncher -c "import sys; print(sys.executable)" 2>$null
|
|
if ($LASTEXITCODE -eq 0 -and -not [string]::IsNullOrWhiteSpace($resolvedFromPy)) {
|
|
$candidates += $resolvedFromPy.Trim()
|
|
}
|
|
} catch {
|
|
}
|
|
}
|
|
|
|
$pathPython = Get-CommandPath @("python.exe", "python")
|
|
if ($pathPython) {
|
|
$candidates += $pathPython
|
|
}
|
|
|
|
$seen = @{}
|
|
foreach ($candidate in $candidates) {
|
|
if ([string]::IsNullOrWhiteSpace($candidate)) {
|
|
continue
|
|
}
|
|
|
|
$resolved = $null
|
|
if (Test-Path -LiteralPath $candidate) {
|
|
$resolved = (Resolve-Path -LiteralPath $candidate).Path
|
|
} else {
|
|
$cmd = Get-Command $candidate -ErrorAction SilentlyContinue
|
|
if ($cmd) {
|
|
$resolved = $cmd.Source
|
|
}
|
|
}
|
|
|
|
if (-not $resolved -or $seen.ContainsKey($resolved)) {
|
|
continue
|
|
}
|
|
$seen[$resolved] = $true
|
|
|
|
if (Test-PythonModule -PythonPath $resolved -ModuleName "uvicorn") {
|
|
return $resolved
|
|
}
|
|
}
|
|
|
|
return $null
|
|
}
|
|
|
|
function Resolve-SystemPython {
|
|
$candidates = @()
|
|
|
|
if ($env:VIRTUAL_ENV) {
|
|
$candidates += Join-Path $env:VIRTUAL_ENV "Scripts\python.exe"
|
|
}
|
|
|
|
$pathPython = Get-CommandPath @("python.exe", "python")
|
|
if ($pathPython) {
|
|
$candidates += $pathPython
|
|
}
|
|
|
|
$pyLauncher = Get-CommandPath @("py.exe", "py")
|
|
if ($pyLauncher) {
|
|
try {
|
|
$resolvedFromPy = & $pyLauncher -c "import sys; print(sys.executable)" 2>$null
|
|
if ($LASTEXITCODE -eq 0 -and -not [string]::IsNullOrWhiteSpace($resolvedFromPy)) {
|
|
$candidates += $resolvedFromPy.Trim()
|
|
}
|
|
} catch {
|
|
}
|
|
}
|
|
|
|
$seen = @{}
|
|
foreach ($candidate in $candidates) {
|
|
if ([string]::IsNullOrWhiteSpace($candidate)) {
|
|
continue
|
|
}
|
|
$resolved = $null
|
|
if (Test-Path -LiteralPath $candidate) {
|
|
$resolved = (Resolve-Path -LiteralPath $candidate).Path
|
|
} else {
|
|
$cmd = Get-Command $candidate -ErrorAction SilentlyContinue
|
|
if ($cmd) {
|
|
$resolved = $cmd.Source
|
|
}
|
|
}
|
|
if (-not $resolved -or $seen.ContainsKey($resolved)) {
|
|
continue
|
|
}
|
|
$seen[$resolved] = $true
|
|
return $resolved
|
|
}
|
|
|
|
return $null
|
|
}
|
|
|
|
function Ensure-ProjectVenv {
|
|
param([string] $ProjectRoot)
|
|
|
|
$venvPython = Join-Path $ProjectRoot ".venv\Scripts\python.exe"
|
|
if (Test-PythonModule -PythonPath $venvPython -ModuleName "uvicorn") {
|
|
return $venvPython
|
|
}
|
|
|
|
$bootstrapPython = Resolve-SystemPython
|
|
if (-not $bootstrapPython) {
|
|
Write-Step "No system Python was found for bootstrapping the backend venv."
|
|
return $null
|
|
}
|
|
|
|
Write-Step "Bootstrapping backend virtual environment in $ProjectRoot\\.venv"
|
|
|
|
if (-not (Test-Path -LiteralPath $venvPython)) {
|
|
Invoke-LoggedCommand `
|
|
-FilePath $bootstrapPython `
|
|
-Arguments @("-m", "venv", ".venv") `
|
|
-WorkingDirectory $ProjectRoot `
|
|
-LogName "backend-venv-create.log"
|
|
}
|
|
|
|
Invoke-LoggedCommand `
|
|
-FilePath $venvPython `
|
|
-Arguments @("-m", "pip", "install", "--upgrade", "pip") `
|
|
-WorkingDirectory $ProjectRoot `
|
|
-LogName "backend-pip-upgrade.log"
|
|
|
|
Invoke-LoggedCommand `
|
|
-FilePath $venvPython `
|
|
-Arguments @("-m", "pip", "install", "-e", ".") `
|
|
-WorkingDirectory $ProjectRoot `
|
|
-LogName "backend-pip-install.log"
|
|
|
|
if (Test-PythonModule -PythonPath $venvPython -ModuleName "uvicorn") {
|
|
return $venvPython
|
|
}
|
|
|
|
Write-Step "Backend venv was created, but uvicorn is still unavailable. Check .server-logs\\backend-*.log"
|
|
return $null
|
|
}
|
|
|
|
function Read-DotEnv {
|
|
param([string] $Path)
|
|
|
|
$result = @{}
|
|
if (-not (Test-Path -LiteralPath $Path)) {
|
|
return $result
|
|
}
|
|
|
|
foreach ($line in Get-Content -LiteralPath $Path) {
|
|
if ($line -match "^\s*$" -or $line -match "^\s*#") {
|
|
continue
|
|
}
|
|
if ($line -match "^\s*([^#=\s]+)\s*=\s*(.*)\s*$") {
|
|
$key = $matches[1]
|
|
$value = $matches[2].Trim()
|
|
if (
|
|
($value.StartsWith('"') -and $value.EndsWith('"')) -or
|
|
($value.StartsWith("'") -and $value.EndsWith("'"))
|
|
) {
|
|
$value = $value.Substring(1, $value.Length - 2)
|
|
}
|
|
$result[$key] = $value
|
|
}
|
|
}
|
|
return $result
|
|
}
|
|
|
|
function Stop-ExistingServers {
|
|
Write-Step "Stopping existing project servers."
|
|
|
|
$currentPid = $PID
|
|
$rootPattern = [regex]::Escape($root)
|
|
$commandPattern = "uvicorn|ont_platform\.api|phase[0-9]_app|vite|npm(\.cmd)?\s+run\s+(dev|preview|start)"
|
|
|
|
$matched = Get-CimInstance Win32_Process | Where-Object {
|
|
if (-not $_.CommandLine) {
|
|
return $false
|
|
}
|
|
if ($_.ProcessId -eq $currentPid) {
|
|
return $false
|
|
}
|
|
$cmd = $_.CommandLine
|
|
return ($cmd -match $commandPattern) -and (
|
|
$cmd -match $rootPattern -or
|
|
$cmd -match "ontology_platform|ont_platform"
|
|
)
|
|
}
|
|
|
|
foreach ($proc in $matched) {
|
|
Write-Step ("Killing process {0} ({1})" -f $proc.ProcessId, $proc.Name)
|
|
Stop-Process -Id $proc.ProcessId -Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
$listeners = Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue |
|
|
Where-Object { $ports -contains $_.LocalPort } |
|
|
Select-Object -ExpandProperty OwningProcess -Unique
|
|
|
|
foreach ($pidToStop in $listeners) {
|
|
if ($pidToStop -and $pidToStop -ne $currentPid) {
|
|
Write-Step ("Killing listener on configured dev port, pid {0}" -f $pidToStop)
|
|
Stop-Process -Id $pidToStop -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
Start-Sleep -Milliseconds 800
|
|
}
|
|
|
|
function Invoke-LoggedCommand {
|
|
param(
|
|
[string] $FilePath,
|
|
[string[]] $Arguments,
|
|
[string] $WorkingDirectory,
|
|
[string] $LogName
|
|
)
|
|
|
|
$logPath = Join-Path $logDir $LogName
|
|
$stderrPath = Join-Path $logDir ($LogName -replace "\.log$", ".stderr.log")
|
|
Write-Step ("Running {0} {1}" -f (Split-Path -Leaf $FilePath), ($Arguments -join " "))
|
|
|
|
Remove-Item -LiteralPath $logPath, $stderrPath -Force -ErrorAction SilentlyContinue
|
|
$proc = Start-Process `
|
|
-FilePath $FilePath `
|
|
-ArgumentList $Arguments `
|
|
-WorkingDirectory $WorkingDirectory `
|
|
-RedirectStandardOutput $logPath `
|
|
-RedirectStandardError $stderrPath `
|
|
-WindowStyle Hidden `
|
|
-Wait `
|
|
-PassThru
|
|
|
|
$exitCode = $proc.ExitCode
|
|
|
|
if ($exitCode -ne 0) {
|
|
throw "Command failed with exit code $exitCode. See $logPath and $stderrPath"
|
|
}
|
|
}
|
|
|
|
function Start-LoggedServer {
|
|
param(
|
|
[string] $Name,
|
|
[int] $Port,
|
|
[string] $FilePath,
|
|
[string[]] $Arguments,
|
|
[string] $WorkingDirectory,
|
|
[hashtable] $Environment = @{}
|
|
)
|
|
|
|
$stdout = Join-Path $logDir "$Name-stdout.log"
|
|
$stderr = Join-Path $logDir "$Name-stderr.log"
|
|
Remove-Item -LiteralPath $stdout, $stderr -Force -ErrorAction SilentlyContinue
|
|
|
|
$previous = @{}
|
|
foreach ($key in $Environment.Keys) {
|
|
$previous[$key] = [Environment]::GetEnvironmentVariable($key, "Process")
|
|
[Environment]::SetEnvironmentVariable($key, [string] $Environment[$key], "Process")
|
|
}
|
|
|
|
try {
|
|
$proc = Start-Process `
|
|
-FilePath $FilePath `
|
|
-ArgumentList $Arguments `
|
|
-WorkingDirectory $WorkingDirectory `
|
|
-RedirectStandardOutput $stdout `
|
|
-RedirectStandardError $stderr `
|
|
-WindowStyle Hidden `
|
|
-PassThru
|
|
} finally {
|
|
foreach ($key in $Environment.Keys) {
|
|
[Environment]::SetEnvironmentVariable($key, $previous[$key], "Process")
|
|
}
|
|
}
|
|
|
|
Write-Step ("Started {0}: pid {1}" -f $Name, $proc.Id)
|
|
return [pscustomobject]@{
|
|
Name = $Name
|
|
Port = $Port
|
|
Pid = $proc.Id
|
|
Stdout = $stdout
|
|
Stderr = $stderr
|
|
}
|
|
}
|
|
|
|
function Clear-ServerLogs {
|
|
param([string] $Name)
|
|
|
|
Remove-Item -LiteralPath `
|
|
(Join-Path $logDir "$Name-stdout.log"), `
|
|
(Join-Path $logDir "$Name-stderr.log") `
|
|
-Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
function Wait-Port {
|
|
param(
|
|
[int] $Port,
|
|
[int] $Seconds = 20
|
|
)
|
|
|
|
$deadline = (Get-Date).AddSeconds($Seconds)
|
|
while ((Get-Date) -lt $deadline) {
|
|
try {
|
|
$client = [System.Net.Sockets.TcpClient]::new()
|
|
$async = $client.BeginConnect("127.0.0.1", $Port, $null, $null)
|
|
if ($async.AsyncWaitHandle.WaitOne(500)) {
|
|
$client.EndConnect($async)
|
|
$client.Close()
|
|
return $true
|
|
}
|
|
$client.Close()
|
|
} catch {
|
|
Start-Sleep -Milliseconds 500
|
|
}
|
|
}
|
|
return $false
|
|
}
|
|
|
|
function Resolve-FrontendDevCommand {
|
|
param(
|
|
[string] $FrontendDir,
|
|
[string] $NpmPath
|
|
)
|
|
|
|
$node = Get-CommandPath @("node.exe", "node")
|
|
$viteCli = Join-Path $FrontendDir "node_modules\vite\bin\vite.js"
|
|
|
|
if ($node -and (Test-Path -LiteralPath $viteCli)) {
|
|
return [pscustomobject]@{
|
|
FilePath = $node
|
|
Arguments = @($viteCli, "--host", "127.0.0.1", "--port", "8000")
|
|
}
|
|
}
|
|
|
|
return [pscustomobject]@{
|
|
FilePath = $NpmPath
|
|
Arguments = @("run", "dev", "--", "--host", "127.0.0.1", "--port", "8000")
|
|
}
|
|
}
|
|
|
|
function Start-Neo4jIfAvailable {
|
|
$composeFile = Join-Path $root "docker-compose.neo4j.yml"
|
|
if (-not (Test-Path -LiteralPath $composeFile)) {
|
|
return
|
|
}
|
|
|
|
$docker = Get-CommandPath @("docker")
|
|
if (-not $docker) {
|
|
Write-Step "Docker was not found; skipping Neo4j compose service."
|
|
return
|
|
}
|
|
|
|
try {
|
|
Invoke-LoggedCommand -FilePath $docker -Arguments @("compose", "-f", $composeFile, "down") -WorkingDirectory $root -LogName "neo4j-down.log"
|
|
Invoke-LoggedCommand -FilePath $docker -Arguments @("compose", "-f", $composeFile, "up", "-d") -WorkingDirectory $root -LogName "neo4j-up.log"
|
|
Write-Step "Neo4j compose service requested."
|
|
} catch {
|
|
Write-Step "Neo4j compose restart skipped/failed. See .server-logs\\neo4j-*.log"
|
|
}
|
|
}
|
|
|
|
Stop-ExistingServers
|
|
Start-Neo4jIfAvailable
|
|
|
|
$npm = Get-CommandPath @("npm.cmd", "npm")
|
|
$frontendDir = Join-Path $root "ontology_platform\web\frontend"
|
|
$frontendReady = $false
|
|
if ((Test-Path -LiteralPath (Join-Path $frontendDir "package.json")) -and $npm) {
|
|
if (-not (Test-Path -LiteralPath (Join-Path $frontendDir "node_modules"))) {
|
|
Invoke-LoggedCommand -FilePath $npm -Arguments @("ci") -WorkingDirectory $frontendDir -LogName "frontend-npm-ci.log"
|
|
}
|
|
Invoke-LoggedCommand -FilePath $npm -Arguments @("run", "build") -WorkingDirectory $frontendDir -LogName "frontend-build.log"
|
|
$frontendReady = $true
|
|
} elseif (Test-Path -LiteralPath (Join-Path $frontendDir "package.json")) {
|
|
Write-Step "npm was not found; skipping frontend rebuild."
|
|
}
|
|
|
|
$servers = @()
|
|
|
|
$ontologyRoot = Join-Path $root "ontology_platform"
|
|
$ontologyPython = Resolve-ProjectPython -ProjectRoot $ontologyRoot
|
|
|
|
if ((-not $ontologyPython) -and (Test-Path -LiteralPath (Join-Path $ontologyRoot "pyproject.toml"))) {
|
|
$ontologyPython = Ensure-ProjectVenv -ProjectRoot $ontologyRoot
|
|
}
|
|
|
|
if ((Test-Path -LiteralPath (Join-Path $ontologyRoot "ont_platform\api\main.py")) -and $ontologyPython) {
|
|
$ontologyEnv = Read-DotEnv -Path (Join-Path $ontologyRoot ".env")
|
|
$ontologyEnv["PHASE"] = "6"
|
|
$ontologyEnv["HOST"] = "127.0.0.1"
|
|
$ontologyEnv["PORT"] = "8001"
|
|
|
|
$servers += Start-LoggedServer `
|
|
-Name "ontology-platform-8001" `
|
|
-Port 8001 `
|
|
-FilePath $ontologyPython `
|
|
-Arguments @("-m", "uvicorn", "ont_platform.api.main:app", "--host", "127.0.0.1", "--port", "8001") `
|
|
-WorkingDirectory $ontologyRoot `
|
|
-Environment $ontologyEnv
|
|
} else {
|
|
Clear-ServerLogs -Name "ontology-platform-8001"
|
|
Write-Step "ontology_platform server entrypoint was not found, or no Python with uvicorn is available for this project."
|
|
Write-Step "Create a venv under ontology_platform\\.venv (or .\\.venv) and install dependencies before rerunning."
|
|
}
|
|
|
|
if ($frontendReady) {
|
|
$frontendDev = Resolve-FrontendDevCommand -FrontendDir $frontendDir -NpmPath $npm
|
|
$servers += Start-LoggedServer `
|
|
-Name "ontology-frontend-8000" `
|
|
-Port 8000 `
|
|
-FilePath $frontendDev.FilePath `
|
|
-Arguments $frontendDev.Arguments `
|
|
-WorkingDirectory $frontendDir
|
|
}
|
|
|
|
$pidFile = Join-Path $logDir "server-pids.txt"
|
|
$servers | ForEach-Object {
|
|
"{0} pid={1} stdout={2} stderr={3}" -f $_.Name, $_.Pid, $_.Stdout, $_.Stderr
|
|
} | Set-Content -LiteralPath $pidFile -Encoding UTF8
|
|
|
|
$missingPorts = @()
|
|
foreach ($port in ($servers | Select-Object -ExpandProperty Port -Unique)) {
|
|
if (Wait-Port -Port $port -Seconds 120) {
|
|
Write-Step ("Port {0} is listening." -f $port)
|
|
} else {
|
|
Write-Step ("Port {0} did not open. Check logs in {1}" -f $port, $logDir)
|
|
$missingPorts += $port
|
|
}
|
|
}
|
|
|
|
if ($missingPorts.Count -gt 0) {
|
|
throw "Some servers did not start: ports $($missingPorts -join ', ')"
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Step "Done."
|
|
if ($servers | Where-Object { $_.Port -eq 8000 }) {
|
|
Write-Host " Ontology UI: http://127.0.0.1:8000/static/"
|
|
}
|
|
if ($servers | Where-Object { $_.Port -eq 8001 }) {
|
|
Write-Host " Ontology API: http://127.0.0.1:8001/docs"
|
|
}
|
|
Write-Host " Logs: $logDir"
|