Files
easyai/update.ps1
T

263 lines
9.4 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#Requires -Version 5.1
# EasyAI Windows 更新脚本
# 拉取仓库最新代码,更新镜像并重启服务
# 用法: .\update.ps1
# 设置控制台编码为 UTF-8
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8
$ErrorActionPreference = "Stop"
$script:LogDir = $PSScriptRoot
if (-not $script:LogDir) { $script:LogDir = $env:TEMP }
$script:LogFile = Join-Path $script:LogDir "update.ps1.log"
function Write-Log {
param([string]$Msg)
try {
$line = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $Msg"
Add-Content -Path $script:LogFile -Value $line -Encoding UTF8 -ErrorAction SilentlyContinue
} catch { }
}
trap {
$errMsg = $_.Exception.Message
try {
Add-Content -Path $script:LogFile -Value "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] 错误: $errMsg" -Encoding UTF8 -ErrorAction SilentlyContinue
} catch { }
Write-Host ""
Write-Host "发生错误: $errMsg" -ForegroundColor Red
if ($env:CI -ne "true") { Read-Host "按 Enter 键退出" }
exit 1
}
function Wait-ForExit {
if ($env:CI -eq "true") { return }
if ($env:UPDATE_NO_WAIT -eq "1") { return }
Write-Host ""
Read-Host "按 Enter 键退出"
}
function Write-Step { param($Msg) Write-Host $Msg }
function Write-Ok { param($Msg) Write-Host " ✓ $Msg" -ForegroundColor Green }
function Write-Err { param($Msg) Write-Host "❌ $Msg" -ForegroundColor Red; throw $Msg }
function Write-Warn { param($Msg) Write-Host "⚠️ $Msg" -ForegroundColor Yellow }
# -h/--help
if ($args -contains "-h" -or $args -contains "--help") {
Write-Host "用法: .\update.ps1"
Write-Host ""
Write-Host "默认拉取仓库并更新镜像;也可通过环境变量跳过交互选择。"
Write-Host ""
Write-Host "环境变量:"
Write-Host " UPDATE_MODE=full|image"
Write-Host " full 更新并拉取仓库(git pull)+ 更新镜像并重启"
Write-Host " image 仅更新镜像并重启(跳过 git pull)"
Write-Host " UPDATE_SKIP_REPO_UPDATE=true|false"
Write-Host " true 等同 UPDATE_MODE=image"
Write-Host " false 等同 UPDATE_MODE=full"
Write-Host " UPDATE_NO_WAIT=1"
Write-Host " 结束或失败时不等待按 Enter"
exit 0
}
# 进入项目目录
$scriptDir = $PSScriptRoot
if (-not $scriptDir) { Write-Err "无法获取脚本所在目录" }
$composePath = Join-Path $scriptDir "docker-compose.yml"
if (-not (Test-Path $composePath)) { Write-Err "未找到 docker-compose.yml,请在 easyai 目录下运行 update.ps1" }
Set-Location $scriptDir
Write-Step "📁 项目目录: $scriptDir"
function ConvertTo-BoolEnv {
param([string]$Value)
$v = if ($null -eq $Value) { "" } else { $Value.Trim().ToLowerInvariant() }
switch ($v) {
{ $_ -in @("1", "true", "yes", "y", "on") } { return $true }
{ $_ -in @("0", "false", "no", "n", "off", "") } { return $false }
default { Write-Err "布尔变量值不合法: $Value(支持 true/false/1/0/yes/no/y/n/on/off" }
}
}
function Resolve-UpdateMode {
if (-not [string]::IsNullOrWhiteSpace($env:UPDATE_SKIP_REPO_UPDATE)) {
$skip = ConvertTo-BoolEnv $env:UPDATE_SKIP_REPO_UPDATE
Write-Step "使用环境变量: UPDATE_SKIP_REPO_UPDATE=$skip"
return $skip
}
if (-not [string]::IsNullOrWhiteSpace($env:UPDATE_MODE)) {
$mode = $env:UPDATE_MODE.Trim().ToLowerInvariant()
switch ($mode) {
{ $_ -in @("full", "repo", "pull", "git", "1") } {
Write-Step "使用环境变量: UPDATE_MODE=$($env:UPDATE_MODE)"
return $false
}
{ $_ -in @("image", "images", "docker", "skip-git", "skip_repo", "skip-repo", "2") } {
Write-Step "使用环境变量: UPDATE_MODE=$($env:UPDATE_MODE)"
return $true
}
default { Write-Err "UPDATE_MODE 只能是 full 或 image,当前为: $($env:UPDATE_MODE)" }
}
}
if ($env:UPDATE_NON_INTERACTIVE -eq "1" -or $env:CI -eq "true") {
Write-Step "使用默认更新方式: 更新并拉取仓库(git pull)+ 更新镜像并重启"
return $false
}
Write-Host ""
Write-Host "请选择更新方式:"
Write-Host " [1] 更新并拉取仓库(git pull)+ 更新镜像并重启(默认)"
Write-Host " [2] 仅更新镜像并重启(跳过 git pull)"
$choice = Read-Host "请选择 [1/2,回车默认 1]"
if ([string]::IsNullOrWhiteSpace($choice)) { $choice = "1" }
switch ($choice) {
"2" { return $true }
"1" { return $false }
default {
Write-Warn "无效选择,将使用默认:更新并拉取仓库"
return $false
}
}
}
$skipRepoUpdate = Resolve-UpdateMode
# ==================== 拉取仓库 ====================
if (-not $skipRepoUpdate) {
Write-Host ""
Write-Host "==========================="
Write-Host "📥 拉取仓库最新代码"
Write-Host "==========================="
if (-not (Test-Path ".git")) {
Write-Err "当前目录不是 Git 仓库,请使用 git clone 克隆项目后使用 update.ps1"
}
Write-Step "📥 正在执行 git pull..."
try {
$output = git pull 2>&1
if ($LASTEXITCODE -ne 0) { throw "git pull 返回 $LASTEXITCODE" }
Write-Ok "仓库已更新到最新版本"
} catch {
Write-Err "git pull 失败,请检查网络或远程仓库配置"
}
# 确保环境配置文件存在
Write-Host ""
Write-Step "📝 检查环境配置文件..."
if (-not (Test-Path ".env") -and (Test-Path ".env.sample")) {
Copy-Item ".env.sample" ".env"
Write-Ok ".env"
}
if (-not (Test-Path ".env.tools") -and (Test-Path ".env.tools.sample")) {
Copy-Item ".env.tools.sample" ".env.tools"
Write-Ok ".env.tools"
}
if (-not (Test-Path ".env.ASG") -and (Test-Path ".env.ASG.sample")) {
Copy-Item ".env.ASG.sample" ".env.ASG"
Write-Ok ".env.ASG"
}
if (-not (Test-Path ".env.AMS") -and (Test-Path ".env.AMS.sample")) {
Copy-Item ".env.AMS.sample" ".env.AMS"
Write-Ok ".env.AMS"
}
Write-Host ""
} else {
Write-Host ""
Write-Step "⏭️ 跳过仓库更新,仅更新镜像并重启"
Write-Host ""
}
# 空值或历史默认值只初始化一次并写回 .env;已有自定义值及数据库轮转配置不受影响。
if (-not (Test-Path ".env")) { Write-Err "未找到 .env,请先执行 start.ps1 完成初始化" }
. (Join-Path $scriptDir "scripts\Initialize-SecurityEnv.ps1")
Initialize-SecurityEnv -Path (Join-Path $scriptDir ".env") -Mode "upgrade"
# 在更新镜像和重建服务前迁移并校验后端公开 API 地址。
. (Join-Path $scriptDir "scripts\Initialize-PublicApiBaseUrl.ps1")
Initialize-PublicApiBaseUrl `
-Path (Join-Path $scriptDir ".env") `
-Mode "upgrade" `
-Override $env:DEPLOY_PUBLIC_API_BASE_URL
# ==================== Docker 检查 ====================
function Test-DockerInstalled {
$docker = Get-Command docker -ErrorAction SilentlyContinue
if (-not $docker) { return $false }
try { $null = & docker --version 2>&1; return $true } catch { return $false }
}
function Test-DockerRunning {
param([int]$TimeoutSeconds = 10)
try {
$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = "docker"; $psi.Arguments = "info"
$psi.RedirectStandardOutput = $true; $psi.RedirectStandardError = $true
$psi.UseShellExecute = $false; $psi.CreateNoWindow = $true
$p = [System.Diagnostics.Process]::Start($psi)
$exited = $p.WaitForExit($TimeoutSeconds * 1000)
if (-not $exited) { try { $p.Kill() } catch { }; return $false }
return ($p.ExitCode -eq 0)
} catch { return $false }
}
function Ensure-DockerRunning {
if (-not (Test-DockerInstalled)) { return $false }
if (Test-DockerRunning) {
Write-Ok "Docker 引擎已运行"
$v = docker --version 2>$null; if ($v) { Write-Host " $v" }
return $true
}
Write-Warn "Docker 未运行,请手动启动 Docker Desktop 后重新执行"
return $false
}
Write-Host "==========================="
Write-Host "🚀 检查 Docker"
Write-Host "==========================="
Write-Host ""
if (-not (Test-DockerInstalled)) {
Write-Err "未检测到 Docker,请先运行 start.ps1 完成首次部署"
}
if (-not (Ensure-DockerRunning)) {
Write-Host ""
Write-Host "请启动 Docker Desktop,确认其完全就绪后再重新运行 update.ps1"
Wait-ForExit
exit 1
}
# 检测 docker compose
$hasComposeV2 = $false
try { $null = docker compose version 2>&1; $hasComposeV2 = $true } catch { }
# ==================== 更新并启动 ====================
Write-Host ""
Write-Step "🚀 重新启动 EasyAI..."
if ($hasComposeV2) {
docker compose pull
if ($LASTEXITCODE -ne 0) { Write-Err "docker compose pull 失败" }
docker compose up -d
if ($LASTEXITCODE -ne 0) { Write-Err "docker compose up 失败" }
} else {
docker-compose pull
if ($LASTEXITCODE -ne 0) { Write-Err "docker-compose pull 失败" }
docker-compose up -d
if ($LASTEXITCODE -ne 0) { Write-Err "docker-compose up 失败" }
}
Write-Host ""
Write-Host "================================"
Write-Host " 更新完成"
Write-Host "================================"
Write-Host "🎉 EasyAI 应用更新成功"
Write-Host "访问地址: http://127.0.0.1:3010"
Write-Host ""
Write-Log "=== 更新完成 ==="
Wait-ForExit