Files
Genarrative/docs/technical/JENKINS_RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md
kdletters 4a0faf5f51
Some checks failed
CI / verify (pull_request) Has been cancelled
Add deploy pipeline SpacetimeDB auto migration
2026-04-29 18:52:09 +08:00

12 KiB
Raw Blame History

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 端口写成 25001,并把同名端口参数继续透传给下游部署,部署阶段以该参数作为最终监听端口。

本次只补 Jenkins 编排与本地部署脚本,不改现有 Rust 发布包构建逻辑,不恢复旧 server-node 部署链。

2. 执行约束

  1. 构建产物目录统一使用 build/<版本号>/
  2. 默认使用 Jenkins BUILD_NUMBER 作为版本号,避免依赖时间戳;如有需要也允许显式传 BUILD_VERSION
  3. 构建构建并部署checkout scm 后、实际构建前必须执行 git reset --hard HEADgit 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 执行,部署目录清空与文件覆盖仍保持普通权限。
  11. WEB_PORT 必须在 构建并部署部署 两条流水线之间使用同名参数传递;部署脚本会把最终端口写入固定部署目录 .env.localGENARRATIVE_WEB_PORT,避免 sudo 启动 hook 时环境变量被清理导致端口回退。

3. 节点与工作区要求

这套方案依赖“本地目录发布”,因此有两个前提:

  1. 构建并部署部署 必须落到同一台 Ubuntu Jenkins Agent或者落到同一块共享文件系统。
  2. 构建并部署 触发 部署 时,必须把 SOURCE_NODE_NAMESOURCE_WORKSPACE_ROOT 一并传下去。

仓库中提供的 Jenkinsfile 已按这个约束实现:

  1. 构建 / 构建并部署 在指定源码目录内 checkout scm 并生成 build/<版本号>/
  2. 构建并部署 结束构建节点占用后,再触发 部署
  3. 部署 优先按 SOURCE_NODE_NAME 调度到同名节点,再读取 SOURCE_WORKSPACE_ROOT/build/<版本号>/

4. 三条流水线定义

4.1 构建

脚本路径:

jenkins/Jenkinsfile.build

核心流程:

  1. checkout scm 后执行 git reset --hard HEADgit clean -fd 清理工作区。
  2. 可选执行 npm ci
  3. 在源码根目录执行:
npm run deploy:rust:remote -- --skip-upload --name <BUILD_VERSION>
  1. 校验 build/<BUILD_VERSION>/ 存在。
  2. 归档 build/<BUILD_VERSION>/** 作为 Jenkins 产物。

默认版本号:

BUILD_VERSION = Jenkins BUILD_NUMBER

4.2 部署

脚本路径:

jenkins/Jenkinsfile.deploy

核心流程:

  1. 读取触发原因;人工启动时跳过上游门禁,上游触发时同时兼容 BuildUpstreamCause 与经典 UpstreamCause 并继续校验上游作业名。
  2. 校验 BUILD_VERSIONSOURCE_WORKSPACE_ROOTDEPLOY_DIRECTORY 非空。
  3. 执行:
scripts/jenkins-deploy-release.sh \
  --source-dir <SOURCE_WORKSPACE_ROOT>/build/<BUILD_VERSION> \
  --deploy-dir /var/lib/jenkins/deploy/Genarrative \
  --web-port <WEB_PORT> \
  [--clear-database] \
  [--no-migrate-on-conflict] \
  [--migration-dir <MIGRATION_DIRECTORY>] \
  --hook-with-sudo

脚本语义:

  1. 若部署目录已有旧版本且存在 stop.sh,先执行旧版本 stop.sh
  2. 覆盖前如果旧部署目录存在 migration-bootstrap-secret.txt,先复制到 run/migration-bootstrap-secret.previous.txt,供新版本 start.sh 在 schema 冲突自动迁移时授权导出旧库。
  3. 只删除发布产物白名单中的旧文件,例如 web/api-serverspacetime_module.wasmmigration-bootstrap-secret.txtscripts/.env*start.shstop.shweb-server.mjsREADME.md
  4. 将指定版本目录中的同名发布产物复制到部署目录;文件产物使用普通复制,web/scripts/ 等目录产物必须递归复制。
  5. WEB_PORTMIGRATE_ON_CONFLICTMIGRATION_DIRECTORY 写入部署目录 .env.local,确保通过 sudo 执行 start.sh 时仍能读取 Jenkins 参数。
  6. 如果 CLEAR_DATABASE=true,部署脚本会以 ./start.sh --clear-database 启动新版本;这样发布阶段的 spacetime publish 会追加 -c=on-conflict,代表人工确认清库,不进入自动导出和回灌。
  7. 执行新版本 start.sh;普通发布遇到 schema 冲突时,默认由发布包内迁移脚本自动导出旧库、清库发布新 wasm、导入回灌。

如果 RUN_DEPLOY_HOOKS_WITH_SUDO=true,旧版本 stop.sh 和新版本 start.sh 会改为 sudo -n 调用;这要求 Jenkins 运行用户提前配置免密 sudo否则部署会直接失败不会进入交互式密码提示。

这样可以满足“发布文件直接覆盖”的要求,同时保留部署目录里像 .spacetimedb/logs/run/database-migrations/ 这类运行态目录,不会因为部署被整体删除。发布白名单内的 .env.env.local 会先以构建产物中的文件为准;部署脚本会在启动 hook 前移除这些环境文件中的 UTF-8 BOM 与 CRLF并把 Jenkins 部署参数 WEB_PORT 写入 .env.localGENARRATIVE_WEB_PORT,把 MIGRATE_ON_CONFLICT 写入 GENARRATIVE_SPACETIME_MIGRATE_ON_CONFLICT,把 MIGRATION_DIRECTORY 写入 GENARRATIVE_SPACETIME_MIGRATION_DIR,避免 start.sh 在 Bash 下把首行变量名误解析成命令,也避免端口和迁移配置只停留在上游构建阶段。start.sh 会先执行 Ubuntu 专用 sync_ubuntu_spacetime_install,优先从 /usr/.local/share/spacetime/bin/<version>/spacetimedb-cli$HOME/.local/share/spacetime/bin/<version>/spacetimedb-cli 同步到部署目录 .spacetimedb/bin/current/spacetimedb-cli,后续启动、探活和 root-dir 占用判定都使用部署目录内 .spacetimedb/,且不再额外设置 --data-dir,避免 Jenkins 机器全局 spacetime login 变化影响本地库更新;如遇 403 Forbidden,按 SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md 排查数据库所有者与 CLI 身份。

4.3 构建并部署

脚本路径:

jenkins/Jenkinsfile.build-and-deploy

核心流程:

  1. checkout scm 后执行 git reset --hard HEADgit clean -fd 清理工作区。
  2. 复用与 构建 相同的构建命令生成 build/<BUILD_VERSION>/
  3. 归档 build/<BUILD_VERSION>/**
  4. 记录当前 NODE_NAME、源码根目录、版本号。
  5. 构建时额外透传 --web-port <WEB_PORT>,默认生成监听 25001 的发布包。
  6. 触发 部署 流水线,并传递:
    • BUILD_VERSION
    • SOURCE_WORKSPACE_ROOT
    • SOURCE_NODE_NAME
    • DEPLOY_DIRECTORY
    • WEB_PORT
    • CLEAR_DATABASE
    • MIGRATE_ON_CONFLICT
    • MIGRATION_DIRECTORY
    • 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:静态网站监听端口;构建并部署 默认值为 25001,并通过下游 部署 同名参数作为最终启动端口。
  6. CLEAR_DATABASE:部署阶段是否清空 SpacetimeDB 数据后再发布 wasm默认 false
  7. MIGRATE_ON_CONFLICT:普通部署遇到 SpacetimeDB schema 冲突时是否自动导出、清库发布、导入回灌;默认 true
  8. MIGRATION_DIRECTORY:自动迁移 JSON 输出目录;留空时使用部署目录内 database-migrations/<database>

如果当前 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. MIGRATE_ON_CONFLICT
  6. MIGRATION_DIRECTORY
  7. RUN_DEPLOY_HOOKS_WITH_SUDO
  8. EXPECTED_UPSTREAM_JOB
  9. WEB_PORT

其中仅 构建并部署 流水线还需要:

  1. DEPLOY_JOB_NAME
  2. RUN_DEPLOY_HOOKS_WITH_SUDO
  3. WEB_PORT
  4. CLEAR_DATABASE
  5. MIGRATE_ON_CONFLICT
  6. MIGRATION_DIRECTORY

如果你选择启用 RUN_DEPLOY_HOOKS_WITH_SUDO=true,推荐提前在服务器上增加一份最小 sudoers 配置,例如:

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 配置环境变量:

GENARRATIVE_ALLOWED_UPSTREAM_JOB=Genarrative-Build-And-Deploy

如果 Job 在 Jenkins Folder 下,值应填写完整上游作业名,例如:

game/Genarrative-Build-And-Deploy

7. 文件清单

本方案对应的仓库文件:

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 仍以发布包内脚本为准,不替代 systemdsupervisornginxtls 与日志轮转治理。