445 lines
14 KiB
PowerShell
445 lines
14 KiB
PowerShell
# 统一部署脚本 - 解决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 "" |