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

305 lines
10 KiB
PowerShell

# install.ps1 - OpenSquilla release installer for Windows PowerShell.
#
# This script installs uv if needed, installs a release wheel with uv tool, then
# prints the explicit next steps. It does not run onboarding or start the gateway.
param(
[string]$Version = "",
[string]$Profile = "",
[string[]]$Extras = @()
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$defaultVersion = 'v0.5.0rc3'
$repoSlug = if ($env:OPENSQUILLA_REPOSITORY) { $env:OPENSQUILLA_REPOSITORY } else { 'opensquilla/opensquilla' }
$pythonVersion = if ($env:OPENSQUILLA_PYTHON_VERSION) { $env:OPENSQUILLA_PYTHON_VERSION } else { '3.12' }
$originalPath = if ($env:Path) { $env:Path } else { '' }
$dryRun = $env:OPENSQUILLA_INSTALL_DRY_RUN -eq '1'
$script:isWindowsHost = if (Get-Variable IsWindows -ErrorAction SilentlyContinue) {
$IsWindows
} else {
$env:OS -eq 'Windows_NT'
}
if (-not $Version) {
$Version = if ($env:OPENSQUILLA_VERSION) { $env:OPENSQUILLA_VERSION } else { $defaultVersion }
}
$profileName = if ($Profile) {
$Profile
} elseif ($env:OPENSQUILLA_INSTALL_PROFILE) {
$env:OPENSQUILLA_INSTALL_PROFILE
} else {
'recommended'
}
$validExtras = @(
'matrix',
'matrix-e2e',
'document-extras'
)
function Split-InstallExtras {
param([string[]]$Values)
$items = New-Object System.Collections.Generic.List[string]
foreach ($value in $Values) {
if (-not $value) {
continue
}
foreach ($part in ($value -split '[,\s]+')) {
$item = $part.Trim()
if ($item -and -not $items.Contains($item)) {
$items.Add($item)
}
}
}
return $items.ToArray()
}
$extraInputs = @()
if ($env:OPENSQUILLA_INSTALL_EXTRAS) {
$extraInputs += $env:OPENSQUILLA_INSTALL_EXTRAS
}
$extraInputs += $Extras
$installExtras = @(Split-InstallExtras $extraInputs)
$unknownExtras = @($installExtras | Where-Object { $_ -notin $validExtras })
if ($unknownExtras.Count -gt 0) {
Write-Error "install.ps1: unsupported extras: $($unknownExtras -join ', '). Supported extras: $($validExtras -join ', ')."
exit 1
}
switch ($profileName) {
'core' { $targetExtras = @() }
'minimal' { $profileName = 'core'; $targetExtras = @() }
'recommended' { $targetExtras = @('recommended') }
default {
Write-Error "install.ps1: unsupported OPENSQUILLA_INSTALL_PROFILE='$profileName'. Supported profiles: core, recommended."
exit 1
}
}
$targetExtras += $installExtras
$packageName = if ($targetExtras.Count -gt 0) {
"opensquilla[$($targetExtras -join ',')]"
} else {
'opensquilla'
}
function Test-ReleaseVersion {
param([string]$Value)
return $Value -match '^v?\d+\.\d+\.\d+((a|b|rc)\d+)?$'
}
if ($Version -notin @('latest', 'stable') -and -not (Test-ReleaseVersion $Version)) {
Write-Error "install.ps1: unsupported OPENSQUILLA_VERSION='$Version'. The release installer only supports latest, stable, or release versions like v0.5.0rc3. Use git clone plus scripts/install_source.ps1 for main, dev, branch, or source installs."
exit 1
}
switch -Regex ($Version) {
'^(latest|stable)$' {
try {
$latest = Invoke-RestMethod -Uri "https://api.github.com/repos/$repoSlug/releases/latest" -Headers @{ 'User-Agent' = 'OpenSquilla installer' }
} catch {
Write-Error "install.ps1: failed to resolve latest release for $repoSlug. $($_.Exception.Message)"
exit 1
}
$releaseTag = [string]$latest.tag_name
if (-not (Test-ReleaseVersion $releaseTag)) {
Write-Error "install.ps1: latest release tag '$releaseTag' is not a supported release version."
exit 1
}
$releaseVersion = if ($releaseTag.StartsWith('v')) { $releaseTag.Substring(1) } else { $releaseTag }
$wheelUrl = "https://github.com/$repoSlug/releases/download/$releaseTag/opensquilla-$releaseVersion-py3-none-any.whl"
$displayVersion = $releaseTag
break
}
'^v' {
$releaseVersion = $Version.Substring(1)
$wheelUrl = "https://github.com/$repoSlug/releases/download/$Version/opensquilla-$releaseVersion-py3-none-any.whl"
$displayVersion = $Version
break
}
default {
$releaseVersion = $Version
$releaseTag = "v$releaseVersion"
$wheelUrl = "https://github.com/$repoSlug/releases/download/$releaseTag/opensquilla-$releaseVersion-py3-none-any.whl"
$displayVersion = $releaseTag
}
}
$installSpec = "$packageName @ $wheelUrl"
function Resolve-Uv {
$cmd = Get-Command uv -ErrorAction SilentlyContinue
if ($cmd) {
return $cmd.Source
}
$candidateDirs = @(
(Join-Path $env:USERPROFILE '.local\bin'),
(Join-Path $env:USERPROFILE '.cargo\bin')
)
$env:Path = ($candidateDirs -join ';') + ';' + $env:Path
foreach ($dir in $candidateDirs) {
$candidate = Join-Path $dir 'uv.exe'
if (Test-Path $candidate -PathType Leaf) {
return $candidate
}
}
$cmd = Get-Command uv -ErrorAction SilentlyContinue
if ($cmd) {
return $cmd.Source
}
return $null
}
function Install-Uv {
Write-Host 'install.ps1: uv not found; installing uv first.'
Invoke-RestMethod -Uri 'https://astral.sh/uv/install.ps1' | Invoke-Expression
}
function Test-WindowsVCRedistInstalled {
if (-not $script:isWindowsHost) {
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 Install-WindowsVCRedistIfNeeded {
if (-not $script:isWindowsHost -or $profileName -ne 'recommended') {
return
}
if ($env:OPENSQUILLA_SKIP_VC_REDIST -eq '1') {
Write-Host 'install.ps1: skipping Microsoft Visual C++ Redistributable check because OPENSQUILLA_SKIP_VC_REDIST=1.'
return
}
if (Test-WindowsVCRedistInstalled) {
Write-Host 'install.ps1: Microsoft Visual C++ Redistributable is already installed.'
return
}
$redistUrl = 'https://aka.ms/vs/17/release/vc_redist.x64.exe'
$winget = Get-Command winget -ErrorAction SilentlyContinue
if ($winget) {
Write-Host 'install.ps1: 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 'install.ps1: Microsoft Visual C++ Redistributable installation completed.'
return
}
Write-Warning "install.ps1: 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.'
}
if ($dryRun) {
Write-Host "install.ps1: dry-run - would install OpenSquilla $displayVersion"
Write-Host "install.ps1: dry-run - would run: uv tool install --python $pythonVersion --force --reinstall-package opensquilla `"$installSpec`""
exit 0
}
$uvBin = Resolve-Uv
if (-not $uvBin) {
Install-Uv
$uvBin = Resolve-Uv
}
if (-not $uvBin) {
Write-Error "install.ps1: uv was not found after installation. Restart PowerShell or add '$env:USERPROFILE\.local\bin' to PATH, then retry."
exit 1
}
Install-WindowsVCRedistIfNeeded
Write-Host "install.ps1: installing OpenSquilla $displayVersion ($profileName)"
& $uvBin tool install --python $pythonVersion --force --reinstall-package opensquilla $installSpec
if ($LASTEXITCODE -ne 0) {
Write-Error "install.ps1: install command failed with exit code $LASTEXITCODE."
exit $LASTEXITCODE
}
$toolBinDir = ''
try {
$toolBinDir = (& $uvBin tool dir --bin 2>$null).Trim()
} catch {
$toolBinDir = ''
}
# Write an install receipt to aid `opensquilla uninstall`. Best-effort.
try {
$receiptHome = if ($env:OPENSQUILLA_STATE_DIR) { $env:OPENSQUILLA_STATE_DIR } else { Join-Path $HOME '.opensquilla' }
$toolDir = ''
try { $toolDir = (& $uvBin tool dir 2>$null).Trim() } catch { $toolDir = '' }
New-Item -ItemType Directory -Force -Path $receiptHome | Out-Null
$receipt = [ordered]@{
version = 1
install_method = 'uv-tool'
installed_at = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
entrypoints = @("$toolBinDir/opensquilla", "$toolBinDir/gateway")
owned_paths = @("$toolDir/opensquilla", "$toolBinDir/opensquilla", "$toolBinDir/gateway")
data_root = $receiptHome
}
$receipt | ConvertTo-Json | Set-Content -Path (Join-Path $receiptHome 'install-receipt.json') -Encoding utf8
} catch {
# Receipt is optional; never fail the install over it.
}
@"
----------------------------------------------------------------------------
OpenSquilla installed from $displayVersion.
Next steps:
opensquilla onboard
opensquilla gateway run
Default gateway bind: 127.0.0.1:18791 (loopback only).
Do not expose the gateway on 0.0.0.0 unless it is behind a trusted reverse
proxy or VPN.
----------------------------------------------------------------------------
"@ | Write-Host
if ($toolBinDir -and -not (($originalPath -split ';') -contains $toolBinDir)) {
@"
PATH note:
Your current PowerShell may not find 'opensquilla' until PATH is refreshed.
Run this command, then retry the next steps:
`$env:Path = "$toolBinDir;" + `$env:Path
Or open a new PowerShell window.
"@ | Write-Host
}