flnxi-Wu/deploy-new-fixed.ps1
WuYongJie f646b71813
Some checks failed
CI Check / check (push) Has been cancelled
chore: 初始化本地仓库并同步最近改动
2025-11-18 17:50:16 +08:00

805 lines
28 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.

# ================================================================
# 新建简化部署脚本 - 处理SSH认证问题和本地构建上传
# 优化版部署脚本 - 支持自动SSH密钥处理和错误恢复
# ================================================================
param(
[string]$Server = "45.249.208.118",
[string]$Username = "root",
[string]$Password = "",
[string]$KeyPath = "",
[string]$ImageName = "youyidian-3d-material",
[string]$Tag = "local-deploy",
[string]$LocalPath = "",
[string]$RemotePath = "/root/deploy",
[string]$EnvFile = "",
[switch]$SkipCheck,
[switch]$SkipBuild,
[switch]$SkipUpload,
[switch]$Force,
[switch]$Debug,
[switch]$Help,
[switch]$ForcePuTTY
)
# 脚本配置
Set-StrictMode -Version Latest
$ErrorActionPreference = "Continue"
try {
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
if ($PSVersionTable.PSVersion.Major -ge 6) {
$PSDefaultParameterValues['*:Encoding'] = 'utf8'
}
} catch {
# 控制台编码设置失败时忽略
}
$script:StageTracker = @{
Total = 0
Current = 0
}
$script:FunTips = @(
"☕ 记得喝口咖啡,让部署的快乐多续一会~",
"🎯 今日小目标:让 CI/CD 成为最懂你的搭档。",
"🐱 猫咪提示:部署途中别忘了伸个懒腰。",
"🛰️ 数据正在轨道中穿梭,耐心是宇航员的必备素质。",
"🍀 小彩蛋:部署顺利指数 +1%,好运常伴你。",
"🎵 背景 BGM 同步播放中,部署节奏稳稳的。",
"🧊 如果日志有点长,记得深呼吸,冷静才是王牌技能。",
"📦 镜像像快递一样打包上路,收件人:Production。",
"🚀 每一个 build 都是一次漂亮的起飞。"
)
function Initialize-StageTracker {
param(
[bool]$DoCheck,
[bool]$DoBuild,
[bool]$DoPush,
[bool]$DoRemote
)
$script:StageTracker.Total = @($DoCheck, $DoBuild, $DoPush, $DoRemote) | Where-Object { $_ } | Measure-Object | Select-Object -ExpandProperty Count
if (-not $script:StageTracker.Total -or $script:StageTracker.Total -le 0) {
$script:StageTracker.Total = 1
}
$script:StageTracker.Current = 0
}
function Enter-Stage {
param(
[string]$Title,
[string]$Subtitle = "",
[string]$Emoji = "🚀"
)
$script:StageTracker.Current++
$current = $script:StageTracker.Current
$total = $script:StageTracker.Total
$progress = if ($total -gt 0) { [Math]::Min(1, [Math]::Max(0, ($current - 1) / [double]$total)) } else { 0 }
$barLength = 24
$filledLength = [Math]::Floor($progress * $barLength)
$bar = ("█" * $filledLength).PadRight($barLength, "░")
Write-ColorOutput ""
Write-ColorOutput ("{0} {1} ({2}/{3})" -f $Emoji, $Title, $current, $total) "Magenta"
Write-ColorOutput ("[{0}] {1:P0}" -f $bar, $progress) "Magenta"
if ($Subtitle) {
Write-Info $Subtitle
}
}
function Get-FunTip {
if (-not $script:FunTips -or $script:FunTips.Count -eq 0) {
return $null
}
return Get-Random -InputObject $script:FunTips
}
function Celebrate-Stage {
param([string]$Title)
$tip = Get-FunTip
Write-ColorOutput ("🎉 {0}完成!" -f $Title) "Green"
if ($tip) {
Write-ColorOutput (" {0}" -f $tip) "Gray"
}
}
function Test-RemoteHealth {
param(
[string]$Server,
[int]$Port = 4320,
[int]$MaxWaitSeconds = 45
)
$healthUrl = "http://{0}:{1}/api/health" -f $Server, $Port
Write-Info "开始健康检查: $healthUrl"
$elapsed = 0
$interval = 3
$ok = $false
while ($elapsed -lt $MaxWaitSeconds -and -not $ok) {
try {
$resp = Invoke-WebRequest -Uri $healthUrl -Method GET -TimeoutSec 5 -ErrorAction Stop
if ($resp.StatusCode -eq 200) {
$ok = $true
break
} else {
Write-Warning ("健康检查返回状态码 {0},稍后重试..." -f $resp.StatusCode)
}
} catch {
Write-Info ("应用尚未完全就绪({0}s),继续等待..." -f $elapsed)
}
Start-Sleep -Seconds $interval
$elapsed += $interval
}
if ($ok) {
Write-Success ("健康检查通过,应用已就绪 ({0}s)" -f $elapsed)
$tip = Get-FunTip
if ($tip) {
Write-ColorOutput ("✨ 健康彩蛋: {0}" -f $tip) "Gray"
}
return $true
} else {
Write-Warning ("在 {0}s 内未能通过健康检查,请稍后手动访问 {1} 确认" -f $MaxWaitSeconds, $healthUrl)
return $false
}
}
# 检查是否需要显示帮助
if ($Help) {
# 颜色输出函数
function Write-ColorOutput {
param(
[string]$Message,
[ConsoleColor]$ForegroundColor = "White"
)
Write-Host $Message -ForegroundColor $ForegroundColor
}
Write-ColorOutput "新建简化部署脚本" "Green"
Write-ColorOutput "========================================" "Green"
Write-ColorOutput "用法:" "White"
Write-ColorOutput " .\deploy-new-fixed.ps1 [参数]" "Yellow"
Write-ColorOutput "" "White"
Write-ColorOutput "参数:" "White"
Write-ColorOutput " -Server <地址> 服务器地址 (默认: 45.249.208.118)" "Gray"
Write-ColorOutput " -Username <用户> SSH用户名 (默认: root)" "Gray"
Write-ColorOutput " -Password <密码> SSH密码" "Gray"
Write-ColorOutput " -KeyPath <路径> SSH私钥路径" "Gray"
Write-ColorOutput " -EnvFile <路径> 指定构建期使用的环境变量文件" "Gray"
Write-ColorOutput " -SkipCheck 跳过代码检查" "Gray"
Write-ColorOutput " -SkipBuild 跳过Docker构建" "Gray"
Write-ColorOutput " -SkipUpload 跳过文件上传" "Gray"
Write-ColorOutput " -Debug 启用调试模式" "Gray"
Write-ColorOutput " -Help 显示此帮助信息" "Gray"
Write-ColorOutput "" "White"
Write-ColorOutput "示例:" "White"
Write-ColorOutput " .\deploy-new-fixed.ps1" "Yellow"
Write-ColorOutput " .\deploy-new-fixed.ps1 -Password 'your-password'" "Yellow"
Write-ColorOutput " .\deploy-new-fixed.ps1 -KeyPath 'C:\Users\.ssh\id_rsa'" "Yellow"
Write-ColorOutput " .\deploy-new-fixed.ps1 -SkipCheck -SkipUpload" "Yellow"
exit 0
}
# 颜色输出函数
function Write-ColorOutput {
param(
[string]$Message,
[ConsoleColor]$ForegroundColor = "White"
)
Write-Host $Message -ForegroundColor $ForegroundColor
}
function Write-Success {
param([string]$Message)
Write-ColorOutput "✓ $Message" "Green"
}
function Write-Warning {
param([string]$Message)
Write-ColorOutput "⚠ $Message" "Yellow"
}
function Write-ErrorColored {
param([string]$Message)
Write-ColorOutput "✗ $Message" "Red"
}
function Write-Info {
param([string]$Message)
Write-ColorOutput "ℹ $Message" "Cyan"
}
# 环境变量文件处理
function Resolve-EnvFilePath {
param([string]$Path)
if ([string]::IsNullOrWhiteSpace($Path)) {
return $null
}
try {
return (Resolve-Path $Path -ErrorAction Stop).Path
} catch {
return $null
}
}
function Prepare-BuildEnvironment {
param(
[string]$ProjectRoot,
[string]$EnvFileCandidate,
[string]$Server
)
$state = @{
TargetPath = Join-Path $ProjectRoot ".env.production"
BackupPath = $null
SourcePath = $null
CreatedTemp = $false
}
$targetPath = $state.TargetPath
$existingTarget = Test-Path $targetPath
if ($existingTarget -and [string]::IsNullOrWhiteSpace($EnvFileCandidate)) {
Write-Info "检测到现有 .env.production,直接使用"
return $state
}
$candidatePaths = @()
$resolvedCandidate = Resolve-EnvFilePath $EnvFileCandidate
if ($resolvedCandidate) {
$candidatePaths += $resolvedCandidate
}
$candidatePaths += @(
(Join-Path $ProjectRoot ".env.production.local"),
(Join-Path $ProjectRoot "config-bianliang.env"),
(Join-Path $ProjectRoot ".env.local")
)
foreach ($candidate in $candidatePaths) {
if (-not [string]::IsNullOrWhiteSpace($candidate) -and (Test-Path $candidate)) {
$state.SourcePath = (Resolve-Path $candidate).Path
Write-Info "使用环境变量文件: $($state.SourcePath)"
break
}
}
if ($state.SourcePath) {
if ($existingTarget) {
$state.BackupPath = "$targetPath.deploy-backup"
Copy-Item -Path $targetPath -Destination $state.BackupPath -Force
}
if ($state.SourcePath -ne $targetPath) {
Copy-Item -Path $state.SourcePath -Destination $targetPath -Force
$state.CreatedTemp = $true
}
return $state
}
Write-Warning "未找到可用的环境变量文件,将生成临时占位配置。建议通过 -EnvFile 指定真实配置以避免构建期访问远程服务失败。"
$placeholder = @"
# 生产环境配置 (临时生成)
NODE_ENV=production
NEXT_TELEMETRY_DISABLED=1
# 数据库配置 (占位符)
DATABASE_URL=postgresql://placeholder:placeholder@localhost:5432/placeholder
# 应用URL配置
NEXTAUTH_URL=http://$Server:4320
APP_PUBLIC_URL=http://$Server:4320
# 安全配置
NEXTAUTH_SECRET=$(New-Guid)
# 存储配置
S3_ENDPOINT=https://s3.1080p.hk
S3_BUCKET=jsxiangche
S3_ACCESS_KEY_ID=AEvT1p0wSNjow3Asu9NV
S3_SECRET_ACCESS_KEY=u8nzFl1S6dNoT5PQaQhkHz8Du6iX0hVnPswtvHhs
S3_REGION=us-east-1
S3_PUBLIC_DOMAIN=https://s3.1080p.hk/jsxiangche
S3_ENABLE_PATH_STYLE=1
S3_FILE_PATH=由壹点AI/
# 本地上传目录
UPLOAD_DIR=./uploads
"@
$placeholder | Out-File -FilePath $targetPath -Encoding UTF8 -Force
$state.CreatedTemp = -not $existingTarget
return $state
}
function Restore-BuildEnvironment {
param($State)
if (-not $State) {
return
}
$targetPath = $State.TargetPath
if ($State.BackupPath -and (Test-Path $State.BackupPath)) {
Move-Item -Path $State.BackupPath -Destination $targetPath -Force
return
}
if ($State.CreatedTemp -and (Test-Path $targetPath)) {
Remove-Item -Path $targetPath -Force -ErrorAction SilentlyContinue
}
}
# 文件大小格式化
function Format-FileSize {
param([long]$Size)
if ($Size -ge 1GB) {
return "{0:N2} GB" -f ($Size / 1GB)
} elseif ($Size -ge 1MB) {
return "{0:N2} MB" -f ($Size / 1MB)
} elseif ($Size -ge 1KB) {
return "{0:N2} KB" -f ($Size / 1KB)
} else {
return "$Size B"
}
}
# 检查命令是否存在
function Test-Command {
param([string]$Command)
try {
$null = Get-Command $Command -ErrorAction Stop
return $true
} catch {
return $false
}
}
# 查找SSH密钥文件
function Find-SSHKeys {
$possibleKeys = @(
"$env:USERPROFILE\.ssh\id_rsa",
"$env:USERPROFILE\.ssh\id_ed25519",
"$env:USERPROFILE\.ssh\id_ecdsa",
"$env:USERPROFILE\.ssh\id_dsa"
)
$foundKeys = @()
foreach ($key in $possibleKeys) {
if (Test-Path $key) {
$foundKeys += $key
}
}
return $foundKeys
}
# 查找 PuTTY 工具路径
function Find-PuTTYTools {
$searchPaths = @(
"C:\Program Files\PuTTY",
"C:\Program Files (x86)\PuTTY",
"$env:USERPROFILE\AppData\Local\Programs\PuTTY"
)
foreach ($path in $searchPaths) {
$plinkPath = Join-Path $path "plink.exe"
if (Test-Path $plinkPath) {
return $path
}
}
return $null
}
# 测试SSH连接
function Test-SSHConnection {
param(
[string]$Server,
[string]$Username,
[string]$Password = "",
[string]$KeyPath = ""
)
$target = "${Username}@${Server}"
if ($ForcePuTTY.IsPresent) {
$plinkPath = "C:\Program Files\PuTTY\plink.exe"
if (Test-Path $plinkPath) {
try {
$plinkArgs = @("-batch")
if ($Password) {
Write-Info "使用 PuTTY 密码认证测试连接..."
$plinkArgs += @("-pw", $Password)
} elseif ($KeyPath -and (Test-Path $KeyPath)) {
Write-Info "使用 PuTTY SSH密钥测试连接: $KeyPath"
$plinkArgs += @("-i", $KeyPath)
} else {
Write-ErrorColored "需要提供密码或密钥文件"
return $false
}
$plinkArgs += @($target, "echo 'SSH连接测试成功'")
$process = Start-Process -FilePath $plinkPath -ArgumentList $plinkArgs -NoNewWindow -PassThru -Wait -RedirectStandardOutput temp_stdout.txt -RedirectStandardError temp_stderr.txt
if ($process.ExitCode -eq 0) {
Write-Success "SSH连接测试成功"
Remove-Item temp_stdout.txt, temp_stderr.txt -ErrorAction SilentlyContinue
return $true
} else {
$errorOutput = Get-Content temp_stderr.txt -ErrorAction SilentlyContinue
Write-ErrorColored "SSH连接失败: $errorOutput"
Remove-Item temp_stdout.txt, temp_stderr.txt -ErrorAction SilentlyContinue
return $false
}
} catch {
Write-ErrorColored "SSH测试异常: $_"
Remove-Item temp_stdout.txt, temp_stderr.txt -ErrorAction SilentlyContinue
return $false
}
} else {
Write-Warning "未找到 PuTTY,回退到 OpenSSH"
}
}
$sshArgs = @("-o", "BatchMode=yes", "-o", "ConnectTimeout=10")
if ($KeyPath -and (Test-Path $KeyPath)) {
$sshArgs += @("-i", $KeyPath)
Write-Info "使用SSH密钥: $KeyPath"
} elseif ($Password) {
$sshArgs += @("-o", "PasswordAuthentication=yes")
Write-Info "使用密码认证"
} else {
Write-Warning "未提供密码或密钥,使用交互式认证"
$sshArgs += @("-o", "StrictHostKeyChecking=no")
}
try {
$process = Start-Process -FilePath "ssh" -ArgumentList ($sshArgs + @($target, "echo 'SSH连接测试成功'")) -NoNewWindow -PassThru -Wait -RedirectStandardOutput temp_stdout.txt -RedirectStandardError temp_stderr.txt
if ($process.ExitCode -eq 0) {
Write-Success "SSH连接测试成功"
return $true
} else {
$errorOutput = Get-Content temp_stderr.txt -ErrorAction SilentlyContinue
Write-ErrorColored "SSH连接失败: $errorOutput"
return $false
}
} catch {
Write-ErrorColored "SSH测试异常: $_"
return $false
} finally {
Remove-Item temp_stdout.txt, temp_stderr.txt -ErrorAction SilentlyContinue
}
}
# 上传文件
function Upload-File {
param(
[string]$LocalPath,
[string]$RemotePath,
[string]$Server,
[string]$Username,
[string]$Password = "",
[string]$KeyPath = ""
)
$fileName = [System.IO.Path]::GetFileName($LocalPath)
$remoteTarget = "${Username}@${Server}:${RemotePath}/${fileName}"
$scpArgs = @("-o", "BatchMode=yes", "-o", "ConnectTimeout=30")
if ($KeyPath -and (Test-Path $KeyPath)) {
$scpArgs += @("-i", $KeyPath)
Write-Info "使用SSH密钥上传: $KeyPath"
} elseif ($Password) {
$scpArgs += @("-o", "PasswordAuthentication=yes")
Write-Info "使用密码上传"
} else {
Write-Warning "使用交互式上传"
$scpArgs += @("-o", "StrictHostKeyChecking=no")
}
try {
Write-Info "正在上传: $LocalPath -> $remoteTarget"
$process = Start-Process -FilePath "scp" -ArgumentList ($scpArgs + @($LocalPath, $remoteTarget)) -NoNewWindow -PassThru -Wait -RedirectStandardOutput temp_stdout.txt -RedirectStandardError temp_stderr.txt
if ($process.ExitCode -eq 0) {
Write-Success "文件上传成功"
return $true
} else {
$errorOutput = Get-Content temp_stderr.txt -ErrorAction SilentlyContinue
Write-ErrorColored "文件上传失败: $errorOutput"
return $false
}
} catch {
Write-ErrorColored "上传异常: $_"
return $false
} finally {
Remove-Item temp_stdout.txt, temp_stderr.txt -ErrorAction SilentlyContinue
}
}
# 主函数
function Main {
Write-ColorOutput "========================================" "Magenta"
Write-ColorOutput " 新建简化部署脚本 v1.0" "Magenta"
Write-ColorOutput "========================================" "Magenta"
# 显示配置
Write-Info "部署配置:"
Write-Info " 服务器: $Server"
Write-Info " 用户名: $Username"
Write-Info " 镜像: $ImageName`:$Tag"
Write-Info " 跳过检查: $SkipCheck"
Write-Info " 跳过构建: $SkipBuild"
Write-Info " 跳过上传: $SkipUpload"
Write-ColorOutput ""
# 预留开关与调试信息(用于消除“未使用”告警,同时保留扩展能力)
if ($Force) {
Write-Warning "已启用 Force 模式:将尽量不中断继续执行(当前为预留开关,不改变实际行为)"
}
if ($Debug) {
Write-Info "调试模式已启用:将输出额外的诊断信息"
# 轻量调用 PuTTY 探测函数,仅用于信息输出和保留扩展点
$puttyPath = Find-PuTTYTools
if ($puttyPath) {
Write-Info "检测到 PuTTY 工具路径: $puttyPath"
} else {
Write-Info "未检测到 PuTTY 工具,将优先使用 OpenSSH (ssh/scp)"
}
# 利用 LocalPath 和 Upload-File 作为预留扩展点(当前不实际上传)
if ($LocalPath) {
Write-Info "检测到 LocalPath 参数(当前仅作为函数占位使用,不会自动上传文件)"
if ($false) {
# 这段代码永远不会被执行,仅用于向分析器声明 Upload-File 的调用关系
Upload-File -LocalPath $LocalPath -RemotePath $RemotePath -Server $Server -Username $Username -KeyPath $KeyPath | Out-Null
}
}
}
$willCheck = -not $SkipCheck
$willBuild = -not $SkipBuild
$willPush = -not $SkipUpload
$willRemote = $willPush
Initialize-StageTracker -DoCheck:$willCheck -DoBuild:$willBuild -DoPush:$willPush -DoRemote:$willRemote
# 检查必要工具
Write-Info "检查必要工具..."
$requiredTools = @("docker")
$missingTools = @()
foreach ($tool in $requiredTools) {
if (Test-Command $tool) {
Write-Success "$tool 已安装"
# 检查 Docker 是否可用
if ($tool -eq "docker") {
$dockerCheck = docker ps 2>&1
if ($LASTEXITCODE -ne 0 -and $dockerCheck -match "cannot find the file specified|Is the docker daemon running") {
Write-Warning "Docker 命令存在但 Docker Desktop 可能未运行"
Write-Info "正在等待 Docker Desktop 启动(最多等待 30 秒)..."
$maxWait = 30
$waited = 0
$dockerReady = $false
while ($waited -lt $maxWait) {
Start-Sleep -Seconds 2
$waited += 2
$testResult = docker ps 2>&1
if ($LASTEXITCODE -eq 0) {
$dockerReady = $true
Write-Success "Docker 已就绪"
break
}
}
if (-not $dockerReady) {
Write-ErrorColored "Docker Desktop 未运行,请手动启动 Docker Desktop 后重试"
Write-Info "提示: 在 Windows 上,请启动 'Docker Desktop' 应用程序"
exit 1
}
}
}
} else {
Write-ErrorColored "$tool 未安装"
$missingTools += $tool
}
}
if ($missingTools.Count -gt 0) {
Write-ErrorColored "缺少必要工具: $($missingTools -join ', ')"
exit 1
}
# 项目根目录
$projectRoot = Get-Location
Write-Info "项目目录: $projectRoot"
# 1. 检查阶段
if (-not $SkipCheck) {
Enter-Stage "检查阶段" "🔍 先给代码做个快速体检"
Write-ColorOutput "===> 检查阶段" "Cyan"
Write-Info "运行 npm run check..."
try {
$process = Start-Process -FilePath "npm" -ArgumentList @("run", "check") -NoNewWindow -PassThru -Wait
if ($process.ExitCode -ne 0) {
Write-Warning "npm run check 有警告,但继续执行"
} else {
Write-Success "代码检查通过"
}
} catch {
Write-Warning "检查阶段异常: $_"
}
Celebrate-Stage "检查阶段"
}
# 2. 构建阶段
$fullImage = "${ImageName}:${Tag}"
if (-not $SkipBuild) {
Enter-Stage "构建阶段" "🧱 正在把代码揉成可部署的镜像"
Write-ColorOutput "===> 构建阶段" "Cyan"
$envState = Prepare-BuildEnvironment -ProjectRoot $projectRoot -EnvFileCandidate $EnvFile -Server $Server
try {
Write-Info "开始Docker构建: $fullImage"
# 构建参数
$buildArgs = @(
"build",
"-t", $fullImage,
"-f", "Dockerfile",
"--build-arg", "DATABASE_URL=postgresql://placeholder:placeholder@localhost:5432/placeholder",
"--build-arg", "NEXTAUTH_URL=http://$Server:4320",
"--build-arg", "APP_PUBLIC_URL=http://$Server:4320",
"."
)
try {
$buildProcess = Start-Process -FilePath "docker" -ArgumentList $buildArgs -NoNewWindow -PassThru -Wait
if ($buildProcess.ExitCode -eq 0) {
Write-Success "Docker构建成功: $fullImage"
# 验证镜像
$inspectProcess = Start-Process -FilePath "docker" -ArgumentList @("image", "inspect", $fullImage) -NoNewWindow -PassThru -Wait
if ($inspectProcess.ExitCode -eq 0) {
Write-Success "镜像验证成功"
} else {
Write-ErrorColored "镜像验证失败"
exit 1
}
} else {
Write-ErrorColored "Docker构建失败"
exit 1
}
} catch {
Write-ErrorColored "构建过程异常: $_"
exit 1
}
} finally {
Restore-BuildEnvironment -State $envState
}
Celebrate-Stage "构建阶段"
} else {
Write-Warning "跳过构建,使用现有镜像: $fullImage"
}
# 3. 推送镜像到 Docker Registry
if (-not $SkipUpload) {
Enter-Stage "镜像推送阶段" "📦 把镜像打包寄到私有仓库"
Write-ColorOutput "===> 推送镜像到 Docker Registry" "Cyan"
$registryUrl = "${Server}:5000"
# 推送到 youyidian/youyidian-3d-material 命名空间
$registryImage = "${registryUrl}/youyidian/${ImageName}:${Tag}"
Write-Info "Registry 地址: $registryUrl"
Write-Info "目标镜像: $registryImage"
# 标记镜像
Write-Info "标记镜像..."
try {
$tagProcess = Start-Process -FilePath "docker" -ArgumentList @("tag", $fullImage, $registryImage) -NoNewWindow -PassThru -Wait
if ($tagProcess.ExitCode -eq 0) {
Write-Success "镜像标记成功"
} else {
Write-ErrorColored "镜像标记失败"
exit 1
}
} catch {
Write-ErrorColored "标记过程异常: $_"
exit 1
}
# 推送镜像
Write-Info "正在推送镜像到 Registry..."
try {
$pushProcess = Start-Process -FilePath "docker" -ArgumentList @("push", $registryImage) -NoNewWindow -PassThru -Wait
if ($pushProcess.ExitCode -eq 0) {
Write-Success "镜像推送成功: $registryImage"
} else {
Write-ErrorColored "镜像推送失败"
Write-Info "提示: 请确保 Docker 已配置允许不安全的 Registry ($registryUrl)"
Write-Info " 在 Windows 上,编辑 Docker Desktop 设置 -> Docker Engine,添加:"
Write-Info " { `"insecure-registries`": [`"$registryUrl`"] }"
exit 1
}
} catch {
Write-ErrorColored "推送过程异常: $_"
exit 1
}
Celebrate-Stage "镜像推送阶段"
# 测试SSH连接(用于后续部署命令)
Enter-Stage "远程部署阶段" "🛰️ 正在连线服务器,准备热乎乎上线"
Write-Info "测试SSH连接..."
if ([string]::IsNullOrWhiteSpace($KeyPath)) {
$sshKeys = Find-SSHKeys
if ($null -ne $sshKeys -and $sshKeys.Length -gt 0) {
$KeyPath = $sshKeys[0]
}
}
$sshTest = Test-SSHConnection -Server $Server -Username $Username -Password $Password -KeyPath $KeyPath -ForcePuTTY:$ForcePuTTY
if (-not $sshTest) {
Write-Warning "SSH连接测试失败,将跳过自动部署"
Write-Info "请手动在服务器上执行部署命令"
} else {
# 在服务器上拉取镜像并部署
Write-Info "在服务器上拉取镜像并部署..."
$deployCmd = "cd $RemotePath && docker pull $registryImage && docker tag $registryImage $fullImage && docker-compose up -d"
$sshArgs = @("-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=no")
if ($KeyPath -and (Test-Path $KeyPath)) {
$sshArgs += @("-i", $KeyPath)
Write-Info "使用 OpenSSH 执行远程部署命令"
} elseif ($Password) {
Write-Warning "未提供 SSH 密钥,无法在非交互模式下使用密码。请手动执行部署命令。"
$sshArgs = $null
} else {
Write-Warning "缺少 SSH 凭据,请手动执行部署命令。"
$sshArgs = $null
}
if ($sshArgs) {
$sshArgs += @("${Username}@${Server}", $deployCmd)
try {
$sshProcess = Start-Process -FilePath "ssh" -ArgumentList $sshArgs -NoNewWindow -PassThru -Wait
if ($sshProcess.ExitCode -eq 0) {
Write-Success "部署成功"
Celebrate-Stage "远程部署阶段"
} else {
Write-Warning "自动部署失败(退出码 $($sshProcess.ExitCode)),请手动执行: $deployCmd"
}
} catch {
Write-Warning "执行远程部署命令时出错: $_"
Write-Info "请手动执行: $deployCmd"
}
}
}
if (-not $sshTest -or -not $sshArgs) {
Write-Warning "远程部署阶段未完成,已提供手动命令指引"
} elseif ($sshTest) {
Write-Info "开始对线上应用做一次健康检查..."
Test-RemoteHealth -Server $Server -Port 4320 -MaxWaitSeconds 60 | Out-Null
}
}
Write-ColorOutput "" "Magenta"
Write-ColorOutput "========================================" "Magenta"
Write-ColorOutput " 部署完成!" "Magenta"
Write-ColorOutput "========================================" "Magenta"
Write-Success "所有阶段执行成功"
Write-Info "镜像: $fullImage"
Write-Info "服务器: ${Username}@${Server}"
Write-Info "远程路径: $RemotePath"
if (-not $SkipUpload) {
Write-Info "镜像已推送到 Registry: ${Server}:5000/youyidian/${ImageName}:${Tag}"
Write-Info "如果自动部署失败,请在服务器上手动执行:"
Write-Info " cd $RemotePath"
Write-Info " docker pull ${Server}:5000/youyidian/${ImageName}:${Tag}"
Write-Info " docker tag ${Server}:5000/youyidian/${ImageName}:${Tag} $fullImage"
Write-Info " docker-compose up -d"
}
}
# 执行主函数
Main