# ============================================================ # MyBlog 一键更新脚本(在 Windows 上运行) # 作用:打包本地代码 -> 上传到服务器 -> 服务器自动 备份/迁移/重启/验证 # 前置条件: # 1. 已配置 SSH 免密登录(首次需执行:ssh-keygen 后 ssh-copy-id 服务器) # 2. 服务器上已按 deploy/DEPLOY.md 完成首次部署(myblog 服务存在) # 用法(在项目根目录 D:\MyBlog 打开 PowerShell): # powershell -ExecutionPolicy Bypass -File .\deploy\update.ps1 # 或直接指定服务器:.\deploy\update.ps1 -Server root@8.145.36.108 # 常用参数: # -Server 服务器地址,默认读取 deploy/deploy.conf(缺省用 root@8.145.36.108) # -IncludeUploads 同时同步本地 uploads/(首次部署或新增静态资源时用) # 安全说明: # - 永远不打包 .env 与 backend/blog.db(密钥与数据库只在服务器上) # - 默认排除 uploads/:网页上传的文件只在服务器,本地打包不覆盖 # ============================================================ param( [string]$Server = "", [switch]$IncludeUploads ) $ErrorActionPreference = "Stop" $ScriptDir = $PSScriptRoot # deploy 目录 $ProjectRoot = Split-Path -Parent $ScriptDir # 项目根目录 $PackageName = "myblog.tar.gz" $RemoteScript = "update_server.sh" function Write-Step([string]$msg) { Write-Host ""; Write-Host "== $msg ==" -ForegroundColor Cyan } # ---------- 1. 确定服务器地址(命令行参数 > deploy.conf > 默认值) ---------- if (-not $Server -and (Test-Path (Join-Path $ScriptDir "deploy.conf"))) { $confLine = Get-Content (Join-Path $ScriptDir "deploy.conf") | Where-Object { $_ -match '^\s*SERVER\s*=' } | Select-Object -First 1 if ($confLine) { $Server = ($confLine -split '=', 2)[1].Trim() } $domainLine = Get-Content (Join-Path $ScriptDir "deploy.conf") | Where-Object { $_ -match '^\s*DOMAIN\s*=' } | Select-Object -First 1 if ($domainLine) { $Domain = ($domainLine -split '=', 2)[1].Trim() } } if (-not $Server) { $Server = "root@8.145.36.108" } Write-Host "目标服务器:$Server" if ($Domain) { Write-Host "验证域名:$Domain" } # ---------- 2. 检查依赖工具 ---------- foreach ($tool in @("tar", "scp", "ssh")) { if (-not (Get-Command $tool -ErrorAction SilentlyContinue)) { throw "缺少工具 $tool,请先安装 OpenSSH 客户端(Windows 设置 -> 可选功能)。" } } # ---------- 3. 打包本地代码(排除密钥/数据库/缓存/上传目录) ---------- Write-Step "打包本地代码" $tmpPkg = Join-Path $env:TEMP $PackageName if (Test-Path $tmpPkg) { Remove-Item -LiteralPath $tmpPkg -Force } Push-Location $ProjectRoot try { # 排除本地敏感运行数据:数据库、密钥配置、IPWatch 家庭端密钥与日志 $excludes = @("--exclude=backend/blog.db", "--exclude=backend/__pycache__", "--exclude=backend/routers/__pycache__", "--exclude=backend/services/__pycache__", "--exclude=venv", "--exclude=deploy/ipwatch/ipwatch.conf", "--exclude=deploy/ipwatch/lastip.txt", "--exclude=deploy/ipwatch/ipwatch.log") if (-not $IncludeUploads) { $excludes += "--exclude=uploads" } $targets = @("frontend", "backend", "deploy", "requirements.txt", ".env.example", "README.md") & tar -czf $tmpPkg @excludes @targets if ($LASTEXITCODE -ne 0) { throw "tar 打包失败" } } finally { Pop-Location } # 校验包内不含敏感文件(防止配置写错把密钥带上服务器) $bad = tar -tzf $tmpPkg | Select-String "(^|/)\.env$|blog\.db|__pycache__|^uploads/|ipwatch\.(conf|log)$|lastip\.txt" | Select-Object -First 5 if ($bad) { throw "更新包内含不应上传的文件:$($bad -join ', ')" } Write-Host ("更新包已生成:{0}({1:N0} KB)" -f $tmpPkg, ((Get-Item $tmpPkg).Length / 1KB)) # ---------- 4. 上传到服务器 ---------- Write-Step "上传更新包与服务器脚本" & scp -o BatchMode=yes -o StrictHostKeyChecking=accept-new $tmpPkg "$($Server):/tmp/$PackageName" if ($LASTEXITCODE -ne 0) { throw "上传失败:请确认已配置 SSH 免密登录" } & scp -o BatchMode=yes (Join-Path $ScriptDir $RemoteScript) "$($Server):/tmp/$RemoteScript" if ($LASTEXITCODE -ne 0) { throw "上传服务器脚本失败" } Write-Host "上传完成" # ---------- 5. 执行服务器端更新(备份/解压/迁移/重启/验证) ---------- Write-Step "执行服务器端更新(备份、迁移、重启、验证)" $domainArg = if ($Domain) { " '$Domain'" } else { "" } # 服务器端脚本若带 Windows 换行(CRLF)会导致 bash 解析失败,先统一转成 LF 再执行 & ssh -o BatchMode=yes $Server "sed -i 's/\r`$//' /tmp/$RemoteScript && bash /tmp/$RemoteScript$domainArg" if ($LASTEXITCODE -ne 0) { Write-Host "服务器端更新失败,请检查上方输出。可用备份回滚:/root/blog-backups/" -ForegroundColor Red exit 1 } Write-Host "" Write-Host "✅ 更新完成!请打开 https://$Domain 验证。" -ForegroundColor Green