Files
AIChat/update.ps1
T
2026-08-18 10:03:35 +08:00

224 lines
6.9 KiB
PowerShell

# AI Stack Windows service updater
# Run in elevated PowerShell:
# powershell -NoProfile -ExecutionPolicy Bypass -File .\update.ps1
param(
[switch]$SkipRestart
)
$ErrorActionPreference = "Stop"
$BASE_DIR = $PSScriptRoot
$WINSW_EXE = Join-Path $BASE_DIR "WinSW.exe"
$LITELLM_XML = Join-Path $BASE_DIR "litellm\litellm-service.xml"
$WEBUI_XML = Join-Path $BASE_DIR "open_webui\openwebui-service.xml"
$ENV_FILE = Join-Path $BASE_DIR ".env"
$LITELLM_VENV = Join-Path $BASE_DIR "litellm\venv"
$WEBUI_VENV = Join-Path $BASE_DIR "open_webui\venv"
$LOG_DIR = Join-Path $BASE_DIR "logs\install"
$UPDATE_LOG = Join-Path $LOG_DIR ("update-{0}.log" -f (Get-Date -Format "yyyyMMdd-HHmmss"))
function Fail {
param([string]$Message)
Write-Host "[ERROR] $Message" -ForegroundColor Red
throw $Message
}
function Step {
param([string]$Message)
Write-Host "[STEP] $Message" -ForegroundColor Cyan
}
function Diag {
param([string]$Message)
Write-Host "[DIAG] $Message" -ForegroundColor DarkGray
}
function Ensure-LogDir {
if (-not (Test-Path $LOG_DIR)) {
New-Item -ItemType Directory -Path $LOG_DIR -Force | Out-Null
}
}
function Get-DotEnvValue {
param([string]$Name)
if (-not (Test-Path $ENV_FILE)) {
return $null
}
foreach ($line in Get-Content $ENV_FILE) {
$trimmed = $line.Trim()
if ([string]::IsNullOrWhiteSpace($trimmed)) { continue }
if ($trimmed.StartsWith("#")) { continue }
if ($trimmed -like "$Name=*") {
return $trimmed.Substring($Name.Length + 1).Trim()
}
}
return $null
}
function Ensure-GeminiApiKey {
$apiKey = $env:GEMINI_API_KEY
if ([string]::IsNullOrWhiteSpace($apiKey)) {
$apiKey = Get-DotEnvValue -Name "GEMINI_API_KEY"
}
if ([string]::IsNullOrWhiteSpace($apiKey)) {
Fail "GEMINI_API_KEY not found. Set a system environment variable or add GEMINI_API_KEY=... to .env"
}
[Environment]::SetEnvironmentVariable("GEMINI_API_KEY", $apiKey, "Machine")
$env:GEMINI_API_KEY = $apiKey
Diag "GEMINI_API_KEY synced to machine environment for Windows services"
}
function Check-Admin {
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object System.Security.Principal.WindowsPrincipal($identity)
if (-not $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)) {
Fail "Please run PowerShell as Administrator."
}
}
function Invoke-External {
param(
[Parameter(Mandatory = $true)]
[string]$File,
[string[]]$Arguments = @(),
[Parameter(Mandatory = $true)]
[string]$ActionLabel
)
$display = if ($Arguments.Count -gt 0) { " " + ($Arguments -join " ") } else { "" }
Step "$ActionLabel -> $File$display"
try {
& $File @Arguments
if ($LASTEXITCODE -ne $null -and $LASTEXITCODE -ne 0) {
Fail "$ActionLabel failed, exit code: $LASTEXITCODE"
}
} catch {
Write-Host "[ERROR] file: $File" -ForegroundColor Red
Write-Host "[ERROR] args: $($Arguments -join ' ')" -ForegroundColor Red
Write-Host "[ERROR] detail: $($_.Exception.Message)" -ForegroundColor Red
throw
}
}
function Check-Prerequisites {
if (-not (Test-Path $WINSW_EXE)) {
Fail "WinSW.exe not found in project root. Download WinSW-x64.exe and rename to WinSW.exe."
}
foreach ($path in @($LITELLM_XML, $WEBUI_XML)) {
if (-not (Test-Path $path)) {
Fail "Required file not found: $path"
}
}
foreach ($venvPath in @($LITELLM_VENV, $WEBUI_VENV)) {
$pythonExe = Join-Path $venvPath "Scripts\python.exe"
if (-not (Test-Path $pythonExe)) {
Fail "Virtual environment not found: $venvPath. Run install.ps1 first."
}
}
}
function Stop-ServicesIfPresent {
foreach ($svc in @("ai-stack-openwebui", "ai-stack-litellm")) {
$service = Get-Service -Name $svc -ErrorAction SilentlyContinue
if ($service -and $service.Status -eq "Running") {
Step "Stop service $svc"
Stop-Service $svc -Force
Start-Sleep -Seconds 2
}
}
}
function Upgrade-VenvPackages {
param(
[string]$PythonExe,
[string[]]$Packages,
[string]$Name
)
Invoke-External -File $PythonExe -Arguments @("-m", "pip", "install", "--upgrade", "pip", "setuptools", "wheel") -ActionLabel "Upgrade pip toolchain ($Name)"
$upgradeArgs = @("-m", "pip", "install", "--upgrade") + $Packages
Invoke-External -File $PythonExe -Arguments $upgradeArgs -ActionLabel "Upgrade packages ($Name)"
}
function Refresh-ServiceBinaries {
$litellmWinSW = Join-Path $BASE_DIR "litellm\WinSW.exe"
$webuiWinSW = Join-Path $BASE_DIR "open_webui\WinSW.exe"
Step "Sync WinSW.exe into service folders"
Copy-Item $WINSW_EXE $litellmWinSW -Force
Copy-Item $WINSW_EXE $webuiWinSW -Force
}
function Start-ServicesIfNeeded {
if ($SkipRestart) {
Diag "SkipRestart is set, services remain stopped after package upgrade."
return
}
Step "Start LiteLLM service"
Start-Service ai-stack-litellm
Start-Sleep -Seconds 3
Step "Start Open WebUI service"
Start-Service ai-stack-openwebui
}
function Update-All {
Write-Host ""
Write-Host "=== Update AI Stack Windows Services ===" -ForegroundColor Cyan
Write-Host ""
Write-Host "This update preserves config files, Open WebUI data, and existing venv directories." -ForegroundColor Yellow
Write-Host "It only upgrades packages and refreshes service runtime files." -ForegroundColor Yellow
Write-Host ""
Check-Admin
Check-Prerequisites
Ensure-GeminiApiKey
Stop-ServicesIfPresent
Refresh-ServiceBinaries
$litellmPython = Join-Path $LITELLM_VENV "Scripts\python.exe"
$webuiPython = Join-Path $WEBUI_VENV "Scripts\python.exe"
Upgrade-VenvPackages -PythonExe $litellmPython -Packages @("email-validator", "litellm[proxy]") -Name "LiteLLM"
Upgrade-VenvPackages -PythonExe $webuiPython -Packages @("open-webui") -Name "Open WebUI"
Start-ServicesIfNeeded
Write-Host ""
Write-Host "[OK] Update completed" -ForegroundColor Green
Write-Host "Configuration and data were preserved." -ForegroundColor Green
if ($SkipRestart) {
Write-Host "Services were not restarted because -SkipRestart was specified." -ForegroundColor Yellow
} else {
Write-Host "Open WebUI: http://127.0.0.1:3000"
Write-Host "LiteLLM API: http://127.0.0.1:4000"
}
Write-Host ""
}
Ensure-LogDir
Write-Host "Update log file: $UPDATE_LOG" -ForegroundColor DarkGray
Start-Transcript -Path $UPDATE_LOG -Append | Out-Null
try {
Update-All
} catch {
Write-Host ""
Write-Host "Update failed. Send this log file:" -ForegroundColor Red
Write-Host " $UPDATE_LOG" -ForegroundColor Red
Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red
throw
} finally {
Stop-Transcript | Out-Null
}