Initial SFERA platform baseline
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
param(
|
||||
[switch]$SkipFrontendBuild,
|
||||
[switch]$SkipSmoke,
|
||||
[string]$WebUrl = "http://127.0.0.1:3000",
|
||||
[string]$WebReadyUrl = "http://127.0.0.1:3000/editor",
|
||||
[string]$ApiHealthUrl = "http://127.0.0.1:8000/health"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Step($Name) {
|
||||
Write-Output ""
|
||||
Write-Output "==> $Name"
|
||||
}
|
||||
|
||||
function Stop-SferaNextProcesses {
|
||||
$processes = Get-CimInstance Win32_Process -Filter "name = 'node.exe'" |
|
||||
Where-Object {
|
||||
$_.CommandLine -match 'sfera-web' -and (
|
||||
$_.CommandLine -match 'next.*start' -or
|
||||
$_.CommandLine -match 'next.*build' -or
|
||||
$_.CommandLine -match 'npm-cli\.js.*run start'
|
||||
)
|
||||
}
|
||||
if ($processes) {
|
||||
Write-Output "Stopping running SFERA Next.js processes."
|
||||
$processes | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
}
|
||||
|
||||
function Stop-SferaApiProcesses {
|
||||
$processes = Get-CimInstance Win32_Process |
|
||||
Where-Object {
|
||||
($_.Name -in @("python.exe", "uvicorn.exe")) -and
|
||||
$_.CommandLine -match 'api_server\.main:app' -and
|
||||
$_.CommandLine -match 'SFERA'
|
||||
}
|
||||
if ($processes) {
|
||||
Write-Output "Stopping SFERA API smoke processes."
|
||||
$processes | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
}
|
||||
|
||||
function Wait-HttpOk($Url, $TimeoutSeconds = 30) {
|
||||
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
||||
do {
|
||||
try {
|
||||
$response = Invoke-WebRequest -UseBasicParsing -Uri $Url -TimeoutSec 15
|
||||
if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 500) {
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
Start-Sleep -Milliseconds 500
|
||||
}
|
||||
} while ((Get-Date) -lt $deadline)
|
||||
|
||||
throw "Timed out waiting for $Url"
|
||||
}
|
||||
|
||||
function Wait-TcpEndpoint($Url, $TimeoutSeconds = 30) {
|
||||
$uri = [System.Uri]$Url
|
||||
$hostName = $uri.Host
|
||||
$port = $uri.Port
|
||||
if ($port -le 0) {
|
||||
$port = if ($uri.Scheme -eq "https") { 443 } else { 80 }
|
||||
}
|
||||
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
||||
do {
|
||||
$client = [System.Net.Sockets.TcpClient]::new()
|
||||
try {
|
||||
$async = $client.BeginConnect($hostName, $port, $null, $null)
|
||||
if ($async.AsyncWaitHandle.WaitOne(2000)) {
|
||||
$client.EndConnect($async)
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
Start-Sleep -Milliseconds 500
|
||||
} finally {
|
||||
$client.Close()
|
||||
}
|
||||
} while ((Get-Date) -lt $deadline)
|
||||
|
||||
throw "Timed out waiting for TCP $hostName`:$port"
|
||||
}
|
||||
|
||||
$Root = Resolve-Path (Join-Path $PSScriptRoot "..")
|
||||
$RustRoot = Join-Path $Root "rust"
|
||||
$FrontendRoot = "R:\codex\SFERA\frontend\sfera-web"
|
||||
$StartedApiForSmoke = $false
|
||||
$StartedWebForSmoke = $false
|
||||
|
||||
if (-not (Test-Path $FrontendRoot)) {
|
||||
$FrontendRoot = Join-Path $Root "frontend\sfera-web"
|
||||
}
|
||||
|
||||
Step "Rust tests and BSL parser build"
|
||||
Push-Location $RustRoot
|
||||
cargo test
|
||||
cargo build -p bsl-parser
|
||||
Pop-Location
|
||||
|
||||
Step "Python and API tests"
|
||||
Push-Location $Root
|
||||
uv run pytest
|
||||
Pop-Location
|
||||
|
||||
Step "Frontend typecheck"
|
||||
Push-Location $FrontendRoot
|
||||
npm run typecheck
|
||||
|
||||
if (-not $SkipFrontendBuild) {
|
||||
Step "Frontend production build"
|
||||
Stop-SferaNextProcesses
|
||||
$env:NEXT_TELEMETRY_DISABLED = "1"
|
||||
node_modules\.bin\next.cmd build
|
||||
}
|
||||
|
||||
if (-not $SkipSmoke) {
|
||||
Step "Frontend smoke checks"
|
||||
try {
|
||||
Wait-HttpOk $ApiHealthUrl 5
|
||||
} catch {
|
||||
Write-Host "Starting API server for smoke checks."
|
||||
$apiStdout = Join-Path $Root ".sfera\api-stdout.log"
|
||||
$apiStderr = Join-Path $Root ".sfera\api-stderr.log"
|
||||
Start-Process -FilePath "uv.exe" `
|
||||
-ArgumentList @("run", "uvicorn", "api_server.main:app", "--app-dir", "services/api-server/src", "--host", "0.0.0.0", "--port", "8000") `
|
||||
-WorkingDirectory $Root `
|
||||
-RedirectStandardOutput $apiStdout `
|
||||
-RedirectStandardError $apiStderr
|
||||
$StartedApiForSmoke = $true
|
||||
Wait-HttpOk $ApiHealthUrl 120
|
||||
}
|
||||
|
||||
$env:SFERA_WEB_URL = $WebUrl
|
||||
Stop-SferaNextProcesses
|
||||
$runningNext = Get-CimInstance Win32_Process -Filter "name = 'node.exe'" |
|
||||
Where-Object { $_.CommandLine -match 'next.*start' -and $_.CommandLine -match 'sfera-web' }
|
||||
if (-not $runningNext) {
|
||||
$stdout = Join-Path $FrontendRoot ".next\start-stdout.log"
|
||||
$stderr = Join-Path $FrontendRoot ".next\start-stderr.log"
|
||||
Start-Process -FilePath "npm.cmd" `
|
||||
-ArgumentList @("run", "start", "--", "--hostname", "0.0.0.0", "--port", "3000") `
|
||||
-WorkingDirectory $FrontendRoot `
|
||||
-RedirectStandardOutput $stdout `
|
||||
-RedirectStandardError $stderr
|
||||
$StartedWebForSmoke = $true
|
||||
Wait-TcpEndpoint $WebUrl 45
|
||||
}
|
||||
npm run smoke:editor
|
||||
npm run smoke:editor:runtime
|
||||
|
||||
if ($StartedWebForSmoke) {
|
||||
Stop-SferaNextProcesses
|
||||
}
|
||||
if ($StartedApiForSmoke) {
|
||||
Stop-SferaApiProcesses
|
||||
}
|
||||
}
|
||||
|
||||
Pop-Location
|
||||
|
||||
Write-Output ""
|
||||
Write-Output "All requested checks passed."
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
uv run pytest packages/sir/tests
|
||||
(cd rust && cargo test)
|
||||
@@ -0,0 +1,59 @@
|
||||
param(
|
||||
[string]$ApiUrl = "http://localhost:8000",
|
||||
[string]$WebUrl = "http://localhost:3000",
|
||||
[string]$Neo4jUrl = "http://localhost:7474",
|
||||
[string]$QdrantUrl = "http://localhost:6333",
|
||||
[string]$ObjectStorageUrl = "http://localhost:9000",
|
||||
[string]$ProjectName = "",
|
||||
[switch]$IncludeProjectSetupSmoke,
|
||||
[string]$BrowserPath = ""
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Test-Http($Name, $Url) {
|
||||
$response = Invoke-WebRequest -UseBasicParsing -Uri $Url -TimeoutSec 10
|
||||
if ($response.StatusCode -lt 200 -or $response.StatusCode -ge 300) {
|
||||
throw "$Name failed: HTTP $($response.StatusCode)"
|
||||
}
|
||||
Write-Host "[ok] $Name $Url"
|
||||
}
|
||||
|
||||
Test-Http "api health" "$ApiUrl/api/health"
|
||||
Test-Http "web health" "$WebUrl/health"
|
||||
$compose = @("compose")
|
||||
if ($ProjectName) {
|
||||
$compose += @("-p", $ProjectName)
|
||||
}
|
||||
$compose += @("-f", "infra/docker/docker-compose.test.yml")
|
||||
docker @compose exec -T postgres pg_isready -U sfera -d sfera | Out-Host
|
||||
Write-Host "[ok] postgres health"
|
||||
Test-Http "neo4j health" "$Neo4jUrl"
|
||||
Test-Http "qdrant health" "$QdrantUrl/readyz"
|
||||
docker @compose exec -T redis redis-cli ping | Out-Host
|
||||
Write-Host "[ok] redis health"
|
||||
Test-Http "object-storage health" "$ObjectStorageUrl/minio/health/live"
|
||||
|
||||
if ($IncludeProjectSetupSmoke) {
|
||||
$webDir = Join-Path (Get-Location).ProviderPath "frontend\sfera-web"
|
||||
Write-Host "[run] project setup runtime smoke $WebUrl"
|
||||
$previousWebUrl = $env:SFERA_WEB_URL
|
||||
$previousBrowserPath = $env:SFERA_BROWSER_PATH
|
||||
try {
|
||||
$env:SFERA_WEB_URL = $WebUrl
|
||||
if ($BrowserPath) {
|
||||
$env:SFERA_BROWSER_PATH = $BrowserPath
|
||||
}
|
||||
cmd /c "pushd `"$webDir`" && npm run smoke:project-setup"
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "project setup runtime smoke failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
Write-Host "[ok] project setup runtime smoke"
|
||||
} finally {
|
||||
if ((Get-Location).Path -eq $webDir) {
|
||||
Pop-Location
|
||||
}
|
||||
$env:SFERA_WEB_URL = $previousWebUrl
|
||||
$env:SFERA_BROWSER_PATH = $previousBrowserPath
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ServerUrl,
|
||||
|
||||
[string]$ApiUrl = "",
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$AgentId,
|
||||
|
||||
[string]$TaskName = "SferaWindowsAgent",
|
||||
|
||||
[string]$ServiceName = "SferaWindowsAgent",
|
||||
|
||||
[string]$InstallDir = "C:\ProgramData\SFERA\WindowsAgent",
|
||||
|
||||
[string[]]$NetworkRoot = @()
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||
throw "Run this installer in PowerShell as Administrator."
|
||||
}
|
||||
|
||||
$sourceScript = Join-Path $PSScriptRoot "sfera-windows-agent.ps1"
|
||||
if (!(Test-Path -LiteralPath $sourceScript -PathType Leaf)) {
|
||||
throw "Agent script not found: $sourceScript"
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
|
||||
$targetScript = Join-Path $InstallDir "sfera-windows-agent.ps1"
|
||||
$configPath = Join-Path $InstallDir "agent-config.json"
|
||||
Copy-Item -LiteralPath $sourceScript -Destination $targetScript -Force
|
||||
$config = [ordered]@{
|
||||
server_url = $ServerUrl.TrimEnd("/")
|
||||
api_url = $ApiUrl.TrimEnd("/")
|
||||
agent_id = $AgentId
|
||||
poll_seconds = 5
|
||||
network_roots = @($NetworkRoot)
|
||||
}
|
||||
$config | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $configPath -Encoding UTF8
|
||||
|
||||
$existingTask = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
|
||||
if ($existingTask) {
|
||||
Stop-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
|
||||
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false
|
||||
}
|
||||
|
||||
$arguments = @(
|
||||
"-STA",
|
||||
"-NoProfile",
|
||||
"-WindowStyle", "Hidden",
|
||||
"-ExecutionPolicy", "Bypass",
|
||||
"-File", "`"$targetScript`"",
|
||||
"-ConfigPath", "`"$configPath`""
|
||||
)
|
||||
|
||||
$existing = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
|
||||
if ($existing) {
|
||||
sc.exe stop $ServiceName | Out-Null
|
||||
sc.exe delete $ServiceName | Out-Null
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
|
||||
$powerShellPath = Join-Path $env:SystemRoot "System32\WindowsPowerShell\v1.0\powershell.exe"
|
||||
if (!(Test-Path -LiteralPath $powerShellPath -PathType Leaf)) {
|
||||
$powerShellPath = Join-Path $PSHOME "powershell.exe"
|
||||
}
|
||||
$action = New-ScheduledTaskAction -Execute $powerShellPath -Argument ($arguments -join " ")
|
||||
$trigger = New-ScheduledTaskTrigger -AtLogOn -User "$env:USERDOMAIN\$env:USERNAME"
|
||||
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit ([TimeSpan]::Zero) -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1)
|
||||
$principal = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\$env:USERNAME" -LogonType Interactive -RunLevel Highest
|
||||
Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Settings $settings -Principal $principal -Description "SFERA bridge between local 1C/EDT/network folders and SFERA server." -Force | Out-Null
|
||||
Start-ScheduledTask -TaskName $TaskName
|
||||
|
||||
Write-Host "Installed and started scheduled task $TaskName."
|
||||
Write-Host "ServerUrl=$ServerUrl"
|
||||
Write-Host "AgentId=$AgentId"
|
||||
Write-Host "Config=$configPath"
|
||||
if ($NetworkRoot.Count -gt 0) {
|
||||
Write-Host "NetworkRoot=$($NetworkRoot -join ', ')"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user