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

445 lines
14 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认证冲突
param(
[string]$Server = "45.249.208.118",
[string]$User = "root",
[string]$SshKeyPath = "$env:USERPROFILE\.ssh\id_rsa",
[string]$Password = "",
[string]$ProjectPath = "/root/youyidian-3d",
[switch]$UsePassword = $false,
[switch]$NoCache = $false,
[ValidateSet("ssh-key", "password", "auto")]
[string]$AuthMethod = "auto"
)
$ErrorActionPreference = "Stop"
Write-Host "========================================" -ForegroundColor Cyan
Write-Host " 统一部署脚本 - 解决SSH冲突" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
# 检查工具
$plinkPath = "C:\Program Files\PuTTY\plink.exe"
$pscpPath = "C:\Program Files\PuTTY\pscp.exe"
$toolsFound = $true
if (-not (Test-Path $plinkPath)) {
Write-Host "警告: 未找到 plink.exe" -ForegroundColor Yellow
$toolsFound = $false
}
if (-not (Test-Path $pscpPath)) {
Write-Host "警告: 未找到 pscp.exe" -ForegroundColor Yellow
$toolsFound = $false
}
if (-not $toolsFound) {
Write-Host "请安装 PuTTY 工具包" -ForegroundColor Red
Write-Host "下载地址: https://www.putty.org/" -ForegroundColor Red
exit 1
}
# 自动选择认证方式
$useSshKey = $false
$usePassword = $false
switch ($AuthMethod) {
"ssh-key" {
$useSshKey = $true
Write-Host "强制使用SSH密钥认证" -ForegroundColor Yellow
}
"password" {
$usePassword = $true
Write-Host "强制使用密码认证" -ForegroundColor Yellow
}
"auto" {
# 自动检测最佳认证方式
if ((-not [string]::IsNullOrEmpty($SshKeyPath)) -and (Test-Path $SshKeyPath)) {
$useSshKey = $true
Write-Host "检测到SSH密钥文件,使用密钥认证" -ForegroundColor Green
} elseif ($UsePassword -or (-not [string]::IsNullOrEmpty($Password))) {
$usePassword = $true
Write-Host "使用密码认证" -ForegroundColor Green
} else {
Write-Host "错误: 未找到SSH密钥且未提供密码" -ForegroundColor Red
Write-Host "请使用 -SshKeyPath 指定密钥文件路径" -ForegroundColor Yellow
Write-Host "或使用 -Password 提供密码" -ForegroundColor Yellow
Write-Host "或使用 -UsePassword 参数启用密码认证" -ForegroundColor Yellow
exit 1
}
}
}
# 验证SSH密钥
if ($useSshKey) {
if (-not (Test-Path $SshKeyPath)) {
Write-Host "错误: SSH密钥文件不存在: $SshKeyPath" -ForegroundColor Red
Write-Host ""
Write-Host "创建SSH密钥的步骤:" -ForegroundColor Yellow
Write-Host "1. 在PowerShell中运行: ssh-keygen -t rsa -b 4096 -C 'deploy@youyidian'" -ForegroundColor Gray
Write-Host "2. 将生成的公钥复制到服务器: ssh-copy-id -i ~/.ssh/id_rsa.pub ${User}@${Server}" -ForegroundColor Gray
Write-Host "3. 或手动复制公钥内容到服务器的 ~/.ssh/authorized_keys" -ForegroundColor Gray
exit 1
}
Write-Host "使用SSH密钥: $SshKeyPath" -ForegroundColor Green
}
# 验证密码
if ($usePassword -and [string]::IsNullOrEmpty($Password)) {
Write-Host "错误: 使用密码认证需要提供 -Password 参数" -ForegroundColor Red
exit 1
}
Write-Host "服务器: $Server" -ForegroundColor Green
Write-Host "用户: $User" -ForegroundColor Green
Write-Host "目标路径: $ProjectPath" -ForegroundColor Green
Write-Host ""
# 需要上传的关键文件列表(完整列表)
$filesToUpload = @(
# 核心配置文件
"package.json",
"package-lock.json",
"next.config.js",
".env",
"prisma/schema.prisma",
"Dockerfile",
"docker-compose.yml",
# 应用核心文件
"app/layout.tsx",
"app/page.tsx",
"app/login/page.tsx",
"app/register/page.tsx",
"app/api/auth/register/route.ts",
"app/api/auth/login/route.ts",
"app/api/auth/logout/route.ts",
"app/api/user/me/route.ts",
"app/api/admin/users/route.ts",
"app/api/admin/dashboard/route.ts",
# 库文件
"lib/prisma.ts",
"lib/auth.ts",
"lib/config.ts",
"lib/configLoader.ts",
"lib/utils.ts",
"middleware.ts",
# 组件和样式
"components/ui",
"components/auth",
"components/admin",
"public",
"styles",
# 其他重要文件
"README.md",
"tsconfig.json",
"tailwind.config.ts"
)
Write-Host "[1/6] 检查文件完整性..." -ForegroundColor Yellow
$missingFiles = @()
foreach ($file in $filesToUpload) {
if (-not (Test-Path $file)) {
$missingFiles += $file
}
}
if ($missingFiles.Count -gt 0) {
Write-Host "以下文件不存在,将跳过:" -ForegroundColor Yellow
$missingFiles | ForEach-Object { Write-Host " - $_" -ForegroundColor Yellow }
Write-Host ""
}
# 测试SSH连接
Write-Host "[2/6] 测试SSH连接..." -ForegroundColor Yellow
$testCmd = "echo 'SSH连接测试成功'"
if ($useSshKey) {
& $plinkPath -ssh -i $SshKeyPath -batch "${User}@${Server}" $testCmd | Out-Null
} else {
if ([string]::IsNullOrEmpty($Password)) {
Write-Host "请输入服务器密码:" -ForegroundColor Yellow -NoNewline
$securePwd = Read-Host -AsSecureString
$passwordPtr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePwd)
$Password = [Runtime.InteropServices.Marshal]::PtrToStringAuto($passwordPtr)
}
& $plinkPath -ssh "${User}@${Server}" $testCmd | Out-Null
}
if ($LASTEXITCODE -ne 0) {
Write-Host "SSH连接测试失败" -ForegroundColor Red
exit 1
}
Write-Host " ✓ SSH连接正常" -ForegroundColor Green
# 确保远程目录存在
Write-Host "[3/6] 准备远程目录..." -ForegroundColor Yellow
$ensureDirCmd = "mkdir -p '$ProjectPath'"
if ($useSshKey) {
& $plinkPath -ssh -i $SshKeyPath -batch "${User}@${Server}" $ensureDirCmd | Out-Null
} else {
& $plinkPath -ssh -pw $Password "${User}@${Server}" $ensureDirCmd | Out-Null
}
if ($LASTEXITCODE -ne 0) {
Write-Host "创建远程目录失败" -ForegroundColor Red
exit 1
}
# 上传文件
Write-Host "[4/6] 上传文件到服务器..." -ForegroundColor Yellow
$uploadedCount = 0
$totalFiles = ($filesToUpload | Where-Object { Test-Path $_ }).Count
foreach ($file in $filesToUpload) {
if (Test-Path $file) {
$remotePath = "$ProjectPath/$file"
$remoteDir = Split-Path $remotePath -Parent
# 确保远程目录存在
$mkdirCmd = "mkdir -p '$remoteDir'"
if ($useSshKey) {
& $plinkPath -ssh -i $SshKeyPath -batch "${User}@${Server}" $mkdirCmd | Out-Null
} else {
& $plinkPath -ssh -pw $Password "${User}@${Server}" $mkdirCmd | Out-Null
}
# 上传文件
$uploadParams = @("-q")
if ($useSshKey) {
$uploadParams += "-i", $SshKeyPath, "-batch"
} else {
$uploadParams += "-pw", $Password
}
$uploadParams += $file, "${User}@${Server}:$remotePath"
& $pscpPath @uploadParams 2>&1 | Out-Null
if ($LASTEXITCODE -eq 0) {
$uploadedCount++
Write-Host " ✓ $file" -ForegroundColor Green
} else {
Write-Host " ✗ $file (上传失败)" -ForegroundColor Red
}
}
}
Write-Host "文件上传完成: $uploadedCount/$totalFiles 个文件" -ForegroundColor $(if ($uploadedCount -eq $totalFiles) { "Green" } else { "Yellow" })
if ($uploadedCount -lt $totalFiles) {
Write-Host "警告: 部分文件上传失败,可能影响部署" -ForegroundColor Yellow
}
# 创建部署脚本
Write-Host "[5/6] 创建并执行远程部署脚本..." -ForegroundColor Yellow
$buildFlag = if ($NoCache) { "--no-cache" } else { "" }
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
# 创建本地临时脚本
$localDeployScript = Join-Path $env:TEMP "deploy-unified-$timestamp.sh"
$utf8NoBom = New-Object System.Text.UTF8Encoding $false
# 远程部署脚本内容
$deployScript = @"
#!/bin/bash
set -e
echo "========================================"
echo "开始部署应用..."
echo "开始时间: $(date)"
echo "========================================"
cd $ProjectPath
# 备份重要数据
echo "[1/7] 备份数据..."
if [ -d "uploads" ] && [ -n "$(ls -A uploads 2>/dev/null)" ]; then
mkdir -p backups
backup_dir="backups/uploads-$(date +%Y%m%d-%H%M%S)"
cp -r uploads "`$backup_dir"
echo " ✓ 上传目录已备份到 `$(basename `$backup_dir)"
fi
# 检查Docker环境
echo "[2/7] 检查Docker环境..."
if ! command -v docker &> /dev/null; then
echo "错误: Docker 未安装" >&2
exit 1
fi
if docker compose version &> /dev/null; then
DOCKER_COMPOSE="docker compose"
elif command -v docker-compose &> /dev/null; then
DOCKER_COMPOSE="docker-compose"
else
echo "错误: Docker Compose 未安装" >&2
exit 1
fi
echo " ✓ 使用Docker Compose: `$DOCKER_COMPOSE"
# 停止旧容器
echo "[3/7] 停止旧容器..."
`$DOCKER_COMPOSE down --timeout 30 2>/dev/null || true
echo " ✓ 旧容器已停止"
# 构建并启动应用
echo "[4/7] 构建并启动应用..."
if [ -n "$buildFlag" ]; then
echo " ⚠ 使用 --no-cache 模式重新构建"
`$DOCKER_COMPOSE build $buildFlag app
fi
echo " ✓ 启动应用容器..."
`$DOCKER_COMPOSE up -d app
if [ `$? -ne 0 ]; then
echo "错误: 容器启动失败" >&2
exit 1
fi
# 等待容器就绪
echo "[5/7] 等待容器就绪..."
for i in {1..30}; do
if `$DOCKER_COMPOSE ps app | grep -q "Up"; then
echo " ✓ 容器运行正常"
break
fi
if [ $i -eq 30 ]; then
echo "错误: 容器启动超时" >&2
exit 1
fi
sleep 2
done
# 数据库迁移
echo "[6/7] 检查数据库状态..."
if [ -f prisma/schema.prisma ]; then
echo " → 执行数据库迁移..."
`$DOCKER_COMPOSE exec -T app npx prisma db push --accept-data-loss --schema=/app/prisma/schema.prisma 2>/dev/null || {
echo " ⚠ 数据库迁移跳过或已存在"
}
fi
# 健康检查
echo "[7/7] 执行健康检查..."
sleep 5
max_attempts=10
attempt=0
while [ `$attempt -lt `$max_attempts ]; do
if curl -f -s http://localhost:3000/api/health > /dev/null 2>&1; then
echo " ✓ 应用健康检查通过"
break
fi
attempt=`$((attempt + 1))
if [ $attempt -eq $max_attempts ]; then
echo " ⚠ 健康检查超时,应用可能需要更多时间启动"
fi
sleep 3
done
# 显示部署结果
echo ""
echo "========================================"
echo "部署完成!"
echo "完成时间: $(date)"
echo "========================================"
echo ""
echo "容器状态:"
`$DOCKER_COMPOSE ps
echo ""
echo "最近日志:"
`$DOCKER_COMPOSE logs app --tail=10
# 显示应用访问信息
echo ""
echo "应用访问信息:"
echo " 本地访问: http://localhost:3000"
echo " 外网访问: http://$Server:4320"
echo ""
"@
[System.IO.File]::WriteAllText($localDeployScript, $deployScript, $utf8NoBom)
# 上传并执行部署脚本
$remoteDeployScript = "/tmp/deploy-unified-$timestamp.sh"
$uploadParams = @("-q")
if ($useSshKey) {
$uploadParams += "-i", $SshKeyPath, "-batch"
} else {
$uploadParams += "-pw", $Password
}
$uploadParams += $localDeployScript, "${User}@${Server}:$remoteDeployScript"
Write-Host " → 上传部署脚本..." -ForegroundColor Yellow
& $pscpPath @uploadParams
if ($LASTEXITCODE -ne 0) {
Write-Host "上传部署脚本失败" -ForegroundColor Red
Remove-Item $localDeployScript -Force -ErrorAction SilentlyContinue
exit 1
}
Write-Host " → 执行远程部署..." -ForegroundColor Yellow
if ($useSshKey) {
& $plinkPath -ssh -i $SshKeyPath -batch "${User}@${Server}" "chmod +x $remoteDeployScript && bash $remoteDeployScript && rm -f $remoteDeployScript"
} else {
& $plinkPath -ssh -pw $Password "${User}@${Server}" "chmod +x $remoteDeployScript && bash $remoteDeployScript && rm -f $remoteDeployScript"
}
$deployExitCode = $LASTEXITCODE
# 清理临时文件
Remove-Item $localDeployScript -Force -ErrorAction SilentlyContinue
# 显示部署结果
Write-Host "[6/6] 部署结果..." -ForegroundColor Yellow
if ($deployExitCode -eq 0) {
Write-Host ""
Write-Host "========================================" -ForegroundColor Green
Write-Host " 部署成功完成!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host ""
Write-Host "🎉 应用已成功部署到服务器" -ForegroundColor Green
Write-Host ""
Write-Host "📍 访问地址:" -ForegroundColor Cyan
Write-Host " 本地: http://localhost:3000" -ForegroundColor White
Write-Host " 外网: http://$Server:4320" -ForegroundColor White
Write-Host ""
Write-Host "🔧 使用方法:" -ForegroundColor Yellow
if ($useSshKey) {
Write-Host " - 使用SSH密钥认证 (推荐)" -ForegroundColor Green
} else {
Write-Host " - 使用密码认证" -ForegroundColor Yellow
Write-Host " 建议配置SSH密钥以提高安全性" -ForegroundColor Gray
}
Write-Host ""
} else {
Write-Host ""
Write-Host "========================================" -ForegroundColor Red
Write-Host " 部署失败!" -ForegroundColor Red
Write-Host " 退出码: $deployExitCode" -ForegroundColor Red
Write-Host "========================================" -ForegroundColor Red
Write-Host ""
Write-Host "🔧 可能的解决方案:" -ForegroundColor Yellow
Write-Host " 1. 检查网络连接" -ForegroundColor White
Write-Host " 2. 验证服务器凭证" -ForegroundColor White
Write-Host " 3. 查看服务器日志: docker-compose logs app" -ForegroundColor White
Write-Host " 4. 重新运行脚本: .\$($MyInvocation.MyCommand.Name) -AuthMethod auto" -ForegroundColor White
Write-Host ""
}
Write-Host "💡 提示:" -ForegroundColor Cyan
Write-Host " - 使用 -NoCache 参数强制重新构建" -ForegroundColor Gray
Write-Host " - 使用 -AuthMethod ssh-key 强制使用密钥认证" -ForegroundColor Gray
Write-Host " - 使用 -AuthMethod password 强制使用密码认证" -ForegroundColor Gray
Write-Host " - 使用 -SshKeyPath 指定自定义密钥路径" -ForegroundColor Gray
Write-Host ""