Files
easyai/scripts/Initialize-ApplicationVersions.ps1
T
wangbo b99eb849b4
Test deployment public URL (Linux) / test-linux (push) Waiting to run
Test start.ps1 (Windows) / test-windows (push) Waiting to run
fix(deploy): 修复组件版本解析和安装升级验收流程
2026-10-18 00:18:00 +08:00

148 lines
9.8 KiB
PowerShell

$script:ApplicationReleaseRoot = Split-Path $PSScriptRoot -Parent
function Read-DeploymentReleaseRecord {
param([string]$Path, [string]$Url='')
if (-not $Url) { return (Get-Content $Path -Raw -Encoding UTF8 | ConvertFrom-Json) }
try {
$address = [Uri]$Url
if ($address.Scheme -notin @('http','https') -or $address.UserInfo) { throw 'Invalid release URL' }
return ((Invoke-WebRequest -UseBasicParsing -Uri $address -TimeoutSec 20 -Headers @{'Accept'='application/json';'Cache-Control'='no-cache'}).Content | ConvertFrom-Json)
} catch { throw '无法读取正式发布源;保留当前服务,不使用历史清单宣称已更新' }
}
function Get-ApplicationVersionPlan {
param([string]$Path)
$content = Get-Content $Path -Raw -Encoding UTF8
$selected = @(([string]$env:UPDATE_SERVICES).Trim() -split '\s+' | Where-Object { $_ })
$requested = if ($env:UPDATE_RELEASE_VERSION) { $env:UPDATE_RELEASE_VERSION } else { $env:DEPLOY_RELEASE_VERSION }
if ($requested -and $requested -notmatch '^\d+\.\d+\.\d+\.\d{8}(\.\d+)?$') { throw '产品版本必须为完整版本号' }
$relative = if ($requested) { "system-update/releases/$requested.json" } else { 'system-update/release-manifest.json' }
$sourceMode = if ($null -ne $env:UPDATE_MANIFEST_SOURCE) { $env:UPDATE_MANIFEST_SOURCE } else { Get-EnvValue $content 'UPDATE_MANIFEST_SOURCE' }
if (-not $sourceMode) { $sourceMode = 'remote' }
$sourceMode = $sourceMode.Trim().ToLowerInvariant()
if ($sourceMode -notin @('remote','local')) { throw 'UPDATE_MANIFEST_SOURCE 只支持 remote 或 local' }
$releaseUrl = if ($null -ne $env:UPDATE_MANIFEST_URL) { $env:UPDATE_MANIFEST_URL } else { Get-EnvValue $content 'UPDATE_MANIFEST_URL' }
if (-not $releaseUrl) { $releaseUrl = 'https://git.51easyai.com/wangbo/easyai/raw/branch/main/system-update/release-manifest.json' }
try {
$address = [Uri]$releaseUrl
if ($address.Scheme -notin @('http','https') -or $address.UserInfo -or $releaseUrl -match '[\x00-\x1f\\]') { throw 'invalid URL' }
} catch { throw '发布源必须是有效 HTTP(S) 地址,不能内嵌登录凭据或控制字符' }
$manifestPath = Join-Path $script:ApplicationReleaseRoot $relative
if ($sourceMode -eq 'remote' -and -not $requested) {
$manifest = Read-DeploymentReleaseRecord -Url $releaseUrl
} elseif (Test-Path $manifestPath) {
$manifest = Read-DeploymentReleaseRecord -Path $manifestPath
} elseif ($sourceMode -eq 'remote') {
$manifest = Read-DeploymentReleaseRecord -Url ([Uri]::new([Uri]$releaseUrl,"releases/$requested.json").AbsoluteUri)
} else { $manifest = Read-DeploymentReleaseRecord -Path $manifestPath }
$version = [string]$manifest.productVersion
if ($manifest.schemaVersion -ne 1 -or $version -notmatch '^\d+\.\d+\.\d+\.\d{8}(\.\d+)?$' -or $manifest.platform -ne 'linux/amd64' -or (-not $requested -and $manifest.channel -ne 'stable') -or ($requested -and $version -ne $requested)) { throw '发布记录的版本、通道或平台不正确' }
$support = Read-DeploymentReleaseRecord -Path (Join-Path $script:ApplicationReleaseRoot 'system-update/deployment-components.json')
$records = @{}
foreach ($item in $support.components) { $records[$item.key] = $item }
$productPath = Join-Path $script:ApplicationReleaseRoot "system-update/releases/$version.json"
if ((Test-Path $productPath) -or $sourceMode -eq 'remote') {
$product = if (Test-Path $productPath) { Read-DeploymentReleaseRecord -Path $productPath } else {
Read-DeploymentReleaseRecord -Url ([Uri]::new([Uri]$releaseUrl,"releases/$version.json").AbsoluteUri)
}
if ($product.productVersion -ne $version) { throw '配套产物记录与产品版本不一致' }
foreach ($item in $product.components) { $records[$item.key] = $item }
}
foreach ($item in $manifest.components) { $records[$item.key] = $item }
foreach ($item in $manifest.deploymentComponents) { $records[$item.key] = $item }
$mapping = @(
@('easyai-server','server-main','SERVER_VERSION','UPDATE_SERVER_VERSION','comfy-ai/comfy-server'),
@('easyai-web','web','WEB_VERSION','UPDATE_WEB_VERSION','comfy-ai/one-ai'),
@('ws-gateway','ws-gateway','WS_GATEWAY_VERSION','UPDATE_GATEWAY_VERSION','easyaigc/wsgateway'),
@('model-conversion-worker','model-conversion-worker','MODEL_CONVERSION_WORKER_VERSION','UPDATE_WORKER_VERSION','easyaigc/model-conversion-worker'),
@('easyai-updater','updater','UPDATER_VERSION','UPDATE_UPDATER_VERSION','easyaigc/easyai-updater'),
@('easyai-asg','service-governance','ASG_VERSION','UPDATE_ASG_VERSION','easyaigc/agent-governance'),
@('agent-memory','memory-service','AMS_VERSION','UPDATE_AMS_VERSION','easyaigc/agent-memory')
)
$plan = @()
$mode = Get-EnvValue $content 'SYSTEM_UPDATE_MODE'
if (-not $mode) { $mode = $env:EASYAI_UPDATE_MODE_HINT }
$legacy = $mode -eq 'legacy_watchtower'
foreach ($entry in $mapping) {
$service,$component,$key,$option,$repository = $entry
$explicit = [Environment]::GetEnvironmentVariable($option)
if ($selected.Count -gt 0 -and $service -notin $selected) {
if ($explicit) { throw "$option 要求在 UPDATE_SERVICES 中指定 $service" }
continue
}
$image = "registry.cn-shanghai.aliyuncs.com/$repository"
$stableDigest = ""
$variant = ""
if ($explicit) {
$pattern = '^\d+\.\d+\.\d+\.\d{8}(\.\d+)?'
if ($service -eq 'easyai-web') { $pattern += '(-no-ssr|-hmg-ai|-easyai|-chinacuc)?' }
if ($service -eq 'easyai-updater') { $pattern = '^\d+\.\d+\.\d+(\.\d{8}(\.\d+)?)?' }
if ($explicit -notmatch ($pattern + '(@sha256:[a-f0-9]{64})?$')) { throw "$option 必须是已发布的固定版本,不能使用浮动标签" }
$target = $explicit
} else {
$item = $records[$component]
$oldWeb = Get-EnvValue $content 'WEB_VERSION'
if (-not $oldWeb) { $oldWeb = Get-EnvValue $content 'VERSION' }
if ($service -eq 'easyai-web' -and $oldWeb -match '(-no-ssr|-hmg-ai|-easyai|-chinacuc)(@.*)?$') {
$variant = $Matches[1]
$variantKey = if ($variant -eq '-no-ssr') { 'web-no-ssr' } else { $variant.Substring(1) }
$item = $records[$variantKey]
if (-not $item) { throw "所选发布没有当前网页变体 $variant;请显式指定 UPDATE_WEB_VERSION" }
}
if (-not $item -or $item.image -ne $image -or $item.digest -notmatch '^sha256:[a-f0-9]{64}$') { throw "发布记录缺少有效配套产物 $component" }
$tag = if ($item.tag) { [string]$item.tag } else { 'latest' }
if ($tag -notmatch '^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$') { throw '配套产物标签不正确' }
$target = if ($service -eq 'easyai-updater' -and $tag -eq 'latest') { $tag } else { "$tag@$($item.digest)" }
if ($legacy -and -not $requested -and $component -in @('server-main','web','ws-gateway')) {
$target = 'latest' + $variant
$stableDigest = [string]$item.digest
}
}
$plan += [PSCustomObject]@{ Service=$service; Key=$key; Version=$target; Image=$image }
if ($component -in @('server-main','web','ws-gateway')) {
$plan += [PSCustomObject]@{ Service=''; Key=$key.Replace('_VERSION','_STABLE_DIGEST'); Version=$stableDigest; Image='' }
}
}
if ($selected.Count -eq 0 -or ('easyai-server' -in $selected -and 'easyai-web' -in $selected)) {
$plan += [PSCustomObject]@{ Service=''; Key='VERSION'; Version=$version; Image='' }
}
return $plan
}
function Set-ApplicationVersions {
param([string]$Path, [object[]]$Plan)
$content = Get-Content $Path -Raw -Encoding UTF8
foreach ($item in $Plan) { $content = Set-EnvValue $content $item.Key $item.Version }
[System.IO.File]::WriteAllText($Path, $content, [System.Text.UTF8Encoding]::new($false))
foreach ($item in $Plan) { Write-Host "📌 已持久化应用版本: $($item.Key)=$($item.Version)" }
}
function Test-ApplicationVersionImages {
param([object[]]$Plan, [string[]]$ComposeCommand)
foreach ($item in $Plan) {
if (-not $item.Service) { continue }
$args = @($ComposeCommand | Select-Object -Skip 1) + @('config','--format','json',$item.Service)
$executable = $ComposeCommand[0]
$configuration = & $executable @args
if ($LASTEXITCODE -ne 0) { throw '无法读取应用的有效镜像定义;尚未拉取或重建服务' }
try {
# Compose includes dependencies even when a service is selected.
# Keep the configuration in memory; do not log its environment.
$resolved = ($configuration -join "`n") | ConvertFrom-Json -ErrorAction Stop
$actual = $resolved.services.PSObject.Properties[$item.Service].Value.image
} catch { throw '无法读取应用的有效镜像定义;尚未拉取或重建服务' }
if ("$actual".Trim() -ne "$($item.Image):$($item.Version)") {
throw "$($item.Service) 的有效镜像未使用环境中的固定版本;请通过部署仓库的受控迁移恢复镜像定义,尚未拉取或重建服务"
}
}
}
function Set-DeploymentReleaseSource {
param([string]$Path)
$content = Get-Content $Path -Raw -Encoding UTF8
if ($null -ne $env:UPDATE_MANIFEST_SOURCE) {
$mode = if ($env:UPDATE_MANIFEST_SOURCE) { $env:UPDATE_MANIFEST_SOURCE.Trim().ToLowerInvariant() } else { 'remote' }
$content = Set-EnvValue $content 'UPDATE_MANIFEST_SOURCE' $mode
}
if ($null -ne $env:UPDATE_MANIFEST_URL) { $content = Set-EnvValue $content 'UPDATE_MANIFEST_URL' $env:UPDATE_MANIFEST_URL }
[System.IO.File]::WriteAllText($Path, $content, [System.Text.UTF8Encoding]::new($false))
}