409 lines
14 KiB
PowerShell
409 lines
14 KiB
PowerShell
param(
|
|
[switch]$Cli,
|
|
[Parameter(ValueFromRemainingArguments = $true)]
|
|
[string[]]$CliArgs
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
$env:PYTHONUTF8 = '1'
|
|
$env:PYTHONIOENCODING = 'utf-8:replace'
|
|
|
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
$PackageDir = Join-Path $ScriptDir 'packages'
|
|
$PythonBin = Join-Path $ScriptDir 'runtime\python\python.exe'
|
|
$VenvBase = if ($env:LOCALAPPDATA) { $env:LOCALAPPDATA } else { $env:TEMP }
|
|
$VenvRoot = Join-Path $VenvBase 'OpenSquilla\venvs'
|
|
$RequiresRouterRuntime = $true
|
|
if ((-not $env:OPENSQUILLA_LLM_API_KEY) -and $env:OPENROUTER_API_KEY) {
|
|
$env:OPENSQUILLA_LLM_API_KEY = $env:OPENROUTER_API_KEY
|
|
}
|
|
|
|
if (-not (Test-Path $PythonBin)) {
|
|
throw "Bundled Python runtime not found: $PythonBin"
|
|
}
|
|
if (-not (Test-Path $PackageDir)) {
|
|
throw "OpenSquilla package directory not found: $PackageDir"
|
|
}
|
|
|
|
function Test-WindowsVCRedistInstalled {
|
|
if (-not [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform(
|
|
[System.Runtime.InteropServices.OSPlatform]::Windows
|
|
)) {
|
|
return $true
|
|
}
|
|
$runtimeKeys = @(
|
|
'HKLM:\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64',
|
|
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\VisualStudio\14.0\VC\Runtimes\x64'
|
|
)
|
|
foreach ($key in $runtimeKeys) {
|
|
if (-not (Test-Path $key)) {
|
|
continue
|
|
}
|
|
$runtime = Get-ItemProperty -Path $key -ErrorAction SilentlyContinue
|
|
if ($runtime -and $runtime.Installed -eq 1 -and $runtime.Major -ge 14) {
|
|
return $true
|
|
}
|
|
}
|
|
return $false
|
|
}
|
|
|
|
function Test-WindowsAdmin {
|
|
if (-not [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform(
|
|
[System.Runtime.InteropServices.OSPlatform]::Windows
|
|
)) {
|
|
return $true
|
|
}
|
|
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
|
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
|
|
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
|
}
|
|
|
|
function Get-WindowsVCRedistInstaller {
|
|
$redistUrl = 'https://aka.ms/vs/17/release/vc_redist.x64.exe'
|
|
$candidateInstallers = @(
|
|
(Join-Path $ScriptDir 'vc_redist.x64.exe'),
|
|
(Join-Path $ScriptDir 'redist\vc_redist.x64.exe'),
|
|
(Join-Path $ScriptDir 'runtime\vc_redist.x64.exe')
|
|
)
|
|
$installerPath = $candidateInstallers |
|
|
Where-Object { Test-Path -LiteralPath $_ } |
|
|
Select-Object -First 1
|
|
if ($installerPath) {
|
|
return $installerPath
|
|
}
|
|
|
|
$downloadDir = Join-Path ([System.IO.Path]::GetTempPath()) 'OpenSquilla'
|
|
$installerPath = Join-Path $downloadDir 'vc_redist.x64.exe'
|
|
New-Item -ItemType Directory -Path $downloadDir -Force | Out-Null
|
|
Write-Host (
|
|
'OpenSquilla: downloading Microsoft Visual C++ Redistributable ' +
|
|
'2015-2022 x64 from Microsoft.'
|
|
)
|
|
try {
|
|
Invoke-WebRequest -Uri $redistUrl -OutFile $installerPath -UseBasicParsing
|
|
return $installerPath
|
|
} catch {
|
|
Write-Warning (
|
|
'OpenSquilla: could not download Microsoft Visual C++ ' +
|
|
"Redistributable from $redistUrl. Error: $($_.Exception.Message)"
|
|
)
|
|
return $null
|
|
}
|
|
}
|
|
|
|
function Install-WindowsVCRedistWithInstaller {
|
|
param(
|
|
[switch]$Repair
|
|
)
|
|
|
|
$installerPath = Get-WindowsVCRedistInstaller
|
|
if (-not $installerPath) {
|
|
return $false
|
|
}
|
|
|
|
$action = if ($Repair) { 'repairing' } else { 'installing' }
|
|
Write-Host (
|
|
"OpenSquilla: $action Microsoft Visual C++ Redistributable 2015-2022 x64..."
|
|
)
|
|
$redistArgs = if ($Repair) {
|
|
@('/repair', '/quiet', '/norestart')
|
|
} else {
|
|
@('/install', '/quiet', '/norestart')
|
|
}
|
|
try {
|
|
if (Test-WindowsAdmin) {
|
|
$process = Start-Process -FilePath $installerPath -ArgumentList $redistArgs -Wait -PassThru
|
|
} else {
|
|
Write-Host (
|
|
'OpenSquilla: administrator approval may be requested to ' +
|
|
'install or repair Microsoft Visual C++ Redistributable.'
|
|
)
|
|
$process = Start-Process -FilePath $installerPath `
|
|
-ArgumentList $redistArgs `
|
|
-Verb RunAs `
|
|
-Wait `
|
|
-PassThru
|
|
}
|
|
} catch {
|
|
Write-Warning (
|
|
'OpenSquilla: Visual C++ Redistributable installer could not be ' +
|
|
"started. Error: $($_.Exception.Message)"
|
|
)
|
|
return $false
|
|
}
|
|
|
|
if ($process.ExitCode -in @(0, 1638, 3010)) {
|
|
Write-Host 'OpenSquilla: Microsoft Visual C++ Redistributable is ready.'
|
|
if ($process.ExitCode -eq 3010) {
|
|
Write-Warning (
|
|
'OpenSquilla: the Visual C++ installer requested a reboot; ' +
|
|
'restart Windows if ONNX Runtime still fails to load.'
|
|
)
|
|
}
|
|
return $true
|
|
}
|
|
|
|
Write-Warning (
|
|
'OpenSquilla: Microsoft Visual C++ Redistributable installer exited ' +
|
|
"with code $($process.ExitCode)."
|
|
)
|
|
return $false
|
|
}
|
|
|
|
function Install-WindowsVCRedistIfNeeded {
|
|
param(
|
|
[switch]$Repair
|
|
)
|
|
|
|
if (-not $RequiresRouterRuntime) {
|
|
return $true
|
|
}
|
|
if ($env:OPENSQUILLA_SKIP_VC_REDIST -eq '1') {
|
|
Write-Host (
|
|
'OpenSquilla: skipping Microsoft Visual C++ Redistributable check ' +
|
|
'because OPENSQUILLA_SKIP_VC_REDIST=1.'
|
|
)
|
|
return $true
|
|
}
|
|
if ((Test-WindowsVCRedistInstalled) -and -not $Repair) {
|
|
return $true
|
|
}
|
|
|
|
$redistUrl = 'https://aka.ms/vs/17/release/vc_redist.x64.exe'
|
|
if ($Repair) {
|
|
if (Install-WindowsVCRedistWithInstaller -Repair) {
|
|
return $true
|
|
}
|
|
} elseif (Install-WindowsVCRedistWithInstaller) {
|
|
return $true
|
|
}
|
|
|
|
$winget = if ($Repair) { $null } else { Get-Command winget -ErrorAction SilentlyContinue }
|
|
if ($winget) {
|
|
Write-Host (
|
|
'OpenSquilla: Microsoft Visual C++ Redistributable not detected; ' +
|
|
'installing with winget.'
|
|
)
|
|
$wingetArgs = @(
|
|
'install',
|
|
'--id',
|
|
'Microsoft.VCRedist.2015+.x64',
|
|
'--exact',
|
|
'--silent',
|
|
'--accept-package-agreements',
|
|
'--accept-source-agreements'
|
|
)
|
|
& winget @wingetArgs
|
|
if ($LASTEXITCODE -eq 0) {
|
|
Write-Host 'OpenSquilla: Microsoft Visual C++ Redistributable installation completed.'
|
|
return $true
|
|
}
|
|
Write-Warning (
|
|
'OpenSquilla: winget could not install Microsoft Visual C++ ' +
|
|
"Redistributable (exit $LASTEXITCODE)."
|
|
)
|
|
}
|
|
|
|
Write-Warning (
|
|
'OpenSquilla: Microsoft Visual C++ Redistributable 2015-2022 x64 is ' +
|
|
'required for the bundled ONNX router.'
|
|
)
|
|
Write-Warning (
|
|
'OpenSquilla can still start with safe router fallback, but bundled ' +
|
|
'ONNX model routing is disabled until this runtime is installed.'
|
|
)
|
|
Write-Warning (
|
|
"If automatic installation fails, install it manually: $redistUrl"
|
|
)
|
|
Write-Warning (
|
|
'After installing, reopen PowerShell and restart OpenSquilla.'
|
|
)
|
|
return $false
|
|
}
|
|
|
|
function Test-OnnxRuntimeImport {
|
|
if (-not (Test-Path $VenvPython)) {
|
|
return $false
|
|
}
|
|
& $VenvPython -c "import onnxruntime as ort; print('onnxruntime', ort.__version__)" | Out-Host
|
|
return ($LASTEXITCODE -eq 0)
|
|
}
|
|
|
|
function Repair-WindowsVCRedistForOnnxIfNeeded {
|
|
if (-not $RequiresRouterRuntime) {
|
|
return
|
|
}
|
|
if ($env:OPENSQUILLA_SKIP_VC_REDIST -eq '1') {
|
|
return
|
|
}
|
|
if (Test-OnnxRuntimeImport) {
|
|
return
|
|
}
|
|
|
|
Write-Warning (
|
|
'OpenSquilla: ONNX Runtime failed to import after setup. Attempting ' +
|
|
'Visual C++ Redistributable repair before starting the gateway.'
|
|
)
|
|
Install-WindowsVCRedistIfNeeded -Repair | Out-Null
|
|
if (Test-OnnxRuntimeImport) {
|
|
return
|
|
}
|
|
|
|
Write-Warning (
|
|
'OpenSquilla: ONNX Runtime still failed after Visual C++ repair. If ' +
|
|
'the router warning remains, check CPU/VM AVX compatibility or install ' +
|
|
'the Microsoft Visual C++ Redistributable manually: ' +
|
|
'https://aka.ms/vs/17/release/vc_redist.x64.exe'
|
|
)
|
|
}
|
|
|
|
if (-not (Test-Path $VenvRoot)) {
|
|
New-Item -ItemType Directory -Path $VenvRoot -Force | Out-Null
|
|
}
|
|
$OpenSquillaWheel = Get-ChildItem -Path $PackageDir -Filter 'opensquilla-*.whl' |
|
|
Sort-Object Name |
|
|
Select-Object -First 1
|
|
if (-not $OpenSquillaWheel) {
|
|
throw "OpenSquilla wheel not found in $PackageDir"
|
|
}
|
|
$Sha256 = [System.Security.Cryptography.SHA256]::Create()
|
|
$WheelStream = [System.IO.File]::OpenRead($OpenSquillaWheel.FullName)
|
|
try {
|
|
$WheelHashFull = -join ($Sha256.ComputeHash($WheelStream) | ForEach-Object {
|
|
$_.ToString('x2')
|
|
})
|
|
} finally {
|
|
$WheelStream.Dispose()
|
|
$Sha256.Dispose()
|
|
}
|
|
$WheelHash = $WheelHashFull.Substring(0, 12).ToLowerInvariant()
|
|
$Hash = [System.Security.Cryptography.SHA256]::Create().ComputeHash(
|
|
[System.Text.Encoding]::UTF8.GetBytes("$ScriptDir|$WheelHash")
|
|
)
|
|
$ReleaseId = -join ($Hash[0..5] | ForEach-Object { $_.ToString('x2') })
|
|
$VenvDir = Join-Path $VenvRoot $ReleaseId
|
|
$VenvPython = Join-Path $VenvDir 'Scripts\python.exe'
|
|
$InstallMarker = Join-Path $VenvDir ".opensquilla-wheelhouse-$WheelHash"
|
|
$env:PATH = "$ScriptDir;$ScriptDir\runtime\python;$env:PATH"
|
|
$env:PATH = "$VenvDir\Scripts;$env:PATH"
|
|
$PortableDataDir = if ($env:OPENSQUILLA_PORTABLE_HOME) {
|
|
$env:OPENSQUILLA_PORTABLE_HOME
|
|
} else {
|
|
Join-Path $VenvBase "OpenSquilla\portable\$ReleaseId"
|
|
}
|
|
if (-not $env:OPENSQUILLA_GATEWAY_CONFIG_PATH) {
|
|
$env:OPENSQUILLA_GATEWAY_CONFIG_PATH = Join-Path $PortableDataDir 'config.toml'
|
|
}
|
|
if (-not $env:OPENSQUILLA_STATE_DIR) {
|
|
$env:OPENSQUILLA_STATE_DIR = $PortableDataDir
|
|
}
|
|
if (-not $env:OPENSQUILLA_GATEWAY_STATE_DIR) {
|
|
$env:OPENSQUILLA_GATEWAY_STATE_DIR = Join-Path $env:OPENSQUILLA_STATE_DIR 'state'
|
|
}
|
|
if (-not $env:OPENSQUILLA_GATEWAY_WORKSPACE_DIR) {
|
|
$env:OPENSQUILLA_GATEWAY_WORKSPACE_DIR = Join-Path $env:OPENSQUILLA_STATE_DIR 'workspace'
|
|
}
|
|
New-Item -ItemType Directory -Path $env:OPENSQUILLA_STATE_DIR -Force | Out-Null
|
|
Install-WindowsVCRedistIfNeeded | Out-Null
|
|
|
|
if (-not (Test-Path $VenvPython)) {
|
|
Write-Host "Creating local OpenSquilla environment..."
|
|
& $PythonBin -m venv --without-pip $VenvDir
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "OpenSquilla environment creation failed with exit code $LASTEXITCODE."
|
|
}
|
|
}
|
|
|
|
if (-not (Test-Path $InstallMarker)) {
|
|
Write-Host "Installing OpenSquilla from bundled wheels..."
|
|
$SitePackages = & $VenvPython -c "import site; print(site.getsitepackages()[0])"
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "OpenSquilla site-packages lookup failed with exit code $LASTEXITCODE."
|
|
}
|
|
$WheelInstallScript = @'
|
|
import pathlib
|
|
import shutil
|
|
import sys
|
|
import zipfile
|
|
|
|
package_dir = pathlib.Path(sys.argv[1])
|
|
site_packages = pathlib.Path(sys.argv[2])
|
|
site_packages.mkdir(parents=True, exist_ok=True)
|
|
for wheel_path in sorted(package_dir.glob("*.whl")):
|
|
with zipfile.ZipFile(wheel_path) as wheel:
|
|
for info in wheel.infolist():
|
|
name = info.filename
|
|
if not name or name.endswith("/"):
|
|
continue
|
|
if ".data/" in name:
|
|
_prefix, data_rel = name.split(".data/", 1)
|
|
kind, _sep, remainder = data_rel.partition("/")
|
|
if kind not in {"purelib", "platlib"} or not remainder:
|
|
continue
|
|
target_rel = pathlib.Path(remainder)
|
|
else:
|
|
target_rel = pathlib.Path(name)
|
|
target = site_packages / target_rel
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
with wheel.open(info) as src, target.open("wb") as dst:
|
|
shutil.copyfileobj(src, dst)
|
|
'@
|
|
$WheelInstallScript | & $PythonBin - $PackageDir $SitePackages
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "OpenSquilla bundled wheel installation failed with exit code $LASTEXITCODE."
|
|
}
|
|
New-Item -ItemType File -Path $InstallMarker -Force | Out-Null
|
|
}
|
|
Repair-WindowsVCRedistForOnnxIfNeeded
|
|
|
|
$OpenSquillaArgs = @("-m", "opensquilla.cli.main")
|
|
|
|
if ($Cli) {
|
|
& $VenvPython @OpenSquillaArgs @CliArgs
|
|
exit $LASTEXITCODE
|
|
}
|
|
|
|
if ((-not (Test-Path $env:OPENSQUILLA_GATEWAY_CONFIG_PATH)) -and $env:OPENROUTER_API_KEY) {
|
|
& $VenvPython @OpenSquillaArgs onboard `
|
|
--provider openrouter `
|
|
--api-key-env OPENROUTER_API_KEY `
|
|
--minimal
|
|
} else {
|
|
& $VenvPython @OpenSquillaArgs onboard
|
|
}
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "OpenSquilla onboarding failed with exit code $LASTEXITCODE."
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "Starting OpenSquilla gateway."
|
|
Write-Host "Web UI: http://127.0.0.1:18791/control/"
|
|
Write-Host "Press Ctrl+C in this terminal to stop the gateway."
|
|
$OutputRedirected = [Console]::IsOutputRedirected
|
|
if (-not $OutputRedirected) {
|
|
& $VenvPython @OpenSquillaArgs gateway run
|
|
$GatewayExitCode = $LASTEXITCODE
|
|
} else {
|
|
$LogDir = Join-Path $env:OPENSQUILLA_STATE_DIR 'logs'
|
|
New-Item -ItemType Directory -Path $LogDir -Force | Out-Null
|
|
$ConsoleLog = Join-Path $LogDir 'gateway-console.log'
|
|
Write-Host "Console log: $ConsoleLog"
|
|
$PreviousErrorActionPreference = $ErrorActionPreference
|
|
$ErrorActionPreference = "Continue"
|
|
try {
|
|
& $VenvPython @OpenSquillaArgs gateway run 2>&1 |
|
|
ForEach-Object {
|
|
if ($_ -is [System.Management.Automation.ErrorRecord]) {
|
|
$_.ToString()
|
|
} else {
|
|
$_
|
|
}
|
|
} |
|
|
Tee-Object -FilePath $ConsoleLog -Append
|
|
$GatewayExitCode = $LASTEXITCODE
|
|
} finally {
|
|
$ErrorActionPreference = $PreviousErrorActionPreference
|
|
}
|
|
}
|
|
exit $GatewayExitCode
|