本地构建 + 同步部署到服务器(Windows)
目标:构建在本地完成,服务器只接收静态文件——服务器不需要装 Node/pnpm,也不用跑构建,几乎零性能消耗。
整体流程:
本地 pnpm build → dist/ 打成一个 tar.gz → scp 上传到服务器 /tmp → ssh 解压覆盖站点目录
配套脚本在项目根目录:deploy-server.ps1,执行入口 pnpm deploy:server。
前置条件
- Windows 10+ 自带的 OpenSSH(
ssh、scp)与tar,无需额外安装; - 本机已装 pnpm(PowerShell 中脚本需调用
pnpm.cmd); - 服务器已配置 SSH 密钥登录。
一、配置 SSH 免密(一次性)
1. 生成密钥(已有可跳过)
ssh-keygen -t ed25519 -C "备注"
# 一路回车,生成 %USERPROFILE%\.ssh\id_ed25519(私钥)和 id_ed25519.pub(公钥)
2. 把公钥装到服务器
先查看公钥内容并复制:
Get-Content $env:USERPROFILE\.ssh\id_ed25519.pub
用密码登录服务器(仅首次):
ssh root@服务器IP
登录后在服务器上执行:
mkdir -p ~/.ssh && chmod 700 ~/.ssh
echo "公钥内容(id_ed25519.pub 的全部内容)" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
exit
3. 验证免密
ssh root@服务器IP echo ok # 不再提示密码即成功
二、脚本配置
deploy-server.ps1 顶部「配置区」5 项:
| 配置项 | 说明 | 示例 |
|---|---|---|
$ServerHost | 服务器 IP / 域名(也可填 ~/.ssh/config 的别名) | 1.2.3.4 |
$ServerUser | SSH 用户;用别名时可置空 '' | root |
$ServerPath | 站点目录(nginx root 指向的目录) | /var/www/note |
$SshKey | 私钥路径;默认密钥(id_ed25519)留空即可 | '' |
$SshPort | SSH 端口 | 22 |
三、执行部署
pnpm deploy:server # 构建 + 同步(共 5 步)
pnpm deploy:server -- -SkipBuild # 跳过构建,只重新同步
脚本内部流程:
pnpm.cmd run build生成dist/;tar -czf把dist/打成单个 tar.gz(只传一个文件,比逐个文件上传快很多);scp上传到服务器/tmp/;ssh远程执行:find清空站点目录 →tar -xzf解压 →chmod -R a+rX;- 清理本地临时压缩包。
常见问题
.ps1 中文乱码
Windows PowerShell 5.1 读取无 BOM 的 UTF-8 脚本会乱码,.ps1 必须保存为 UTF-8 with BOM,可这样转换:
$p = '.\deploy-server.ps1'
[IO.File]::WriteAllText((Resolve-Path $p), (Get-Content -Raw -Encoding UTF8 $p), (New-Object Text.UTF8Encoding $true))
首次连接与连通性测试
脚本已加 -o StrictHostKeyChecking=accept-new,首次连接自动记录服务器指纹。手动测试连通性:
# BatchMode=yes:需要密码时直接失败,而不是卡住等待输入
ssh -o BatchMode=yes -o ConnectTimeout=10 root@服务器IP echo ok
站点目录会被整体清空
脚本是「整体替换」:先 find "$ServerPath" -mindepth 1 -delete,再解压新文件,保证没有旧文件残留。所以该目录不要放其他需要保留的内容。
为什么不用 rsync
Windows 原生没有 rsync(需要 WSL / cwRsync)。站点文件量小(几百个文件压缩后几 MB 左右),整个 tar 包全量传输最简单可靠。
进阶(暂未实现)
- pre-push 钩子:
git push时自动构建并同步(写.git/hooks/pre-push),服务器依旧不用装环境; - 服务器 WebHook:代码平台推送后通知服务器,服务器
git pull后自行构建(需要服务器装 Node/pnpm,适合多设备发布场景)。
附:deploy-server.ps1 完整代码
已做脱敏处理:
$ServerHost等配置请替换为你自己的值。
<#
deploy-server.ps1 — 本地构建 + 一键同步到服务器(构建/压缩全在本地完成,服务器只负责解压)
用法:
1. 修改下面「配置区」:服务器地址、SSH 用户、站点目录、私钥路径(可选)
2. 直接运行: pnpm deploy:server
只想重新同步、不重新构建: pnpm deploy:server -- -SkipBuild
流程:
pnpm build → 把 dist/ 打成单个 tar.gz → scp 上传 → ssh 在服务器解压覆盖站点目录
前提:
- 本机已能免密 ssh 登录服务器(SSH 密钥登录)
- $ServerPath 与 nginx 的 root 配置一致
#>
param(
[switch]$SkipBuild
)
$ErrorActionPreference = 'Stop'
Set-Location $PSScriptRoot
# ---------- 配置区(按实际修改)----------
$ServerHost = '1.2.3.4' # 服务器 IP / 域名;也可填 ~/.ssh/config 里的 Host 别名
$ServerUser = 'root' # SSH 用户;若用 ssh 别名(别名里已含 User),可置空 ''
$ServerPath = '/var/www/note' # 站点目录(nginx root 指向的目录)
$SshKey = '' # 私钥文件路径;留空 = 使用默认密钥(~/.ssh/id_*)
$SshPort = 22 # SSH 端口
# ----------------------------------------
function Assert-Ok([string]$Step) {
if ($LASTEXITCODE -ne 0) { throw "[x] $Step 失败(exit code $LASTEXITCODE)" }
}
foreach ($cmd in 'ssh', 'scp', 'tar', 'pnpm.cmd') {
if (-not (Get-Command $cmd -ErrorAction SilentlyContinue)) {
throw "[x] 找不到命令 '$cmd':请确认已启用 Windows OpenSSH 客户端(ssh/scp)、并已安装 pnpm"
}
}
if ($ServerUser) { $target = "$ServerUser@$ServerHost" } else { $target = $ServerHost }
$tarName = 'record-dist.tar.gz'
$tarFile = Join-Path $env:TEMP $tarName
$opts = @('-o', 'StrictHostKeyChecking=accept-new')
if ($SshKey) { $opts += @('-i', $SshKey) }
# 1. 本地构建
if (-not $SkipBuild) {
Write-Host '==> [1/5] 本地构建(pnpm build)...' -ForegroundColor Cyan
pnpm.cmd run build
Assert-Ok '构建'
} else {
Write-Host '==> [1/5] 已跳过构建(-SkipBuild)' -ForegroundColor DarkGray
}
# 2. 打包 dist 为单个文件(比逐个文件上传快很多)
Write-Host '==> [2/5] 打包 dist ...' -ForegroundColor Cyan
Remove-Item $tarFile -Force -ErrorAction SilentlyContinue
tar -czf $tarFile -C dist .
Assert-Ok '打包'
# 3. 上传
Write-Host "==> [3/5] 上传到 $target ..." -ForegroundColor Cyan
scp @opts -P $SshPort $tarFile "${target}:/tmp/${tarName}"
Assert-Ok '上传'
# 4. 服务器端解压覆盖(先清空旧文件再解压,几乎不消耗服务器性能)
Write-Host "==> [4/5] 覆盖服务器目录 $ServerPath ..." -ForegroundColor Cyan
$remoteCmd = "set -e; mkdir -p '$ServerPath' && find '$ServerPath' -mindepth 1 -delete && tar -xzf /tmp/$tarName -C '$ServerPath' && rm -f /tmp/$tarName && chmod -R a+rX '$ServerPath'"
ssh @opts -p $SshPort $target $remoteCmd
Assert-Ok '服务器端解压'
# 5. 清理本地临时文件
Remove-Item $tarFile -Force -ErrorAction SilentlyContinue
Write-Host '==> [5/5] 部署完成' -ForegroundColor Green