Jw
首页
技术
英语
首页
技术
英语
  • 本站部署

    • 本地构建 + 同步到服务器
  • 知微(知识库 Agent)

    • 生产部署记录
    • HTTPS 证书基础
    • SSE 流式响应

本地构建 + 同步部署到服务器(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
$ServerUserSSH 用户;用别名时可置空 ''root
$ServerPath站点目录(nginx root 指向的目录)/var/www/note
$SshKey私钥路径;默认密钥(id_ed25519)留空即可''
$SshPortSSH 端口22

三、执行部署

pnpm deploy:server                 # 构建 + 同步(共 5 步)
pnpm deploy:server -- -SkipBuild   # 跳过构建,只重新同步

脚本内部流程:

  1. pnpm.cmd run build 生成 dist/;
  2. tar -czf 把 dist/ 打成单个 tar.gz(只传一个文件,比逐个文件上传快很多);
  3. scp 上传到服务器 /tmp/;
  4. ssh 远程执行:find 清空站点目录 → tar -xzf 解压 → chmod -R a+rX;
  5. 清理本地临时压缩包。

常见问题

.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
最近更新: 2026/10/1 09:50
Contributors: liujw