416 lines
14 KiB
PowerShell
416 lines
14 KiB
PowerShell
# ================================================================
|
||
# 新建简化部署脚本 - 处理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 |