179 lines
7.8 KiB
Markdown
179 lines
7.8 KiB
Markdown
# 一键部署脚本
|
|
|
|
在本机运行一条命令,自动完成「打包源码 → 上传服务器 → 构建 → 重启进程」。
|
|
|
|
服务器上**不需要安装任何东西**,它复用的就是你已经能用的 SSH。
|
|
|
|
---
|
|
|
|
## 一次性配置
|
|
|
|
### 1. 填配置文件
|
|
|
|
编辑 `deploy/deploy.config.ps1`,需要改这几项:
|
|
|
|
| 配置项 | 填什么 | 怎么查 |
|
|
|---|---|---|
|
|
| `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
|
|
100.8 KB (src, prisma, tools, package.json, ...)
|
|
[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/`(依赖在服务器上装)
|
|
- `dist/`、`.next/`(构建产物在服务器上生成)
|
|
|
|
服务端实际约 100 KB,管理端约 25 KB。
|
|
|
|
---
|
|
|
|
## 已知限制
|
|
|
|
**删除的文件不会同步删除。** 脚本是「覆盖式上传」,如果你在本地删掉某个源码文件,服务器上那份还在。
|
|
|
|
一般不影响运行(多余的文件不会被执行),但如果你发现改了名字的旧文件仍在作怪,手动上服务器删掉即可。
|
|
|
|
需要「完全同步」的场景(比如删文件、想要版本回滚),改用 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 目标目录` |
|