feat(deploy): 增加受控更新与环境配置迁移
This commit is contained in:
+89
-6
@@ -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 " 更新完成"
|
||||
|
||||
Reference in New Issue
Block a user