Files
cozsweet-frontend-nextjs/docs/auto-build-restart.md
T

171 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 自动构建与自动重启流程
本文档说明当前项目通过 Git remote + `post-receive` hook 实现自动构建、自动重启的流程。核心文件为 [.githooks/post-receive](../.githooks/post-receive)。
## 部署入口
当前涉及两个服务器 remote
| 环境 | Git remote | 推送分支 | 服务端端口 | 部署脚本 |
| --- | --- | --- | --- | --- |
| 测试环境 | `test` | `test` | `9135` | `scripts/deploy/deploy_web_test.sh` |
| 生产环境 | `production` | `main` | `9185` | `scripts/deploy/deploy_web.sh` |
当前 remote 配置示例:
```bash
test root@43.106.13.130:/root/cozsweet-repos/test
production root@43.106.13.130:/root/cozsweet-repos/main
```
常用发布入口:
```bash
# 发布测试环境
./scripts/release/pre_release_web.sh
# 发布生产环境
./scripts/release/release_web.sh
```
也可以只执行推送脚本:
```bash
# 推送 test remote 的 test 分支
./scripts/deploy/deploy_web_test.sh
# 推送 production remote 的 main 分支
./scripts/deploy/deploy_web.sh
```
## 本地发布流程
测试环境发布流程:
1. 进入 test worktree`cozsweet-frontend-nextjs.worktrees/cozsweet-nextjs-test`
2. 确认当前分支。
3. 执行 `git rebase dev`,把 `dev` 的最新代码变基到 `test` 分支。
4. 复制测试环境图标到 `public/`
5. 复制 `env-example/.env.local.example``.env.local`
6. 执行 `scripts/deploy/deploy_web_test.sh`
7. `deploy_web_test.sh` 推送 `test` remote 的 `test` 分支。
8. 推送成功后尝试清除 Cloudflare CDN 缓存。
生产环境发布流程:
1. 进入 main worktree`cozsweet-frontend-nextjs.worktrees/cozsweet-nextjs-main`
2. 确认当前分支。
3. 执行 `git rebase dev`,把 `dev` 的最新代码变基到 `main` 分支。
4. 复制生产环境图标到 `public/`
5. 复制 `env-example/.env.production.example``.env.production`
6. 执行 `scripts/deploy/deploy_web.sh`
7. `deploy_web.sh` 强制推送 `production` remote 的 `main` 分支。
8. 推送成功后尝试清除 Cloudflare CDN 缓存。
## 服务器端 post-receive 流程
服务器收到 push 后,`post-receive` hook 会自动执行以下步骤:
1. 初始化 Node / pnpm 环境。
2. 解析当前仓库目录,进入服务端工作树。
3. 初始化日志文件:`logs/post-receive.log`
4. 覆盖写入本次执行元信息,包括时间、分支、commit、cwd。
5. 执行 `git reset --hard HEAD`,让服务端工作树同步到刚推送的最新 commit。
6. 根据当前分支复制环境变量文件。
7. 根据当前分支选择 Next.js 启动端口。
8. 执行 `pnpm run build:deploy`
9. 如果构建失败,写入失败日志并中止,不会重启服务。
10. 如果构建成功,查找并强杀当前端口上的旧 Next.js 进程。
11. 等待端口释放,并重试确认。
12. 端口释放成功后,执行 `nohup pnpm run start` 后台启动新服务。
## 分支、环境变量、端口映射
`post-receive` 当前按服务端工作树所在分支判断环境:
| 服务端分支 | 环境变量复制 | 启动端口 |
| --- | --- | --- |
| `main` | `env-example/.env.production.example``.env.production` | `9185` |
| `test` | `env-example/.env.local.example``.env.local` | `9135` |
| `dev` | `env-example/.env.development.example``.env.local` | `9135` |
| 其他分支 | 跳过 env 复制 | `9135` |
本地生产发布脚本和服务器端 `post-receive` 已统一使用 `.env.production`。Next.js 在生产构建时会读取 `.env.production`;测试和开发发布仍使用 `.env.local`
本地部署脚本清除 Cloudflare CDN 缓存时,也会按当前分支读取 env:`main` 优先读取 `.env.production`,其他分支优先读取 `.env.local`。如果优先文件不存在或缺少 `CF_ZONE_ID` / `CF_API_TOKEN`,会尝试读取另一个文件作为兜底。
## 自动重启机制
重启由 `post-receive` 中的端口进程管理实现:
1. 使用 `ss -tulnp` 查询占用目标端口的进程。
2. 如果找到旧进程,执行 `kill -9` 强制停止。
3. 默认等待 `3` 秒。
4. 最多重试 `3` 次确认端口已经释放。
5. 如果端口仍未释放,中止启动,避免新服务出现 `EADDRINUSE`
6. 如果端口释放成功,执行:
```bash
PORT="$START_PORT" nohup pnpm run start >> "$LOG_FILE" 2>&1 &
```
`package.json` 中的 `start` 脚本会优先使用传入的 `PORT`,因此测试环境固定使用 `9135`,生产环境固定使用 `9185`
## 日志与排查
服务器端日志文件:
```bash
logs/post-receive.log
```
当前日志策略是每次 `post-receive` 执行时覆盖旧日志,而不是追加。
常见排查命令:
```bash
# 查看最近一次部署日志
tail -n 200 logs/post-receive.log
# 查看端口占用
ss -tulnp | grep ':9135'
ss -tulnp | grep ':9185'
# 查看当前分支和 commit
git branch --show-current
git rev-parse --short HEAD
```
构建失败时,日志中会出现:
```text
build EXIT_CODE=<非 0>
ABORTED ... (build failed, start skipped)
```
这种情况下不会停止旧服务,也不会启动新服务。
端口释放失败时,日志中会出现:
```text
stop FAILED: port <端口> still occupied
```
这种情况下不会启动新服务,需要人工检查占用该端口的进程。
## 关键约束
1. `scripts/deploy/*` 只负责推送代码,不负责本地构建和本地启动。
2. 自动构建和自动重启只发生在服务器端 `post-receive` hook。
3. `main` 分支对应生产环境,`test` 分支对应测试环境。
4. 生产推送当前使用 `git push --force production main`
5. 构建失败时不会启动新服务,避免用旧 `.next` 跑新代码。
6. 服务启动使用 `nohup` 后台进程,不依赖 PM2 或 systemd。
## 后续优化建议
1. 将服务进程交给 PM2 或 systemd 管理,提升崩溃自动恢复能力。
2. 保留最近 N 次部署日志,而不是只保留最后一次日志,方便排查历史问题。
3. 构建命令可以保留完整输出到单独日志文件,当前 `pnpm run build:deploy` 输出被丢弃,只记录退出码。
4. 可以在启动后增加健康检查,确认新服务真正可访问后再判定部署成功。