init with react+axum+spacetimedb
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-26 18:06:23 +08:00
commit cbc27bad4a
20199 changed files with 883714 additions and 0 deletions

View File

@@ -0,0 +1,202 @@
# Jenkins Rust 构建与部署流水线方案
日期:`2026-04-23`
## 1. 目标
本方案为当前仓库补齐 3 条 Jenkins 流水线:
1. `构建`:只负责在仓库根目录执行 `npm run deploy:rust:remote -- --skip-upload`,生成发布包。
2. `部署`:只负责把指定发布版本部署到 `/var/lib/jenkins/deploy/Genarrative/`,允许人工按参数启动,并支持按参数决定是否清空 SpacetimeDB 数据。
3. `构建并部署`:先构建,再把构建出的版本号传给 `部署` 流水线并等待部署完成;同时暴露 `WEB_PORT` 参数,默认把发布包 Web 端口写成 `80`,并透传是否清库。
本次只补 Jenkins 编排与本地部署脚本,不改现有 Rust 发布包构建逻辑,不恢复旧 `server-node` 部署链。
## 2. 执行约束
1. 构建产物目录统一使用 `build/<版本号>/`
2. 默认使用 Jenkins `BUILD_NUMBER` 作为版本号,避免依赖时间戳;如有需要也允许显式传 `BUILD_VERSION`
3. `构建``构建并部署``checkout scm` 后、实际构建前必须执行 `git reset --hard HEAD``git clean -fd`,避免固定源码目录内的 Git 变更和未跟踪文件影响发布包;不使用 `-x`,避免删除 `node_modules/` 等忽略目录后与 `RUN_NPM_CI=false` 冲突。
4. `部署` 流水线允许人工启动;没有上游触发 cause 时按人工部署处理,不再直接失败。
5. `部署` 流水线仅在存在上游触发 cause 时校验上游作业名与传入的 `EXPECTED_UPSTREAM_JOB` 一致;如配置了环境变量 `GENARRATIVE_ALLOWED_UPSTREAM_JOB`,还必须与该值一致。
6. `构建并部署` 在触发 `部署` 前先释放自己的构建节点,避免单执行器节点出现死锁。
7. `部署` 不重新构建,不重新上传,不从 Jenkins 插件仓库复制产物,直接使用上游构建节点的本地 `build/<版本号>/` 目录。
8. `部署` 流水线读取触发原因时必须使用 `currentBuild.getBuildCauses(...)` 这类白名单方法,不能直接访问 `currentBuild.rawBuild`,否则会被 Jenkins Script Security 拦截。
9. 由于 Jenkins Pipeline 的 `build` 步骤触发下游时,原因类型通常是 `org.jenkinsci.plugins.workflow.support.steps.build.BuildUpstreamCause`,实现上需要同时兼容它和经典的 `hudson.model.Cause$UpstreamCause`,否则会把真实的上游触发误判成人工执行。
10. 如果线上进程的启停必须经过 `sudo`,只允许 `start.sh` / `stop.sh` 这两个 hook 使用 `sudo -n` 执行,部署目录清空与文件覆盖仍保持普通权限。
## 3. 节点与工作区要求
这套方案依赖“本地目录发布”,因此有两个前提:
1. `构建并部署``部署` 必须落到同一台 Ubuntu Jenkins Agent或者落到同一块共享文件系统。
2. `构建并部署` 触发 `部署` 时,必须把 `SOURCE_NODE_NAME``SOURCE_WORKSPACE_ROOT` 一并传下去。
仓库中提供的 Jenkinsfile 已按这个约束实现:
1. `构建` / `构建并部署` 在指定源码目录内 `checkout scm` 并生成 `build/<版本号>/`
2. `构建并部署` 结束构建节点占用后,再触发 `部署`
3. `部署` 优先按 `SOURCE_NODE_NAME` 调度到同名节点,再读取 `SOURCE_WORKSPACE_ROOT/build/<版本号>/`
## 4. 三条流水线定义
### 4.1 构建
脚本路径:
```text
jenkins/Jenkinsfile.build
```
核心流程:
1. `checkout scm` 后执行 `git reset --hard HEAD``git clean -fd` 清理工作区。
2. 可选执行 `npm ci`
3. 在源码根目录执行:
```bash
npm run deploy:rust:remote -- --skip-upload --name <BUILD_VERSION>
```
4. 校验 `build/<BUILD_VERSION>/` 存在。
5. 归档 `build/<BUILD_VERSION>/**` 作为 Jenkins 产物。
默认版本号:
```text
BUILD_VERSION = Jenkins BUILD_NUMBER
```
### 4.2 部署
脚本路径:
```text
jenkins/Jenkinsfile.deploy
```
核心流程:
1. 读取触发原因;人工启动时跳过上游门禁,上游触发时同时兼容 `BuildUpstreamCause` 与经典 `UpstreamCause` 并继续校验上游作业名。
2. 校验 `BUILD_VERSION``SOURCE_WORKSPACE_ROOT``DEPLOY_DIRECTORY` 非空。
3. 执行:
```bash
scripts/jenkins-deploy-release.sh \
--source-dir <SOURCE_WORKSPACE_ROOT>/build/<BUILD_VERSION> \
--deploy-dir /var/lib/jenkins/deploy/Genarrative \
[--clear-database] \
--hook-with-sudo
```
脚本语义:
1. 若部署目录已有旧版本且存在 `stop.sh`,先执行旧版本 `stop.sh`
2. 只删除发布产物白名单中的旧文件,例如 `web/``api-server``spacetime_module.wasm``.env*``start.sh``stop.sh``web-server.mjs``README.md`
3. 将指定版本目录中的同名发布产物移动到部署目录。
4. 如果 `CLEAR_DATABASE=true`,部署脚本会以 `./start.sh --clear-database` 启动新版本;这样发布阶段的 `spacetime publish` 会追加 `-c always`
5. 执行新版本 `start.sh`
如果 `RUN_DEPLOY_HOOKS_WITH_SUDO=true`,第 1 步和第 4 步会改为 `sudo -n` 调用;这要求 Jenkins 运行用户提前配置免密 sudo否则部署会直接失败不会进入交互式密码提示。
这样可以满足“发布文件直接覆盖”的要求,同时保留部署目录里像 `spacetimedb-data/``logs/``run/` 这类运行态目录,不会因为部署被整体删除。发布白名单内的 `.env``.env.local` 仍会以构建产物中的文件为准;部署脚本会在启动 hook 前移除这些环境文件中的 UTF-8 BOM 与 CRLF避免 `start.sh` 在 Bash 下把首行变量名误解析成命令。
### 4.3 构建并部署
脚本路径:
```text
jenkins/Jenkinsfile.build-and-deploy
```
核心流程:
1. `checkout scm` 后执行 `git reset --hard HEAD``git clean -fd` 清理工作区。
2. 复用与 `构建` 相同的构建命令生成 `build/<BUILD_VERSION>/`
3. 归档 `build/<BUILD_VERSION>/**`
4. 记录当前 `NODE_NAME`、源码根目录、版本号。
5. 构建时额外透传 `--web-port <WEB_PORT>`,默认生成监听 `80` 的发布包。
6. 触发 `部署` 流水线,并传递:
- `BUILD_VERSION`
- `SOURCE_WORKSPACE_ROOT`
- `SOURCE_NODE_NAME`
- `DEPLOY_DIRECTORY`
- `CLEAR_DATABASE`
- `EXPECTED_UPSTREAM_JOB`
## 5. Jenkins 参数建议
三条流水线统一建议暴露以下参数:
1. `AGENT_LABEL`:默认执行节点标签。
2. `GENARRATIVE_WORKSPACE_ROOT`:源码根目录;为空时回退到 Jenkins 当前工作区。
3. `BUILD_VERSION`:发布版本号;为空时回退到 `BUILD_NUMBER`
4. `RUN_NPM_CI`:是否在构建前执行 `npm ci`
5. `WEB_PORT`:发布包内静态网站监听端口;`构建并部署` 默认值为 `80`
6. `CLEAR_DATABASE`:部署阶段是否清空 SpacetimeDB 数据后再发布 wasm默认 `false`
如果当前 Jenkins 没有额外配置独立 Agent而是直接在控制器自身执行任务`AGENT_LABEL` 应填写 `built-in`
如果 Jenkins 进程以默认 `jenkins` 用户运行,部署目录建议直接放在 `/var/lib/jenkins/deploy/Genarrative` 这类 Jenkins 自有目录下,避免再依赖 `/home/ubuntu/*` 的额外写权限。
如果目标 Ubuntu 的 Jenkins `sh` 默认实际落到 `/bin/sh -> dash`,而流水线脚本又使用了 `set -euo pipefail`,则必须显式通过 `bash -lc` 执行命令,不能直接依赖 Jenkins 默认 `sh` 解释器。
其中仅 `部署` 流水线还需要:
1. `SOURCE_WORKSPACE_ROOT`
2. `SOURCE_NODE_NAME`
3. `DEPLOY_DIRECTORY`
4. `CLEAR_DATABASE`
5. `RUN_DEPLOY_HOOKS_WITH_SUDO`
6. `EXPECTED_UPSTREAM_JOB`
其中仅 `构建并部署` 流水线还需要:
1. `DEPLOY_JOB_NAME`
2. `RUN_DEPLOY_HOOKS_WITH_SUDO`
3. `WEB_PORT`
4. `CLEAR_DATABASE`
如果你选择启用 `RUN_DEPLOY_HOOKS_WITH_SUDO=true`,推荐提前在服务器上增加一份最小 sudoers 配置,例如:
```text
jenkins ALL=(root) NOPASSWD: /var/lib/jenkins/deploy/Genarrative/start.sh
jenkins ALL=(root) NOPASSWD: /var/lib/jenkins/deploy/Genarrative/stop.sh
```
这样可以把提权范围收敛到固定部署目录下的启停脚本,而不是把整个部署流程都交给 `sudo`
## 6. 推荐 Job 命名
建议在 Jenkins 中创建以下 3 个 Pipeline Job并分别指向仓库中的脚本路径
1. `Genarrative-Build` -> `jenkins/Jenkinsfile.build`
2. `Genarrative-Deploy` -> `jenkins/Jenkinsfile.deploy`
3. `Genarrative-Build-And-Deploy` -> `jenkins/Jenkinsfile.build-and-deploy`
同时给 `Genarrative-Deploy` 配置环境变量:
```text
GENARRATIVE_ALLOWED_UPSTREAM_JOB=Genarrative-Build-And-Deploy
```
如果 Job 在 Jenkins Folder 下,值应填写完整上游作业名,例如:
```text
game/Genarrative-Build-And-Deploy
```
## 7. 文件清单
本方案对应的仓库文件:
```text
jenkins/Jenkinsfile.build
jenkins/Jenkinsfile.deploy
jenkins/Jenkinsfile.build-and-deploy
scripts/jenkins-deploy-release.sh
```
## 8. 风险与边界
1. 该方案依赖本地目录切换,不适用于“构建节点”和“部署节点”完全隔离且不共享文件系统的 Jenkins 架构。
2. 当前 `部署` 采取的是“覆盖固定部署目录”的方式,不包含版本回滚目录管理;如需保留完整历史版本,应在后续单独补一层 release/current 软链接结构。
3. 当前 `start.sh` / `stop.sh` 仍以发布包内脚本为准,不替代 `systemd``supervisor``nginx``tls` 与日志轮转治理。