# 一键部署脚本 在本机运行一条命令,自动完成「打包源码 → 上传服务器 → 构建 → 重启进程」。 服务器上**不需要安装任何东西**,它复用的就是你已经能用的 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 目标目录` |