Files
2026-07-13 13:12:33 +08:00

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