feat(deploy): 增加受控更新与环境配置迁移

This commit is contained in:
2026-09-27 11:12:55 +08:00
parent 74ada0b0bf
commit 195e2e5a98
12 changed files with 667 additions and 17 deletions
+89 -6
View File
@@ -58,6 +58,10 @@ if ($args -contains "-h" -or $args -contains "--help") {
Write-Host " false 等同 UPDATE_MODE=full"
Write-Host " UPDATE_NO_WAIT=1"
Write-Host " 结束或失败时不等待按 Enter"
Write-Host " UPDATE_HEALTH_TIMEOUT_SECONDS=秒数"
Write-Host " 等待核心服务恢复可用的最长时间,默认 300 秒"
Write-Host ""
Write-Host "仓库更新后会自动执行 scripts/update-env.d 中的幂等环境配置迁移。"
exit 0
}
@@ -136,9 +140,10 @@ if (-not $skipRepoUpdate) {
Write-Err "当前目录不是 Git 仓库,请使用 git clone 克隆项目后使用 update.ps1"
}
Write-Step "📥 正在执行 git pull..."
$beforeRevision = (git rev-parse HEAD).Trim()
Write-Step "📥 正在执行 git pull --ff-only..."
try {
$output = git pull 2>&1
$output = git pull --ff-only 2>&1
if ($LASTEXITCODE -ne 0) { throw "git pull 返回 $LASTEXITCODE" }
Write-Ok "仓库已更新到最新版本"
} catch {
@@ -165,6 +170,16 @@ if (-not $skipRepoUpdate) {
Write-Ok ".env.AMS"
}
$afterRevision = (git rev-parse HEAD).Trim()
if ($beforeRevision -ne $afterRevision -and $env:EASYAI_UPDATE_REEXECUTED -ne "1") {
Write-Step "🔄 检测到部署仓库已更新,切换到新版更新脚本继续执行..."
$env:EASYAI_UPDATE_REEXECUTED = "1"
$env:UPDATE_SKIP_REPO_UPDATE = "true"
$hostExecutable = (Get-Process -Id $PID).Path
& $hostExecutable -NoProfile -ExecutionPolicy Bypass -File $PSCommandPath
exit $LASTEXITCODE
}
Write-Host ""
} else {
Write-Host ""
@@ -174,6 +189,12 @@ if (-not $skipRepoUpdate) {
# 空值、历史默认 JWT 密钥和缺失的 WS ticket 密钥只初始化一次并写回 .env;已有强密钥保持不变。
if (-not (Test-Path ".env")) { Write-Err "未找到 .env,请先执行 start.ps1 完成初始化" }
# 每次更新都执行版本化、幂等的环境配置迁移;已有非空自定义值保持不变。
& (Join-Path $scriptDir "scripts\Apply-UpdateEnvMigrations.ps1") `
-EnvPath (Join-Path $scriptDir ".env") `
-ProjectRoot $scriptDir
. (Join-Path $scriptDir "scripts\Initialize-ServerHttpBindIp.ps1")
Initialize-ServerHttpBindIp -Path (Join-Path $scriptDir ".env")
@@ -243,21 +264,83 @@ Assert-RedisPersistence -ProjectRoot $scriptDir
$hasComposeV2 = $false
try { $null = docker compose version 2>&1; $hasComposeV2 = $true } catch { }
function Invoke-Compose {
param([Parameter(ValueFromRemainingArguments = $true)][string[]]$ComposeArgs)
if ($hasComposeV2) {
& docker compose @ComposeArgs
} else {
& docker-compose @ComposeArgs
}
return $LASTEXITCODE
}
function Wait-CoreServices {
$timeoutSeconds = 300
if (-not [string]::IsNullOrWhiteSpace($env:UPDATE_HEALTH_TIMEOUT_SECONDS)) {
if (-not [int]::TryParse($env:UPDATE_HEALTH_TIMEOUT_SECONDS, [ref]$timeoutSeconds) -or $timeoutSeconds -le 0) {
Write-Err "UPDATE_HEALTH_TIMEOUT_SECONDS 必须是正整数,当前为: $($env:UPDATE_HEALTH_TIMEOUT_SECONDS)"
}
}
$services = @("easyai-server", "ws-gateway", "easyai-web")
$deadline = (Get-Date).AddSeconds($timeoutSeconds)
Write-Step "⏳ 正在等待核心服务恢复可用(最长 $timeoutSeconds 秒)..."
while ((Get-Date) -lt $deadline) {
$allReady = $true
foreach ($service in $services) {
if ($hasComposeV2) {
$containerId = (& docker compose ps -q $service 2>$null | Select-Object -First 1)
} else {
$containerId = (& docker-compose ps -q $service 2>$null | Select-Object -First 1)
}
if ([string]::IsNullOrWhiteSpace($containerId)) {
$allReady = $false
continue
}
$state = (& docker inspect --format '{{.State.Running}}|{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' $containerId 2>$null)
$parts = "$state".Trim().Split('|')
if ($parts.Count -lt 2 -or $parts[0] -ne "true" -or ($parts[1] -ne "none" -and $parts[1] -ne "healthy")) {
$allReady = $false
}
}
if ($allReady) {
Write-Ok "核心服务已恢复可用"
return
}
Start-Sleep -Seconds 5
}
Write-Host "❌ 更新后核心服务未在 $timeoutSeconds 秒内恢复可用" -ForegroundColor Red
if ($hasComposeV2) {
docker compose ps easyai-server ws-gateway easyai-web
} else {
docker-compose ps easyai-server ws-gateway easyai-web
}
Write-Err "请检查核心服务日志,确认失败原因后重试"
}
# ==================== 更新并启动 ====================
Write-Host ""
Write-Step "🚀 重新启动 EasyAI..."
Write-Step "📦 当前磁盘空间:"
Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Root -eq ([System.IO.Path]::GetPathRoot($scriptDir)) } | Format-Table Name, Used, Free -AutoSize
if ($hasComposeV2) {
docker compose pull
if ($LASTEXITCODE -ne 0) { Write-Err "docker compose pull 失败" }
if ($LASTEXITCODE -ne 0) { Write-Err "镜像拉取失败,请检查网络、镜像仓库权限和服务器磁盘空间后重试" }
docker compose up -d
if ($LASTEXITCODE -ne 0) { Write-Err "docker compose up 失败" }
if ($LASTEXITCODE -ne 0) { Write-Err "容器启动失败,EasyAI 未被标记为更新成功" }
} else {
docker-compose pull
if ($LASTEXITCODE -ne 0) { Write-Err "docker-compose pull 失败" }
if ($LASTEXITCODE -ne 0) { Write-Err "镜像拉取失败,请检查网络、镜像仓库权限和服务器磁盘空间后重试" }
docker-compose up -d
if ($LASTEXITCODE -ne 0) { Write-Err "docker-compose up 失败" }
if ($LASTEXITCODE -ne 0) { Write-Err "容器启动失败,EasyAI 未被标记为更新成功" }
}
Wait-CoreServices
Write-Host ""
Write-Host "================================"
Write-Host " 更新完成"