flnxi-Wu/scripts/smoke-install.ps1
2026-05-30 19:33:59 +08:00

196 lines
6.0 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.

param(
[string]$ProjectDir = ".",
[string]$ComposeFile = "docker-compose.prod.yml",
[string]$EnvFile = ".env",
[string]$AppService = "app",
[string]$PostgresService = "postgres",
[string]$MinioService = "minio",
[switch]$Start,
[switch]$SkipMigrationStatus,
[switch]$SkipObjectStorageCheck,
[int]$HealthTimeoutSeconds = 120
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
function Write-Info {
param([string]$Message)
Write-Host "[INFO] $Message"
}
function Write-Ok {
param([string]$Message)
Write-Host "[OK] $Message"
}
function Read-EnvFile {
param([string]$Path)
$values = @{}
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
return $values
}
foreach ($line in Get-Content -LiteralPath $Path) {
if ($line -match '^\s*#' -or $line -notmatch '=') {
continue
}
$parts = $line -split '=', 2
$key = $parts[0].Trim()
if ([string]::IsNullOrWhiteSpace($key)) {
continue
}
$value = $parts[1].Trim()
if (($value.StartsWith('"') -and $value.EndsWith('"')) -or ($value.StartsWith("'") -and $value.EndsWith("'"))) {
$value = $value.Substring(1, $value.Length - 2)
}
$values[$key] = $value
}
return $values
}
function Get-EnvValue {
param([hashtable]$EnvValues, [string]$Name, [string]$Default = "")
if ($EnvValues.ContainsKey($Name)) {
return $EnvValues[$Name]
}
return $Default
}
function Invoke-DockerCompose {
param([string[]]$Arguments)
& docker @Arguments
if ($LASTEXITCODE -ne 0) {
throw "docker 命令失败,退出码:$LASTEXITCODE"
}
}
function Get-ServiceContainerId {
param([string[]]$ComposeArgs, [string]$Service)
$containerId = (& docker @ComposeArgs ps -q $Service).Trim()
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($containerId)) {
throw "未找到服务容器:$Service。"
}
return $containerId
}
function Wait-Health {
param([string]$HealthUrl, [int]$TimeoutSeconds)
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
do {
try {
$response = Invoke-WebRequest -Uri $HealthUrl -UseBasicParsing -TimeoutSec 10
if ($response.StatusCode -eq 200) {
Write-Ok "/api/health 正常"
return
}
} catch {
Start-Sleep -Seconds 5
}
} while ((Get-Date) -lt $deadline)
throw "/api/health 在 $TimeoutSeconds 秒内没有通过:$HealthUrl"
}
function Wait-ServiceRunning {
param([string]$ContainerId, [string]$Service, [int]$TimeoutSeconds)
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
do {
$status = (& docker inspect $ContainerId --format '{{.State.Status}}').Trim()
if ($LASTEXITCODE -eq 0 -and $status -eq "running") {
Write-Ok "$Service 容器已运行"
return
}
Start-Sleep -Seconds 5
} while ((Get-Date) -lt $deadline)
throw "$Service 容器在 $TimeoutSeconds 秒内没有稳定运行。"
}
$resolvedProjectDir = (Resolve-Path -LiteralPath $ProjectDir).Path
Push-Location $resolvedProjectDir
try {
if (-not (Test-Path -LiteralPath $ComposeFile -PathType Leaf)) {
throw "Compose 文件不存在:$ComposeFile"
}
$composePath = (Resolve-Path -LiteralPath $ComposeFile).Path
$envPath = Join-Path $resolvedProjectDir $EnvFile
$envValues = Read-EnvFile $envPath
$composeArgs = @("compose", "-f", $composePath)
if (Test-Path -LiteralPath $envPath -PathType Leaf) {
$composeArgs += @("--env-file", $envPath)
}
Write-Info "检查 Compose 配置"
Invoke-DockerCompose ($composeArgs + @("config", "--quiet"))
if ($Start) {
Write-Info "启动客户安装编排"
Invoke-DockerCompose ($composeArgs + @("up", "-d"))
}
Write-Info "检查容器状态"
Invoke-DockerCompose ($composeArgs + @("ps"))
$appContainerId = Get-ServiceContainerId -ComposeArgs $composeArgs -Service $AppService
Wait-ServiceRunning -ContainerId $appContainerId -Service $AppService -TimeoutSeconds $HealthTimeoutSeconds
$runningImage = (& docker inspect $appContainerId --format '{{.Config.Image}}').Trim()
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($runningImage)) {
throw "无法读取应用运行镜像。"
}
Write-Ok "应用运行镜像:$runningImage"
$versionOutput = & docker @composeArgs exec -T $AppService sh -c 'cat VERSION.txt 2>/dev/null || true'
$version = "$versionOutput".Trim()
if (-not [string]::IsNullOrWhiteSpace($version)) {
Write-Ok "应用版本文件:$version"
} else {
Write-Host "[WARN] 镜像内未读到 VERSION.txt,请确认发布镜像是否包含版本文件。"
}
$publicUrl = Get-EnvValue $envValues "APP_PUBLIC_URL"
if ([string]::IsNullOrWhiteSpace($publicUrl)) {
$publicUrl = Get-EnvValue $envValues "NEXTAUTH_URL"
}
if ([string]::IsNullOrWhiteSpace($publicUrl)) {
throw "未配置 APP_PUBLIC_URL / NEXTAUTH_URL,无法验收健康接口。"
}
Wait-Health -HealthUrl ($publicUrl.TrimEnd("/") + "/api/health") -TimeoutSeconds $HealthTimeoutSeconds
if (-not $SkipMigrationStatus) {
Write-Info "检查数据库迁移状态"
Invoke-DockerCompose ($composeArgs + @("exec", "-T", $AppService, "npx", "prisma", "migrate", "status", "--schema=prisma/schema.prisma"))
}
if (-not $SkipObjectStorageCheck) {
$bucket = Get-EnvValue $envValues "S3_BUCKET" "flnxi-materials"
Write-Info "检查 MinIO bucket:$bucket"
Invoke-DockerCompose ($composeArgs + @("exec", "-T", $MinioService, "sh", "-c", "test -d /data/$bucket"))
Write-Ok "对象存储 bucket 存在:$bucket"
}
Write-Info "最近应用日志"
& docker @composeArgs logs --tail 80 $AppService
if ($LASTEXITCODE -ne 0) {
throw "读取应用日志失败。"
}
Write-Ok "首装基础验收完成"
} finally {
Pop-Location
}