12 KiB
Jenkins Rust 构建与部署流水线方案
日期:2026-04-23
1. 目标
本方案为当前仓库补齐 3 条 Jenkins 流水线:
构建:只负责在仓库根目录执行npm run deploy:rust:remote -- --skip-upload,生成发布包。部署:只负责把指定发布版本部署到/var/lib/jenkins/deploy/Genarrative/,允许人工按参数启动,并支持按参数决定是否清空 SpacetimeDB 数据。构建并部署:先构建,再把构建出的版本号传给部署流水线并等待部署完成;同时暴露WEB_PORT参数,默认把发布包 Web 端口写成25001,并把同名端口参数继续透传给下游部署,部署阶段以该参数作为最终监听端口。
本次只补 Jenkins 编排与本地部署脚本,不改现有 Rust 发布包构建逻辑,不恢复旧 server-node 部署链。
2. 执行约束
- 构建产物目录统一使用
build/<版本号>/。 - 默认使用 Jenkins
BUILD_NUMBER作为版本号,避免依赖时间戳;如有需要也允许显式传BUILD_VERSION。 构建与构建并部署在checkout scm后、实际构建前必须执行git reset --hard HEAD与git clean -fd,避免固定源码目录内的 Git 变更和未跟踪文件影响发布包;不使用-x,避免删除node_modules/等忽略目录后与RUN_NPM_CI=false冲突。部署流水线允许人工启动;没有上游触发 cause 时按人工部署处理,不再直接失败。部署流水线仅在存在上游触发 cause 时校验上游作业名与传入的EXPECTED_UPSTREAM_JOB一致;如配置了环境变量GENARRATIVE_ALLOWED_UPSTREAM_JOB,还必须与该值一致。构建并部署在触发部署前先释放自己的构建节点,避免单执行器节点出现死锁。部署不重新构建,不重新上传,不从 Jenkins 插件仓库复制产物,直接使用上游构建节点的本地build/<版本号>/目录。部署流水线读取触发原因时必须使用currentBuild.getBuildCauses(...)这类白名单方法,不能直接访问currentBuild.rawBuild,否则会被 Jenkins Script Security 拦截。- 由于 Jenkins Pipeline 的
build步骤触发下游时,原因类型通常是org.jenkinsci.plugins.workflow.support.steps.build.BuildUpstreamCause,实现上需要同时兼容它和经典的hudson.model.Cause$UpstreamCause,否则会把真实的上游触发误判成人工执行。 - 如果线上进程的启停必须经过
sudo,只允许start.sh/stop.sh这两个 hook 使用sudo -n执行,部署目录清空与文件覆盖仍保持普通权限。 WEB_PORT必须在构建并部署与部署两条流水线之间使用同名参数传递;部署脚本会把最终端口写入固定部署目录.env.local的GENARRATIVE_WEB_PORT,避免sudo启动 hook 时环境变量被清理导致端口回退。
3. 节点与工作区要求
这套方案依赖“本地目录发布”,因此有两个前提:
构建并部署与部署必须落到同一台 Ubuntu Jenkins Agent,或者落到同一块共享文件系统。构建并部署触发部署时,必须把SOURCE_NODE_NAME和SOURCE_WORKSPACE_ROOT一并传下去。
仓库中提供的 Jenkinsfile 已按这个约束实现:
构建/构建并部署在指定源码目录内checkout scm并生成build/<版本号>/。构建并部署结束构建节点占用后,再触发部署。部署优先按SOURCE_NODE_NAME调度到同名节点,再读取SOURCE_WORKSPACE_ROOT/build/<版本号>/。
4. 三条流水线定义
4.1 构建
脚本路径:
jenkins/Jenkinsfile.build
核心流程:
checkout scm后执行git reset --hard HEAD与git clean -fd清理工作区。- 可选执行
npm ci。 - 在源码根目录执行:
npm run deploy:rust:remote -- --skip-upload --name <BUILD_VERSION>
- 校验
build/<BUILD_VERSION>/存在。 - 归档
build/<BUILD_VERSION>/**作为 Jenkins 产物。
默认版本号:
BUILD_VERSION = Jenkins BUILD_NUMBER
4.2 部署
脚本路径:
jenkins/Jenkinsfile.deploy
核心流程:
- 读取触发原因;人工启动时跳过上游门禁,上游触发时同时兼容
BuildUpstreamCause与经典UpstreamCause并继续校验上游作业名。 - 校验
BUILD_VERSION、SOURCE_WORKSPACE_ROOT、DEPLOY_DIRECTORY非空。 - 执行:
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
脚本语义:
- 若部署目录已有旧版本且存在
stop.sh,先执行旧版本stop.sh。 - 覆盖前如果旧部署目录存在
migration-bootstrap-secret.txt,先复制到run/migration-bootstrap-secret.previous.txt,供新版本start.sh在 schema 冲突自动迁移时授权导出旧库。 - 只删除发布产物白名单中的旧文件,例如
web/、api-server、spacetime_module.wasm、migration-bootstrap-secret.txt、scripts/、.env*、start.sh、stop.sh、web-server.mjs、README.md。 - 将指定版本目录中的同名发布产物复制到部署目录;文件产物使用普通复制,
web/、scripts/等目录产物必须递归复制。 - 把
WEB_PORT、MIGRATE_ON_CONFLICT、MIGRATION_DIRECTORY写入部署目录.env.local,确保通过 sudo 执行start.sh时仍能读取 Jenkins 参数。 - 如果
CLEAR_DATABASE=true,部署脚本会以./start.sh --clear-database启动新版本;这样发布阶段的spacetime publish会追加-c=on-conflict,代表人工确认清库,不进入自动导出和回灌。 - 执行新版本
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.local 的 GENARRATIVE_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
核心流程:
checkout scm后执行git reset --hard HEAD与git clean -fd清理工作区。- 复用与
构建相同的构建命令生成build/<BUILD_VERSION>/。 - 归档
build/<BUILD_VERSION>/**。 - 记录当前
NODE_NAME、源码根目录、版本号。 - 构建时额外透传
--web-port <WEB_PORT>,默认生成监听25001的发布包。 - 触发
部署流水线,并传递:BUILD_VERSIONSOURCE_WORKSPACE_ROOTSOURCE_NODE_NAMEDEPLOY_DIRECTORYWEB_PORTCLEAR_DATABASEMIGRATE_ON_CONFLICTMIGRATION_DIRECTORYEXPECTED_UPSTREAM_JOB
5. Jenkins 参数建议
三条流水线统一建议暴露以下参数:
AGENT_LABEL:默认执行节点标签。GENARRATIVE_WORKSPACE_ROOT:源码根目录;为空时回退到 Jenkins 当前工作区。BUILD_VERSION:发布版本号;为空时回退到BUILD_NUMBER。RUN_NPM_CI:是否在构建前执行npm ci。WEB_PORT:静态网站监听端口;构建并部署默认值为25001,并通过下游部署同名参数作为最终启动端口。CLEAR_DATABASE:部署阶段是否清空 SpacetimeDB 数据后再发布 wasm;默认false。MIGRATE_ON_CONFLICT:普通部署遇到 SpacetimeDB schema 冲突时是否自动导出、清库发布、导入回灌;默认true。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 解释器。
其中仅 部署 流水线还需要:
SOURCE_WORKSPACE_ROOTSOURCE_NODE_NAMEDEPLOY_DIRECTORYCLEAR_DATABASEMIGRATE_ON_CONFLICTMIGRATION_DIRECTORYRUN_DEPLOY_HOOKS_WITH_SUDOEXPECTED_UPSTREAM_JOBWEB_PORT
其中仅 构建并部署 流水线还需要:
DEPLOY_JOB_NAMERUN_DEPLOY_HOOKS_WITH_SUDOWEB_PORTCLEAR_DATABASEMIGRATE_ON_CONFLICTMIGRATION_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,并分别指向仓库中的脚本路径:
Genarrative-Build->jenkins/Jenkinsfile.buildGenarrative-Deploy->jenkins/Jenkinsfile.deployGenarrative-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. 风险与边界
- 该方案依赖本地目录切换,不适用于“构建节点”和“部署节点”完全隔离且不共享文件系统的 Jenkins 架构。
- 当前
部署采取的是“覆盖固定部署目录”的方式,不包含版本回滚目录管理;如需保留完整历史版本,应在后续单独补一层 release/current 软链接结构。 - 当前
start.sh/stop.sh仍以发布包内脚本为准,不替代systemd、supervisor、nginx、tls与日志轮转治理。