#!/usr/bin/env pwsh # update.ps1 - One-command update & restart for ocr-sprint-service (local dev) param( [ValidateSet("cpu", "gpu")] [string] $OcrMode ) $ErrorActionPreference = "Stop" $Port = 8000 $ProjectRoot = $PSScriptRoot $VenvDir = Join-Path $ProjectRoot ".venv" $Python = Join-Path $VenvDir "Scripts\python.exe" function Invoke-Step { param( [Parameter(Mandatory = $true)] [scriptblock] $Command, [Parameter(Mandatory = $true)] [string] $FailureMessage ) & $Command if ($LASTEXITCODE -ne 0) { Write-Host " $FailureMessage" -ForegroundColor Red exit $LASTEXITCODE } } function Get-DotEnvValue { param( [Parameter(Mandatory = $true)] [string] $Name ) $envFile = Join-Path $ProjectRoot ".env" if (Test-Path $envFile) { $line = Get-Content $envFile | Where-Object { $_ -match "^\s*$Name\s*=" } | Select-Object -Last 1 if ($line) { return (($line -split "=", 2)[1] -split "\s+#", 2)[0].Trim() } } return [Environment]::GetEnvironmentVariable($Name) } function Set-DotEnvValue { param( [Parameter(Mandatory = $true)] [string] $Name, [Parameter(Mandatory = $true)] [string] $Value ) $envFile = Join-Path $ProjectRoot ".env" if (-not (Test-Path $envFile)) { New-Item -Path $envFile -ItemType File | Out-Null } $lines = @(Get-Content $envFile) $updated = $false for ($i = 0; $i -lt $lines.Count; $i++) { if ($lines[$i] -match "^\s*$Name\s*=") { $comment = "" if ($lines[$i] -match "(\s+#.*)$") { $comment = $Matches[1] } $lines[$i] = "$Name=$Value$comment" $updated = $true } } if (-not $updated) { $lines += "$Name=$Value" } Set-Content -Path $envFile -Value $lines } function Test-PythonPackage { param( [Parameter(Mandatory = $true)] [string] $Name ) & $Python -m pip show $Name *> $null return $LASTEXITCODE -eq 0 } function Add-NvidiaDllPaths { $dllDirs = @( (Join-Path $VenvDir "Lib\site-packages\nvidia\cudnn\bin"), (Join-Path $VenvDir "Lib\site-packages\nvidia\cublas\bin"), (Join-Path $VenvDir "Lib\site-packages\nvidia\cuda_nvrtc\bin") ) foreach ($dir in $dllDirs) { if ((Test-Path $dir) -and (($env:PATH -split ";") -notcontains $dir)) { $env:PATH = "$dir;$env:PATH" } } } Set-Location $ProjectRoot if (-not (Test-Path $Python)) { Write-Host "Virtualenv not found at $VenvDir. Creating one..." -ForegroundColor Yellow $venvCreated = $false $pythonLauncher = Get-Command py -ErrorAction SilentlyContinue if ($pythonLauncher) { foreach ($version in @("3.12", "3.11", "3.10")) { & py "-$version" -m venv $VenvDir 2>$null if ($LASTEXITCODE -eq 0) { $venvCreated = $true break } } } if (-not $venvCreated) { $systemPython = Get-Command python -ErrorAction SilentlyContinue if (-not $systemPython) { Write-Host " Python was not found. Install Python 3.10-3.12, then rerun this script." -ForegroundColor Red exit 1 } & python -m venv $VenvDir $venvCreated = ($LASTEXITCODE -eq 0) } if (-not $venvCreated) { Write-Host " Failed to create virtualenv." -ForegroundColor Red exit $LASTEXITCODE } } $env:VIRTUAL_ENV = $VenvDir $env:PATH = "$(Join-Path $VenvDir 'Scripts');$env:PATH" if ($PSBoundParameters.ContainsKey("OcrMode")) { $ocrUseGpuValue = if ($OcrMode -eq "gpu") { "true" } else { "false" } Set-DotEnvValue "OCR_USE_GPU" $ocrUseGpuValue $env:OCR_USE_GPU = $ocrUseGpuValue Write-Host "OCR mode set to $($OcrMode.ToUpperInvariant()) and saved to .env." -ForegroundColor Green } # ── [1/5] Git pull ────────────────────────────────────────────────────────── Write-Host "`n[1/5] Pulling latest code..." -ForegroundColor Cyan Invoke-Step { git pull } "Git pull failed." # ── [2/5] Install/update dependencies ─────────────────────────────────────── Write-Host "`n[2/5] Installing/updating dependencies..." -ForegroundColor Cyan Invoke-Step { & $Python -m pip install -e ".[dev]" -q } "Dependency install failed." $ocrUseGpu = (Get-DotEnvValue "OCR_USE_GPU") if ($ocrUseGpu -and $ocrUseGpu.ToLowerInvariant() -in @("1", "true", "yes", "on")) { Write-Host " GPU mode enabled; checking Paddle CUDA runtime..." -ForegroundColor Cyan if (-not (Test-PythonPackage "paddlepaddle-gpu")) { Invoke-Step { & $Python -m pip install paddlepaddle-gpu==2.6.2 -i https://www.paddlepaddle.org.cn/packages/stable/cu118/ -q } "Paddle GPU install failed." } if (-not (Test-PythonPackage "nvidia-cudnn-cu11")) { Invoke-Step { & $Python -m pip install nvidia-cudnn-cu11==8.9.5.29 -q } "NVIDIA cuDNN install failed." } Add-NvidiaDllPaths } else { Write-Host " CPU mode enabled; checking Paddle CPU runtime..." -ForegroundColor Cyan if (-not ((Test-PythonPackage "paddlepaddle") -or (Test-PythonPackage "paddlepaddle-gpu"))) { Invoke-Step { & $Python -m pip install paddlepaddle==2.6.2 -q } "Paddle CPU install failed." } } # ── [3/5] Database migration ───────────────────────────────────────────────── Write-Host "`n[3/5] Running database migrations..." -ForegroundColor Cyan & $Python -m alembic upgrade head if ($LASTEXITCODE -ne 0) { Write-Host " Migration conflict detected, stamping current state as head..." -ForegroundColor Yellow Invoke-Step { & $Python -m alembic stamp head } "Alembic stamp failed." Write-Host " Retrying upgrade for any remaining new migrations..." -ForegroundColor Yellow & $Python -m alembic upgrade head if ($LASTEXITCODE -ne 0) { Write-Host " Migration still failed. Please check alembic manually." -ForegroundColor Red exit 1 } } Write-Host " Migrations OK." -ForegroundColor Green # ── [4/5] Free up port ─────────────────────────────────────────────────────── Write-Host "`n[4/5] Checking port $Port..." -ForegroundColor Cyan # Use Get-NetTCPConnection for reliable port detection on Windows $connections = Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue if ($connections) { foreach ($conn in $connections) { $procId = $conn.OwningProcess $procName = (Get-Process -Id $procId -ErrorAction SilentlyContinue).Name Write-Host " Port $Port used by '$procName' (PID $procId), killing..." -ForegroundColor Yellow Stop-Process -Id $procId -Force -ErrorAction SilentlyContinue } # Wait until port is actually released (max 5 seconds) $waited = 0 do { Start-Sleep -Milliseconds 500 $waited += 500 $still = Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue } while ($still -and $waited -lt 5000) if ($still) { Write-Host " Port $Port still in use after waiting. Try a different port or restart manually." -ForegroundColor Red exit 1 } Write-Host " Port $Port freed." -ForegroundColor Green } else { Write-Host " Port $Port is free." -ForegroundColor Green } # ── [5/5] Start dev server ─────────────────────────────────────────────────── Write-Host "`n[5/5] Starting dev server on port $Port (Ctrl+C to stop)..." -ForegroundColor Cyan & $Python -m uvicorn ocr_sprint.main:app --reload --host 0.0.0.0 --port $Port