Files

233 lines
8.6 KiB
Batchfile

# Piano Plan 部署脚本 - 钢琴练习方案系统
# 版本:v2.0 (安全版)
# 核心原则:备份优先、不删除、自动验证
#
# 使用前:
# 1. 修改下方 CONFIG 部分,填写正确的值
# 2. 确认本地代码已验证通过
# 3. 确认目标环境正确
# ============================================================
# 配置(请根据实际情况修改)
# ============================================================
$IMAGE_NAME = "piano-plan"
$IMAGE_TAG = "latest"
$CONTAINER_NAME = "piano-plan"
$REMOTE_HOST = "root@47.106.65.108"
$LOCAL_PATH = "D:\F\NewI\opencode\daily-workspace\projects\青年钢琴集体课\练习方案系统"
$REMOTE_IMAGE_PATH = "/tmp/piano-plan.tar"
# 必须保留的挂载(不要修改)
# ============================================================
$ErrorActionPreference = "Stop"
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host " Piano Plan 部署脚本 (安全版 v2.0)" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
# ----------------------------------------------------------
# 部署前确认(必须!)
# ----------------------------------------------------------
Write-Host "【重要】部署前必须获得用户明确同意" -ForegroundColor Yellow
Write-Host ""
$confirm = Read-Host "是否确认要执行部署?(输入 'YES' 继续)"
if ($confirm -ne "YES") {
Write-Host "部署已取消" -ForegroundColor Red
exit 0
}
Write-Host ""
# ----------------------------------------------------------
# 安全检查函数
# ----------------------------------------------------------
function Test-ContainerExists {
param([string]$Name)
$result = ssh -i ~/.ssh/id_rsa $REMOTE_HOST "docker ps -a --filter name=$Name --format '{{.Names}}'"
return ($result -eq $Name)
}
function Backup-File {
param([string]$Source, [string]$Dest)
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$backupPath = "$Dest backup_$timestamp"
Write-Host " [备份] $Source -> $backupPath" -ForegroundColor Yellow
ssh -i ~/.ssh/id_rsa $REMOTE_HOST "cp -r '$Source' '$backupPath'"
if ($LASTEXITCODE -eq 0) {
Write-Host " [OK] 备份成功" -ForegroundColor Green
} else {
Write-Host " [ERROR] 备份失败!" -ForegroundColor Red
throw "备份失败,终止部署"
}
}
function Get-CurrentMounts {
$mounts = ssh -i ~/.ssh/id_rsa $REMOTE_HOST "docker inspect $CONTAINER_NAME --format '{{json .HostConfig.Binds}}'" 2>$null
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrEmpty($mounts)) {
Write-Host " [WARNING] 无法获取当前挂载,将使用默认挂载配置" -ForegroundColor Yellow
return $null
}
return $mounts
}
function Write-Section {
param([string]$Title)
Write-Host ""
Write-Host "[$Title]" -ForegroundColor Cyan
}
function Write-Step {
param([int]$Num, [int]$Total, [string]$Msg)
Write-Host " Step $Num/$Total`: $Msg" -ForegroundColor White
}
function Test-ServiceHealth {
Write-Host " 检查服务健康状态..." -ForegroundColor Yellow
$status = ssh -i ~/.ssh/id_rsa $REMOTE_HOST "curl -s -o /dev/null -w '%{http_code}' http://localhost:5001/ 2>`$null"
if ($status -eq "302" -or $status -eq "200") {
Write-Host " [OK] 服务正常 (HTTP $status)" -ForegroundColor Green
return $true
} else {
Write-Host " [WARNING] 服务异常 (HTTP $status)" -ForegroundColor Red
return $false
}
}
function Test-DataIntegrity {
Write-Host " 检查数据完整性..." -ForegroundColor Yellow
$fileCount = ssh -i ~/.ssh/id_rsa $REMOTE_HOST "docker exec piano-plan ls -1 /app/个性化方案/*.md 2>/dev/null | wc -l"
if ($fileCount -ge 15) {
Write-Host " [OK] 问题文件完整 ($fileCount 个 md 文件)" -ForegroundColor Green
return $true
} else {
Write-Host " [ERROR] 问题文件不完整!只有 $fileCount 个" -ForegroundColor Red
return $false
}
}
# ============================================================
# 主流程
# ============================================================
# Step 1: 前置检查
Write-Section "1. 前置检查"
Write-Step 1 7 "确认容器存在"
if (-not (Test-ContainerExists $CONTAINER_NAME)) {
Write-Host " [ERROR] 容器 '$CONTAINER_NAME' 不存在!" -ForegroundColor Red
Write-Host " 请检查 CONTAINER_NAME 配置是否正确" -ForegroundColor Red
exit 1
}
Write-Host " [OK] 容器存在" -ForegroundColor Green
# Step 2: 获取当前挂载(关键!)
Write-Step 2 7 "获取当前挂载配置"
Write-Host " [INFO] 将保留原有挂载配置" -ForegroundColor Cyan
$currentMounts = Get-CurrentMounts
# Step 3: 构建镜像
Write-Section "2. 构建 Docker 镜像"
Set-Location $LOCAL_PATH
Write-Host " 正在构建镜像..." -ForegroundColor Yellow
docker build -t "$IMAGE_NAME`:$IMAGE_TAG" . 2>&1 | ForEach-Object { Write-Host " $_" }
if ($LASTEXITCODE -ne 0) {
Write-Host " [ERROR] Docker 构建失败!" -ForegroundColor Red
exit 1
}
Write-Host " [OK] 镜像构建成功" -ForegroundColor Green
# Step 4: 备份当前容器配置(挂载信息)
Write-Section "3. 备份"
Write-Step 3 7 "备份容器配置"
$mountBackupPath = "/tmp/piano_plan_mounts_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt"
ssh -i ~/.ssh/id_rsa $REMOTE_HOST "echo '$currentMounts' > '$mountBackupPath'"
Write-Host " [OK] 挂载配置已备份到 $mountBackupPath" -ForegroundColor Green
# Step 5: 保存并上传镜像
Write-Section "4. 上传镜像"
Write-Step 4 7 "保存镜像"
docker save "$IMAGE_NAME`:$IMAGE_TAG" -o "$LOCAL_PATH\$IMAGE_NAME.tar"
Write-Host " [OK] 镜像已保存" -ForegroundColor Green
Write-Step 5 7 "上传镜像到服务器"
scp -i ~/.ssh/id_rsa "$LOCAL_PATH\$IMAGE_NAME.tar" "$REMOTE_HOST`:$REMOTE_IMAGE_PATH"
Write-Host " [OK] 镜像已上传" -ForegroundColor Green
# Step 6: 停止旧容器(关键:不删除!)
Write-Section "5. 停止旧容器"
Write-Step 6 7 "停止容器 (NOT 删除)"
Write-Host " [WARNING] 即将停止容器,配置和挂载将保留" -ForegroundColor Yellow
ssh -i ~/.ssh/id_rsa $REMOTE_HOST "docker stop $CONTAINER_NAME"
Write-Host " [OK] 容器已停止" -ForegroundColor Green
# Step 7: 加载新镜像
Write-Section "6. 启动新容器"
Write-Step 7 7 "加载新镜像并启动"
# 构建挂载参数(使用原有挂载)
$mountArgs = "-v /opt/piano-plan/个性化方案:/app/个性化方案 -v piano-plan-data:/app/data -v piano-plan-output:/app/output -v piano-plan-config:/app/config"
$runCmd = @"
docker rm $CONTAINER_NAME 2>/dev/null
docker run -d --name $CONTAINER_NAME -p 5001:5001 --restart unless-stopped -e FLASK_ENV=production -e PYTHONDONTWRITEBYTECODE=1 $mountArgs $IMAGE_NAME`:$IMAGE_TAG
"@
Write-Host " [INFO] 启动命令: $runCmd" -ForegroundColor Cyan
ssh -i ~/.ssh/id_rsa $REMOTE_HOST $runCmd
Write-Host " [OK] 新容器已启动" -ForegroundColor Green
# 等待容器启动
Start-Sleep -Seconds 3
# ============================================================
# 部署后验证
# ============================================================
Write-Section "7. 部署后验证"
$allPassed = $true
Write-Host ""
Write-Host " 检查项:" -ForegroundColor White
# 检查1: 容器状态
Write-Host " [1/3] 容器状态" -ForegroundColor White
$containerStatus = ssh -i ~/.ssh/id_rsa $REMOTE_HOST "docker ps --filter name=$CONTAINER_NAME --format '{{.Status}}'"
if ($containerStatus -like "Up *") {
Write-Host " [OK] 容器运行中: $containerStatus" -ForegroundColor Green
} else {
Write-Host " [ERROR] 容器状态异常: $containerStatus" -ForegroundColor Red
$allPassed = $false
}
# 检查2: 服务健康
Write-Host " [2/3] 服务健康检查" -ForegroundColor White
if (-not (Test-ServiceHealth)) {
$allPassed = $false
}
# 检查3: 数据完整性
Write-Host " [3/3] 数据完整性检查" -ForegroundColor White
if (-not (Test-DataIntegrity)) {
$allPassed = $false
}
# ============================================================
# 完成
# ============================================================
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
if ($allPassed) {
Write-Host " 部署完成!" -ForegroundColor Green
} else {
Write-Host " 部署完成(但有警告,请检查!)" -ForegroundColor Yellow
}
Write-Host ""
Write-Host " 访问地址: https://piano.yoin.fun" -ForegroundColor Cyan
Write-Host " 备份位置: $mountBackupPath" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
# 清理本地临时文件
Remove-Item "$LOCAL_PATH\$IMAGE_NAME.tar" -ErrorAction SilentlyContinue