# ================================================================ # 新建简化部署脚本 - 处理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 = "/opt/deploy", [switch]$SkipCheck, [switch]$SkipBuild, [switch]$SkipUpload, [switch]$Force, [switch]$Debug, [switch]$Help ) # 脚本配置 Set-StrictMode -Version Latest $ErrorActionPreference = "Continue" # 颜色输出函数 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 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\youyidian_prod_ed25519", "$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 } # 测试SSH连接 function Test-SSHConnection { param( [string]$Server, [string]$Username, [string]$Password = "", [string]$KeyPath = "" ) $sshArgs = @("-o", "BatchMode=yes", "-o", "ConnectTimeout=10") $target = "${Username}@${Server}" 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 = "" ) $remoteTarget = "${Username}@${Server}:${RemotePath}/" $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 "" # 检查必要工具 Write-Info "检查必要工具..." $requiredTools = @("docker") $missingTools = @() foreach ($tool in $requiredTools) { if (Test-Command $tool) { Write-Success "$tool 已安装" } 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) { 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 "检查阶段异常: $_" } } # 2. 构建阶段 $fullImage = "${ImageName}:${Tag}" if (-not $SkipBuild) { Write-ColorOutput "===> 构建阶段" "Cyan" 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 } } else { Write-Warning "跳过构建,使用现有镜像: $fullImage" } # 3. 打包阶段 if (-not $SkipUpload) { Write-ColorOutput "===> 打包阶段" "Cyan" $archiveDir = Join-Path $projectRoot "deploy-artifacts" if (-not (Test-Path $archiveDir)) { New-Item -ItemType Directory -Path $archiveDir | Out-Null Write-Success "创建打包目录: $archiveDir" } $timestamp = Get-Date -Format "yyyyMMddHHmmss" $safeName = $ImageName -replace '[\\/]', '_' $archivePath = Join-Path $archiveDir "$safeName-$Tag-$timestamp.tar" Write-Info "正在打包镜像..." Write-Info "目标文件: $archivePath" try { $saveProcess = Start-Process -FilePath "docker" -ArgumentList @("save", "-o", $archivePath, $fullImage) -NoNewWindow -PassThru -Wait if ($saveProcess.ExitCode -eq 0) { if (Test-Path $archivePath) { $fileSize = (Get-Item $archivePath).Length Write-Success "镜像打包完成: $(Format-FileSize $fileSize)" $LocalPath = $archivePath } else { Write-ErrorColored "打包文件未生成" exit 1 } } else { Write-ErrorColored "镜像打包失败" exit 1 } } catch { Write-ErrorColored "打包过程异常: $_" exit 1 } } # 4. 上传阶段 if (-not $SkipUpload) { Write-ColorOutput "===> 上传阶段" "Cyan" # 寻找SSH密钥 if ([string]::IsNullOrWhiteSpace($KeyPath)) { Write-Info "查找SSH密钥..." $sshKeys = Find-SSHKeys if ($sshKeys.Count -gt 0) { $KeyPath = $sshKeys[0] Write-Success "找到SSH密钥: $KeyPath" } else { Write-Warning "未找到SSH密钥文件" } } # 测试SSH连接 Write-Info "测试SSH连接..." $sshTest = Test-SSHConnection -Server $Server -Username $Username -Password $Password -KeyPath $KeyPath if (-not $sshTest) { Write-ErrorColored "SSH连接测试失败,请检查配置" Write-Info "提示: 可以使用 -KeyPath 指定密钥文件或 -Password 指定密码" exit 1 } # 上传文件 if (-not [string]::IsNullOrWhiteSpace($LocalPath) -and (Test-Path $LocalPath)) { Write-Info "开始上传文件..." $uploadSuccess = Upload-File -LocalPath $LocalPath -RemotePath $RemotePath -Server $Server -Username $Username -Password $Password -KeyPath $KeyPath if ($uploadSuccess) { Write-Success "文件上传成功" } else { Write-ErrorColored "文件上传失败" exit 1 } } else { Write-ErrorColored "未找到要上传的文件: $LocalPath" exit 1 } } 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 "下一步请在服务器上运行:" Write-Info " cd $RemotePath" Write-Info " docker load < $([System.IO.Path]::GetFileName($LocalPath))" Write-Info " docker-compose up -d" } } # 检查是否需要显示帮助 if ($Help -or $args.Count -eq 0) { Write-ColorOutput "新建简化部署脚本" "Green" Write-ColorOutput "========================================" "Green" Write-ColorOutput "用法:" "White" Write-ColorOutput " .\deploy-new.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 " -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.ps1" "Yellow" Write-ColorOutput " .\deploy-new.ps1 -Password 'your-password'" "Yellow" Write-ColorOutput " .\deploy-new.ps1 -KeyPath 'C:\Users\.ssh\id_rsa'" "Yellow" Write-ColorOutput " .\deploy-new.ps1 -SkipCheck -SkipUpload" "Yellow" exit 0 } # 执行主函数 Main