199 lines
8.8 KiB
Markdown
199 lines
8.8 KiB
Markdown
# 一键部署脚本
|
||
|
||
在本机运行一条命令,自动完成「本地构建(可选)→ 打包 → 上传服务器 → 重启进程」。
|
||
|
||
服务器上**不需要安装任何东西**,它复用的就是你已经能用的 SSH。
|
||
|
||
## 用哪个脚本
|
||
|
||
| 你的系统 | 脚本 | 配置文件 |
|
||
|---|---|---|
|
||
| Windows | `deploy.ps1` | `deploy.config.ps1`(照抄 `deploy.config.example.ps1`) |
|
||
| macOS / Linux | `deploy.sh` | `deploy.config.sh`(照抄 `deploy.config.example.sh`) |
|
||
|
||
Mac 上先执行一次 `chmod +x deploy/deploy.sh`,之后用法和 Windows 版完全一样:
|
||
|
||
```bash
|
||
./deploy/deploy.sh api
|
||
./deploy/deploy.sh admin
|
||
./deploy/deploy.sh
|
||
./deploy/deploy.sh api --dry-run # 只打包、只显示会上传哪些文件,不真传
|
||
./deploy/deploy.sh api --with-install # 顺带装依赖(只有改了 package.json 才需要)
|
||
```
|
||
|
||
> 第一次用先跑 `--dry-run`,它会打印打包大小和文件清单,核对无误再去掉这个参数。
|
||
|
||
---
|
||
|
||
## 一次性配置
|
||
|
||
### 1. 填配置文件
|
||
|
||
编辑 `deploy/deploy.config.ps1`(Mac 上是 `deploy/deploy.config.sh`,字段名多一层下划线,含义完全一样),需要改这几项:
|
||
|
||
| 配置项 | 填什么 | 怎么查 |
|
||
|---|---|---|
|
||
| `Host` | 服务器 IP 或域名 | — |
|
||
| `Port` | SSH 端口 | 宝塔 → 安全 → SSH 端口 |
|
||
| `User` | SSH 登录用户 | 一般是 `root` |
|
||
| `NodeBin` | Node 的 bin 目录 | 宝塔 → 软件商店 → Node 版本管理器 |
|
||
| `Api.RemotePath` | 服务端在服务器上的绝对路径 | 你部署时放的位置 |
|
||
| `Admin.RemotePath` | 管理端在服务器上的绝对路径 | 同上 |
|
||
| `Api.RestartCmd` | 重启服务端的命令 | 服务器上执行 `pm2 list` 看进程名 |
|
||
| `Admin.RestartCmd` | 重启管理端的命令 | 同上 |
|
||
| `Api.StartCmd` / `Admin.StartCmd` | 杀掉旧进程后把服务拉起来的命令 | 照抄模板;项目路径或端口变了才需要改 |
|
||
| `Api.HealthUrl` / `Admin.HealthUrl` | 启动后的探活地址 | 照抄模板;端口变了才需要改 |
|
||
| `NginxCacheDir` | Nginx 反向代理缓存目录 | 宝塔 → 软件商店 → Nginx → 配置修改,搜 `proxy_cache_path` |
|
||
| `NginxBin` | Nginx 可执行文件路径 | 一般是 `/www/server/nginx/sbin/nginx` |
|
||
|
||
### 2. 配置免密登录(强烈建议)
|
||
|
||
不配的话,每次部署要输两次密码。配好之后全程无提示。
|
||
|
||
```powershell
|
||
# 本机生成密钥(已生成过可跳过,直接第二次命令)
|
||
ssh-keygen -t ed25519
|
||
|
||
# 把公钥传到服务器
|
||
type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh root@你的服务器 "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
|
||
```
|
||
|
||
### 3. 确认服务器上的 .env 已就位
|
||
|
||
脚本**不会上传** `.env`(这是故意的,避免覆盖服务器上的密钥配置)。
|
||
|
||
- 服务端:`yelaymaths_api/.env` 必须已在服务器上配好
|
||
- 管理端:`yelaymaths_admin/.env.local` 必须已在服务器上配好
|
||
- 注意 `NEXT_PUBLIC_*` 是**构建时**写死的,所以第一次构建前它就得在
|
||
|
||
---
|
||
|
||
## 日常使用
|
||
|
||
```powershell
|
||
.\deploy\deploy.ps1 api # 只部署服务端
|
||
.\deploy\deploy.ps1 admin # 只部署管理端
|
||
.\deploy\deploy.ps1 # 两个都部署
|
||
```
|
||
|
||
输出长这样:
|
||
|
||
```
|
||
=== yelaymaths_api (server API) ===
|
||
[1/5] Building locally: pnpm prisma:generate && pnpm build
|
||
[2/5] Packing yelaymaths_api
|
||
160 KB (dist, prisma, package.json, pnpm-lock.yaml, pnpm-workspace.yaml)
|
||
[3/5] Uploading to /www/wwwroot/yelaymaths/yelaymaths_api/api
|
||
[4/5] Running on server
|
||
__DEPLOY_DONE__
|
||
[5/5] Restarted
|
||
Started, health check passed: http://127.0.0.1:3002/healthz
|
||
Nginx proxy cache cleared and reloaded
|
||
|
||
All done in 12.0s
|
||
```
|
||
|
||
### 其它参数
|
||
|
||
| 参数 | 作用 |
|
||
|---|---|
|
||
| `-DryRun` | 只打包、只显示会上传哪些文件,**不真的传**。第一次用建议先跑这个 |
|
||
| `-WithInstall` | 额外安装依赖。**只在改了 `package.json` 时才需要** |
|
||
|
||
---
|
||
|
||
## 为什么默认不装依赖
|
||
|
||
服务器上的全局 pnpm 是 **8.x**,而本机是 10.x,两代锁文件格式不兼容——服务器直接读不了我们的 `pnpm-lock.yaml`。
|
||
|
||
而服务器上**不能升级 pnpm**(其他服务也在用)。
|
||
|
||
好在只有「装依赖」这一步需要版本匹配,**「构建」不需要**——`pnpm build` 只是执行 `package.json` 里的脚本,pnpm 8 也能跑。
|
||
|
||
所以日常部署走「上传源码 + 构建」,用服务器上现成的 `node_modules`,既不碰全局 pnpm,也快得多。
|
||
|
||
只有 `package.json` 变了才需要装依赖,这时用 `-WithInstall`,它会通过 `npx` 临时拉一个 10.27.0 的 pnpm 来跑,同样不影响全局。
|
||
|
||
---
|
||
|
||
## 上传哪些文件
|
||
|
||
脚本用的是**白名单**,只有列在 `DeployPaths` 里的路径会被打包。所以这些东西**结构上不可能**被传上去:
|
||
|
||
- `.env` / `.env.local`(服务器上的配置不会被覆盖)
|
||
- `node_modules/`
|
||
- 白名单之外的源码目录
|
||
|
||
Windows 版配置(`deploy.config.ps1`)已经在**本机构建、只传产物**:服务端传 `dist` + `prisma`(约 160 KB),
|
||
管理端传 `.next` + `next.config.ts`(约 540 KB,73 MB 的构建缓存已排除),服务器不再执行编译,部署完自动重启并探活。
|
||
|
||
---
|
||
|
||
## 已知限制
|
||
|
||
**删除的文件不会同步删除。** 脚本是「覆盖式上传」,如果你在本地删掉某个源码文件,服务器上那份还在。
|
||
|
||
一般不影响运行(多余的文件不会被执行),但如果你发现改了名字的旧文件仍在作怪,手动上服务器删掉即可。
|
||
|
||
需要「完全同步」的场景(比如删文件、想要版本回滚),改用 git 推送部署更合适——那是另一个方案,需要时再说。
|
||
|
||
---
|
||
|
||
## 部署结束后会清一次 Nginx 缓存
|
||
|
||
宝塔在 `/www/server/nginx/conf/proxy.conf` 里对**所有**反向代理站点开了 `proxy_cache cache_one`,而 Next.js 给页面发的响应头是 `Cache-Control: s-maxage=31536000`——翻译过来就是「这一页允许缓存一年」。
|
||
|
||
两边一叠加,后果很隐蔽:新构建传上去、进程也重启了,用户访问到的还是缓存里的旧 HTML;而旧 HTML 引用的分包早被新构建删掉,请求直接 404,页面渲染到一半就断,表现成「某个区块整块消失」,看起来像代码写错了,实际跟代码毫无关系。
|
||
|
||
所以脚本在每次部署的最后会多做一步:
|
||
|
||
```bash
|
||
find /www/server/nginx/proxy_cache_dir -mindepth 1 -delete # 清空缓存文件
|
||
/www/server/nginx/sbin/nginx -s reload # 重载 Nginx
|
||
```
|
||
|
||
服务端和管理端两条部署线都带这一步。缓存只影响响应速度,清空后第一次访问会回源到新构建,不会丢数据。
|
||
|
||
把 `NginxCacheDir` 留空(`''`)就会跳过这一步。
|
||
|
||
---
|
||
|
||
## 部署结束会把服务自动拉起来
|
||
|
||
杀掉旧进程不等于新进程会跑起来。之前这里是「只杀不拉」,所以每次部署完都要去宝塔面板点一下「启动」,忘了点线上就是 502。
|
||
|
||
现在每个应用多两项配置:
|
||
|
||
- `RestartCmd`:杀掉旧进程
|
||
- `StartCmd`:把新进程拉起来(`setsid` + `nohup` + 重定向,SSH 会话断了也不受影响)
|
||
- `HealthUrl`:拉起后探活,30 秒内没响应就让部署直接判失败
|
||
|
||
所以正常输出结尾会是:
|
||
|
||
```
|
||
[5/5] Restarted
|
||
Started, health check passed: http://127.0.0.1:3001/
|
||
Nginx proxy cache cleared and reloaded
|
||
```
|
||
|
||
进程日志写在服务器上:`/www/wwwlogs/yelaymaths-api.out.log`、`/www/wwwlogs/yelaymaths-admin.out.log`。
|
||
|
||
有一点要知道:进程是脚本拉起来的,不是宝塔面板拉起来的,所以**面板里那个项目的状态可能显示成「未启动」**,但服务本身是正常在跑的(脚本每次都会探活确认)。如果想让面板重新接管,在面板点一次「重启」即可,之后照常用面板管。
|
||
|
||
只有 `RestartCmd` 不想自己启动(比如服务由 PM2 或 systemd 托管),把 `StartCmd` 留空(`''`)就会退回「只杀不拉」的老行为。
|
||
|
||
---
|
||
|
||
## 出问题怎么排查
|
||
|
||
| 现象 | 原因 | 处理 |
|
||
|---|---|---|
|
||
| `deploy.config.ps1 not found` | 没建配置文件 | 复制 `deploy.config.example.ps1` |
|
||
| 一直提示输密码 | 没配免密登录 | 见上面第 2 步 |
|
||
| `pnpm: command not found` | `NodeBin` 填错 | 在服务器上执行 `which pnpm` 看真实路径 |
|
||
| `pm2: command not found` | PM2 不在 Node 的 bin 目录 | 服务器上执行 `which pm2`,把目录也加到 `NodeBin` |
|
||
| 构建成功但页面没变 | 进程没重启,或 Nginx 缓存没清 | 检查 `RestartCmd`、`NginxCacheDir` 填对没有 |
|
||
| 页面某个区块整块消失 | 缓存里的旧 HTML 引用了已 404 的旧分包 | 手动清一次 `NginxCacheDir` 并 reload |
|
||
| 部署报 `service did not come up` | 新进程没起来 | 看 `/www/wwwlogs/yelaymaths-*.out.log` 的报错,多半是端口被占或依赖没装 |
|
||
| `Permission denied` | 目标目录权限不对 | 服务器上 `chown -R root:root 目标目录` |
|