35 KiB
生产部署计划
更新时间:2026-05-02
当前落地进度
已落地生产基础设施骨架与首批生产 Jenkinsfile:
deploy/systemd/spacetimedb.servicedeploy/systemd/genarrative-api.servicedeploy/nginx/genarrative.confdeploy/nginx/genarrative-dev-http.confdeploy/nginx/snippets/genarrative-maintenance.confdeploy/env/api-server.env.examplescripts/deploy/maintenance-on.shscripts/deploy/maintenance-off.shscripts/deploy/maintenance-status.shscripts/build-production-release.shscripts/jenkins-checkout-source.shscripts/deploy/production-web-deploy.shscripts/deploy/production-api-deploy.shscripts/deploy/production-stdb-publish.shjenkins/Jenkinsfile.production-web-buildjenkins/Jenkinsfile.production-web-deployjenkins/Jenkinsfile.production-api-buildjenkins/Jenkinsfile.production-api-deployjenkins/Jenkinsfile.production-stdb-module-buildjenkins/Jenkinsfile.production-stdb-module-publishjenkins/Jenkinsfile.production-full-build-and-deployjenkins/Jenkinsfile.production-server-provisionjenkins/Jenkinsfile.production-database-exportjenkins/Jenkinsfile.production-database-importjenkins/Jenkinsfile.production-notify-emailnpm run build:production-release
旧 Jenkins 一体化发布链对应的 Jenkinsfile 已从仓库移除,生产构建和发布入口统一切到 jenkins/Jenkinsfile.production-*。scripts/deploy-rust-remote.sh 等旧发布包脚本暂保留为历史迁移参考,不再作为生产 Jenkins Job 的入口。
目标
将当前部署方式调整为单机生产推荐方案:生产运行路径不使用 Docker,不再使用旧的一体化启动脚本,由 systemd 托管 SpacetimeDB 与 Rust api-server,由 Nginx 托管主站、后台前端与必要反向代理。
本计划用于重新创建 Jenkins 流水线、服务器环境配置、网站发布、api-server 发布、SpacetimeDB 模块发布,以及数据库人工导入导出流程。
生产架构
- Nginx 作为唯一公网入口,负责 HTTPS、静态站点、后台静态页面、维护页与
/admin/api/反向代理。 - SpacetimeDB 作为系统服务运行,监听
127.0.0.1:3000,数据根目录为/stdb。 - Rust
api-server作为系统服务运行,监听127.0.0.1:8082,只被 Nginx 的/admin/api/访问。 - 主站与后台前端构建为静态文件,发布到服务器固定目录,不放入 Jenkins 目录,也不跟随 Docker 镜像。
- 除网站静态发布外,
api-server发布、SpacetimeDB 模块发布、数据库导入、服务器配置变更都必须先进入维护模式。
服务器目录
/opt/genarrative/releases/<version>/:每次发布的完整版本目录。/opt/genarrative/current:指向当前生效版本的软链接。/srv/genarrative/web:指向/opt/genarrative/current/web,供 Nginx 托管静态站点。/etc/genarrative/api-server.env:api-server生产环境变量文件。/var/lib/genarrative/maintenance/enabled:维护模式开关文件。/stdb:SpacetimeDB 程序、配置与数据根目录。
生产密钥
/etc/genarrative/api-server.env 中的生产密钥指所有只能存在于生产服务器、不能进入 Git、不能进入构建产物的敏感配置。典型内容包括:
- LLM 或第三方服务 API Key。
- 短信服务 Access Key 与 Secret。
- 后台登录、会话、签名、加密相关密钥。
- 生产 SpacetimeDB 地址与数据库名。
- 只允许生产使用的回调地址、白名单或内部令牌。
该文件由服务器配置流水线或人工初始化创建,权限建议为 root:genarrative、0640。Jenkins 构建任务不能读取该文件;只有生产发布或服务启动需要读取。
systemd 服务
SpacetimeDB
- 服务名:
spacetimedb.service - 运行用户:
spacetimedb - 工作目录:
/stdb - 启动命令:
/stdb/spacetime --root-dir=/stdb start --listen-addr=127.0.0.1:3000 - 对外暴露:默认不直接暴露公网端口。
该方案与 SpacetimeDB 官方自托管文档一致:使用 Ubuntu、专用用户、/stdb 根目录、systemd 服务和 Nginx。
api-server
- 服务名:
genarrative-api.service - 运行用户:
genarrative - 工作目录:
/opt/genarrative/current - 可执行文件:
/opt/genarrative/current/api-server - 环境文件:
/etc/genarrative/api-server.env - 监听地址:
127.0.0.1:8082
api-server 不放入 Docker,也不直接暴露公网端口。发布时替换版本目录并重启 genarrative-api.service。
Nginx 规则
生产正式入口只保留必要路由:
/:主站静态页面。/admin/:后台前端静态页面,后台构建时使用/admin/作为 base path。/admin/api/:反向代理到http://127.0.0.1:8082/admin/api/。- HTTP 到 HTTPS:
production-https模式只保留 301 重定向。 /maintenance.html:维护中页面。
移除这些公网反向代理:
/api/*/generated-*- 公网
/healthz - 其他旧的一体化 web server 代理入口。
SpacetimeDB 公网路由默认保持收敛,只按实际前端 SDK 需要暴露最小集合。禁止开放可远程发布数据库或管理实例的通用入口。
Nginx 配置文件分为两类:
deploy/nginx/genarrative.conf:生产正式域名 HTTPS 配置,genarrative.example.com只是占位域名,安装时必须替换为真实SERVER_NAME,并要求/etc/letsencrypt/live/<SERVER_NAME>/fullchain.pem与privkey.pem已存在。deploy/nginx/genarrative-dev-http.conf:开发服无域名时的 HTTP-only 配置,只允许DEPLOY_TARGET=development使用。没有域名时,SERVER_NAME填开发机 IP 或临时主机名。它仍复用同一套静态目录、后台 API 反代和 SpacetimeDB SDK 最小公网路由,不恢复旧/api/*、/generated-*或公网/healthz。
维护模式
维护模式由 /var/lib/genarrative/maintenance/enabled 控制:
- 文件存在:进入维护模式。
- 文件不存在:退出维护模式。
行为:
- 网站静态资源发布不进入维护模式。
api-server发布、SpacetimeDB 模块发布、数据库导入、服务器配置变更必须进入维护模式。- 普通页面在维护模式下展示
/maintenance.html。 /admin/api/*在维护模式下返回 503。- 静态资源仍允许访问,避免维护页样式和资源加载失败。
- 发布成功后自动解除维护模式。
- 发布失败时保持维护模式,并通过邮件通知人工处理。
构建产物
生产发布包构建入口:
npm run build:production-release -- --name <version>
每次构建产物按版本号归档:
build/<version>/
├─ web/
│ ├─ index.html
│ ├─ assets/
│ ├─ maintenance.html
│ └─ admin/
├─ web.tar.gz
├─ web.tar.gz.sha256
├─ api-server
├─ api-server.sha256
├─ spacetime_module.wasm
├─ spacetime_module.wasm.sha256
├─ release-manifest.json
├─ scripts/
│ ├─ database-export.mjs
│ ├─ database-import.mjs
│ ├─ spacetime-migration-common.mjs
│ ├─ maintenance-on.sh
│ ├─ maintenance-off.sh
│ └─ maintenance-status.sh
├─ deploy/
│ ├─ systemd/
│ │ ├─ spacetimedb.service
│ │ └─ genarrative-api.service
│ ├─ nginx/
│ │ ├─ genarrative.conf
│ │ ├─ genarrative-dev-http.conf
│ │ └─ snippets/genarrative-maintenance.conf
│ └─ env/api-server.env.example
└─ README.md
web/ 可以保留在构建目录中供本地 smoke test 与人工排查使用,但 Jenkins Web Build 归档和 Web Deploy 传输必须以 web.tar.gz 为主,避免把大量静态碎文件逐个传回 Jenkins controller。api-server 和 spacetime_module.wasm 是单文件产物,默认直接归档单文件与对应 .sha256,不强制压缩。
不再生成旧产物:
web-server.mjs- 旧的一体化
start.sh - 旧的一体化
stop.sh
Jenkins 节点
Jenkins 可运行在 Windows 或其他机器上,本机 Windows 只作为人工触发入口;构建与发布动作只允许由 Jenkins 调度到 Linux agent 执行。当前已接入的 Linux agent 是开发/构建机,同时也是 development 环境部署机。
开发/构建/开发部署实例
- Jenkins Job 参数不暴露真实节点名、IP 或带 IP 的标签。
- 构建 Job 固定使用 label expression:
linux && genarrative-build。 - 当前开发/构建/开发部署 agent 必须同时配置
linux与genarrative-build两个标签;非 Linux 节点不能承担构建或部署。 - 用途:拉代码、安装依赖、构建主站、构建后台、构建
api-server、构建 SpacetimeDB wasm、归档产物,并执行DEPLOY_TARGET=development的开发环境部署。
生产/发布实例
- Jenkins Job 参数不暴露真实节点名、IP 或带 IP 的标签。
- 生产机已作为独立 Linux Jenkins agent 接入,节点名使用脱敏名称
genarrative-release-deploy-01,调度标签只使用linux与genarrative-release-deploy。 - 生产机真实连接地址只允许保存在 Jenkins 节点 SSH launcher 的
host字段中,不能写入节点名、调度标签、Job 参数默认值或文档推荐命令。 - 发布 Job 通过
DEPLOY_TARGET选择逻辑部署目标,再在 Jenkinsfile 内部映射到 Linux-only 脱敏调度表达式:development -> linux && genarrative-build,release -> linux && genarrative-release-deploy。 - 用途:服务器配置、发布静态网站、发布
api-server、发布 SpacetimeDB 模块、数据库导入导出、维护模式切换。
Git 仓库访问
Jenkins controller 与 Linux agent 看到的 Git 服务地址不同,必须拆成两层配置:
- Jenkins Job 的
Pipeline script from SCM由 controller 执行,SCM URL 使用 controller 可访问的公网地址:http://82.157.175.59:3000/GenarrativeAI/Genarrative.git。 - Jenkinsfile 内部的源码、脚本 checkout 在 Linux agent 上执行,
GIT_REMOTE_URL使用 agent 本机可访问地址:http://127.0.0.1:3000/GenarrativeAI/Genarrative.git。
因此生产 Jenkinsfile 不使用 checkout scm 作为构建源码入口,而是显式 checkout([$class: 'GitSCM', userRemoteConfigs: [[url: "${GIT_REMOTE_URL}"]], ...])。后续 scripts/jenkins-checkout-source.sh 会继续把 origin 设置为 GIT_REMOTE_URL,并按 SOURCE_BRANCH / COMMIT_HASH 拉取和校验目标提交。
127.0.0.1 只代表当前执行该阶段的 Linux agent 自身;如果 release agent 与 Git 服务不在同一台机器,必须把对应 Jenkinsfile 的 GIT_REMOTE_URL 改成 release agent 可访问的内网地址,不能让 release 发布阶段回退到 controller 公网拉取。
SSH PEM 凭证
在 Jenkins 中使用 SSH Username with private key 类型添加 PEM 私钥:
genarrative-dev-ssh-key:开发/构建实例 SSH 凭证。genarrative-prod-root-ssh:当前开发/构建实例已使用的 SSH 凭证;生产/发布实例复用同一个凭证。
推荐使用非 root 用户,例如 jenkins。该用户只通过 sudoers 获得必要命令权限,例如 systemctl restart genarrative-api、nginx -t、维护脚本、发布目录切换等。
Jenkins 流水线
生产 Jenkins 目标流水线:
Genarrative-Server-ProvisionGenarrative-Web-BuildGenarrative-Web-DeployGenarrative-Api-BuildGenarrative-Api-DeployGenarrative-Stdb-Module-BuildGenarrative-Stdb-Module-PublishGenarrative-Database-ExportGenarrative-Database-ImportGenarrative-Full-Build-And-DeployGenarrative-Notify-Email
已落地的生产流水线脚本文件:
jenkins/Jenkinsfile.production-web-buildjenkins/Jenkinsfile.production-web-deployjenkins/Jenkinsfile.production-api-buildjenkins/Jenkinsfile.production-api-deployjenkins/Jenkinsfile.production-stdb-module-buildjenkins/Jenkinsfile.production-stdb-module-publishjenkins/Jenkinsfile.production-full-build-and-deployjenkins/Jenkinsfile.production-server-provisionjenkins/Jenkinsfile.production-database-exportjenkins/Jenkinsfile.production-database-importjenkins/Jenkinsfile.production-notify-email
Genarrative-Database-Export、Genarrative-Database-Import 的生产版 Jenkinsfile 已落地;旧的数据库导入导出 Jenkinsfile 已删除,避免继续沿用旧部署目录和旧一体化发布链假设。
构建流水线运行在当前 Linux agent 的脱敏 label expression linux && genarrative-build。发布、导入导出和服务器配置流水线通过 DEPLOY_TARGET 映射到 Linux-only 脱敏部署表达式;其中 development 映射到当前 Linux 开发/构建/开发部署 agent 的 linux && genarrative-build,release 映射到独立 Linux 生产部署 agent 的 linux && genarrative-release-deploy,不能复用当前开发/构建/开发部署 agent。真实机器名、IP 和带 IP 的 Jenkins label 只允许留在 Jenkins 节点连接配置中,不能暴露为 Job 参数默认值、调度标签或文档推荐值。
发布流水线通过 Jenkins copyArtifacts(...) 从对应构建 Job 获取归档产物,因此 Jenkins 需要安装并启用 Copy Artifact 插件。数据库导入流水线的手动上传模式使用 stashedFile 文件参数,因此 Jenkins 还需要安装并启用 File Parameter 插件。所有生产 Pipeline 日志必须带时间戳以便审计,Jenkins 需要安装 Timestamper 插件,并在全局配置中启用 Enabled for all Pipeline builds。邮件通知流水线使用 Jenkins Pipeline mail step,Jenkins 需要安装/启用 Mailer 能力,并在系统配置中配置 SMTP。生产发布不能退回到读取构建 workspace 本地目录的旧模式。
邮件通知的持久收件人不写入 Git,由 Jenkins Secret text 凭据 genarrative-notification-emails 保存,凭据内容为逗号分隔邮箱。所有生产流水线仍提供 NOTIFICATION_EMAILS 参数作为本次运行的追加收件人;通知 Job 会把持久收件人凭据与本次 NOTIFICATION_EMAILS 合并去重后发送,参数留空时只发送给持久收件人。流水线结束时在 post { always { ... } } 中异步触发 Genarrative-Notify-Email,把来源 Job、构建号、构建 URL、结果、源码分支、源码 commit、发布版本、部署目标和数据库名传给通知 Job。通知 Job 失败不能反向改变业务流水线结果,只在来源流水线日志中记录触发失败。
持久收件人在 Jenkins controller 的 Manage Jenkins -> Credentials -> System -> Global credentials 中新增 Secret text 凭据,ID 固定为 genarrative-notification-emails,Secret 填 ops@example.com,dev@example.com 这类逗号分隔邮箱。SMTP 服务器在 Manage Jenkins -> System 的 E-mail Notification 区域配置。邮件地址属于 Jenkins 持久化配置,不作为仓库文件提交。
所有发布流水线必须提供 DEPLOY_TARGET 参数,用于选择逻辑部署目标:
- 默认值:
development,用于当前 Linux 开发/构建/开发部署 agent 上的开发环境部署,对应脱敏调度表达式linux && genarrative-build。 - 备选值:
release,对应生产发布目标,由独立 Linux 生产部署 agent 执行。
发布流水线的 agent 必须使用 DEPLOY_TARGET 在 Jenkinsfile 内部映射到脱敏 label expression,并且表达式必须包含 linux,避免在参数页面暴露真实节点名、IP 或带 IP 的 Jenkins label,也避免非 Linux 节点执行构建或部署。release 目标还必须要求 CONFIRM_RELEASE_DEPLOY_AGENT=true。Genarrative-Full-Build-And-Deploy 也必须透传同一个 DEPLOY_TARGET,确保 Stdb publish、API deploy、Web deploy 三个部署动作落到同一类目标部署环境。
Rust 构建缓存与磁盘控制
Jenkins 在 agent 上执行构建时会为不同 Job 或不同构建创建独立 workspace;Rust 默认把编译产物写入仓库内 server-rs/target/,会导致每个 workspace 都保留一份完整 target 目录,占用大量磁盘。生产构建流水线必须把 Rust 缓存固定到 workspace 外的稳定目录。
如果构建 agent 使用 root 账户执行,缓存目录不应写死为 /var/lib/jenkins。推荐优先使用单独数据盘,例如 /data/jenkins-cache/genarrative/;如果没有数据盘,可使用 /var/cache/genarrative-build/:
mkdir -p /var/cache/genarrative-build/{api-server,stdb-module}
chmod 700 /var/cache/genarrative-build
API 构建流水线建议设置:
environment {
CARGO_HOME = '/var/cache/genarrative-build/api-server/cargo-home'
CARGO_TARGET_DIR = '/var/cache/genarrative-build/api-server/cargo-target/prod-release'
CARGO_INCREMENTAL = '0'
RUSTC_WRAPPER = 'sccache'
SCCACHE_DIR = '/var/cache/genarrative-build/api-server/sccache'
SCCACHE_CACHE_SIZE = '30G'
}
Stdb module 构建流水线建议设置:
environment {
CARGO_HOME = '/var/cache/genarrative-build/stdb-module/cargo-home'
CARGO_TARGET_DIR = '/var/cache/genarrative-build/stdb-module/cargo-target/prod-release'
CARGO_INCREMENTAL = '0'
RUSTC_WRAPPER = 'sccache'
SCCACHE_DIR = '/var/cache/genarrative-build/stdb-module/sccache'
SCCACHE_CACHE_SIZE = '30G'
}
如使用数据盘,则把上述路径替换为:
environment {
CARGO_HOME = '/data/jenkins-cache/genarrative/<component>/cargo-home'
CARGO_TARGET_DIR = '/data/jenkins-cache/genarrative/<component>/cargo-target/prod-release'
CARGO_INCREMENTAL = '0'
RUSTC_WRAPPER = 'sccache'
SCCACHE_DIR = '/data/jenkins-cache/genarrative/<component>/sccache'
SCCACHE_CACHE_SIZE = '30G'
}
其中 <component> 使用 api-server 或 stdb-module。API 与 Stdb module 并行构建时不能共享同一个 CARGO_HOME 或 CARGO_TARGET_DIR,否则容易在 Cargo package cache 或 target 目录上出现 Blocking waiting for file lock on package cache 等锁等待。
Rust 构建流水线还必须在真正执行 cargo 前 source scripts/jenkins-prepare-cargo-env.sh。该脚本会把 HOME 临时切到组件级缓存目录,显式导出组件级 CARGO_HOME、CARGO_TARGET_DIR、SCCACHE_DIR,并在 ${CARGO_HOME}/config.toml 写入可用的 Cargo sparse registry 配置。这样可以避免构建 agent 使用 root 账户时继续读取 /root/.cargo/config 中失效的全局镜像配置,例如错误的 replace-with = "tuna" 导致 config.json not found in registry。
server-rs/.cargo/config.toml 只保留 Linux release 目标的 linker/rustflags 等仓库级构建配置,不在仓库级 config.toml 里重定义 agent 全局镜像源。不要把这些约束写到单个 crate 的 Cargo.toml,因为 Cargo 不会从 crate manifest 的 [target.x86_64-unknown-linux-gnu] 读取构建器配置。
由于 server-rs/.cargo/config.toml 使用 clang 与 -fuse-ld=lld 构建 Linux release 目标,构建 agent 必须安装 clang 和 lld。Genarrative-Server-Provision 负责通过 apt-get、dnf 或 yum 安装 clang、lld、pkg-config/pkgconf-pkg-config、OpenSSL headers 与 CA 证书;API/Stdb 构建流水线在执行 Cargo 前必须检查 clang 与 lld,缺失时直接失败并提示先运行 Server-Provision。
scripts/build-production-release.sh 必须尊重 CARGO_TARGET_DIR,不能硬编码从 server-rs/target/ 拷贝 Rust 产物。脚本中的产物路径应按以下口径计算:
CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-${SERVER_RS_DIR}/target}"
API_BINARY_SOURCE="${CARGO_TARGET_DIR}/x86_64-unknown-linux-gnu/release/api-server"
WASM_SOURCE="${CARGO_TARGET_DIR}/wasm32-unknown-unknown/release/spacetime_module.wasm"
并发与清理规则:
- 同一个 Rust 构建 Job 建议使用
disableConcurrentBuilds(),避免同一组件的多个 release 构建同时写入同一最终产物路径。 - 如果 Linux agent 未安装
sccache,Rust 构建流水线必须自动取消RUSTC_WRAPPER,回退到直接使用rustc,不能因为缺少可选缓存工具阻断真实构建。 - 生产发布流水线只能消费
build/<version>/或 Jenkins 归档产物,不允许从共享cargo-target目录直接发布。 SCCACHE_CACHE_SIZE必须设置上限,避免编译缓存无限增长。- 对
/var/cache/genarrative-build/*/cargo-target或数据盘对应目录配置定期清理,建议保留最近 14 到 30 天。 - Jenkins Job 必须配置构建记录和归档产物保留策略,避免历史 release 包长期堆积。
统一源码版本参数
所有构建流水线、发布流水线和 Genarrative-Full-Build-And-Deploy 都必须支持以下参数:
SOURCE_BRANCH:源码分支,默认master,代表origin/master最新提交。COMMIT_HASH:可选 Git commit hash,留空时使用origin/<SOURCE_BRANCH>最新 commit;填写时必须是 7 到 40 位十六进制 hash,并且该 commit 必须属于origin/<SOURCE_BRANCH>。DEPLOY_TARGET:逻辑部署目标选择参数。发布流水线和Genarrative-Full-Build-And-Deploy必填;构建流水线仅在PUBLISH_AFTER_BUILD=true时用于触发下游发布。
执行规则:
- 流水线先按 Jenkins SCM 配置 checkout 仓库,再执行
git fetch --tags --prune origin "+refs/heads/<SOURCE_BRANCH>:refs/remotes/origin/<SOURCE_BRANCH>"。 - 如果工作区是浅克隆,流水线必须尝试
git fetch --unshallow --tags,确保能验证目标 commit 与分支关系。 COMMIT_HASH为空时,detached checkout 到refs/remotes/origin/<SOURCE_BRANCH>当前最新 commit。COMMIT_HASH非空时,先解析到完整 commit,再用git merge-base --is-ancestor <commit> refs/remotes/origin/<SOURCE_BRANCH>校验该提交属于指定分支,校验通过后 detached checkout。- 流水线日志必须输出最终
SOURCE_BRANCH与实际SOURCE_COMMIT。 - 构建产物必须写入
release-manifest.json,至少包含version、source_branch、source_commit、built_at和组件类型,供发布、回滚和审计使用。
构建流水线使用上述参数决定实际构建源码。发布流水线也暴露同名参数,但只用于选择本次发布使用的部署脚本、配置模板和 smoke test 逻辑;被发布的应用文件仍必须来自 Jenkins 归档产物或指定 release 包,不允许在发布流水线中重新构建。
当构建流水线以 PUBLISH_AFTER_BUILD=true 触发下游发布流水线时,必须把 SOURCE_BRANCH 和实际解析出的 SOURCE_COMMIT 作为下游 COMMIT_HASH 传递,确保部署逻辑和刚生成的产物可追溯到同一源码版本。
构建流水线支持参数 PUBLISH_AFTER_BUILD:
false:只构建并归档产物。true:构建成功后触发对应发布流水线。
发布流水线必须从归档产物获取文件,不依赖构建 workspace 的本地状态。
流水线职责
Genarrative-Server-Provision
用于生产服务器一次性或低频配置:
- 创建
spacetimedb、genarrative等系统用户。 - 创建
/stdb、/opt/genarrative、/srv/genarrative、/etc/genarrative、/var/lib/genarrative/maintenance。 - 安装或更新 SpacetimeDB。
- 安装 systemd unit。
- 可选安装 Nginx 配置和维护模式 snippet。
- 安装 Nginx 配置时执行
nginx -t,通过后必须执行nginx -s reload,确保新配置对当前 Nginx master/worker 生效。 - 启用并启动
spacetimedb.service与genarrative-api.service。
该流水线属于高风险操作,默认要求人工确认后执行。
已落地的 Jenkinsfile 为 jenkins/Jenkinsfile.production-server-provision。该流水线默认 DRY_RUN=true,只打印将执行的初始化动作;真正写入系统用户、目录、systemd、环境文件并启动服务时,必须设置 DRY_RUN=false 且勾选 CONFIRM_PROVISION。当 DEPLOY_TARGET=release 时,还必须勾选 CONFIRM_RELEASE_DEPLOY_AGENT,并通过 linux && genarrative-release-deploy 调度到独立 release 部署 agent。
首次真实初始化默认保持 NGINX_CONFIG_MODE=none,先完成系统用户、目录、SpacetimeDB、systemd unit 与 /etc/genarrative/api-server.env 落盘。开发服没有域名时,使用 DEPLOY_TARGET=development + NGINX_CONFIG_MODE=development-http 安装 deploy/nginx/genarrative-dev-http.conf,并把 SERVER_NAME 填为开发机 IP 或临时主机名。等正式域名确定,并且目标机已经存在 /etc/letsencrypt/live/<SERVER_NAME>/fullchain.pem 与 /etc/letsencrypt/live/<SERVER_NAME>/privkey.pem 后,再把 SERVER_NAME 改成真实域名,并设置 NGINX_CONFIG_MODE=production-https 安装 Nginx HTTPS 配置。流水线会拒绝 release 目标安装 development-http,也会拒绝用占位域名或缺失证书安装 production-https。Nginx 配置写入后必须先 nginx -t,再 nginx -s reload,不能只验证配置而不重载当前进程。
若误用占位域名执行过真实初始化,失败通常发生在 nginx -t,错误表现为找不到 /etc/letsencrypt/live/genarrative.example.com/fullchain.pem 或 privkey.pem。新版初始化在 NGINX_CONFIG_MODE=none 时会检测并禁用上一轮留下的占位域名 Nginx 配置,避免它继续影响后续 nginx -t。
Web Build / Deploy
构建:
- 先按
SOURCE_BRANCH/COMMIT_HASH解析并 checkout 目标源码,默认构建origin/master最新 commit。 - 默认执行
npm ci安装前端依赖,确保 Jenkins 新 workspace 中存在vite等构建工具。 - 构建主站静态文件。
- 构建后台前端,base path 为
/admin/。 - 生成或复制
maintenance.html。 - 将
web/打包为web.tar.gz,生成web.tar.gz.sha256。 - 归档
web.tar.gz、web.tar.gz.sha256和release-manifest.json;web/展开目录不作为 Jenkins 主归档对象。
发布:
- 先按
SOURCE_BRANCH/COMMIT_HASH解析并 checkout 部署脚本源码,默认使用origin/master最新 commit;上游构建触发时使用上游传入的实际构建 commit。 - 获取
web.tar.gz与web.tar.gz.sha256,先校验 checksum,再解压到/opt/genarrative/releases/<version>/web。 - 更新
/opt/genarrative/current与/srv/genarrative/web指向。 - 执行 Nginx 配置测试和静态页面 smoke test。
- 不进入维护模式。
Api Build / Deploy
构建:
- 先按
SOURCE_BRANCH/COMMIT_HASH解析并 checkout 目标源码,默认构建origin/master最新 commit。 - 编译 Rust
api-server。 - 归档单一可执行文件、必要运行说明和
release-manifest.json。
发布:
- 先按
SOURCE_BRANCH/COMMIT_HASH解析并 checkout 部署脚本源码,默认使用origin/master最新 commit;上游构建触发时使用上游传入的实际构建 commit。 - 进入维护模式。
- 解包到
/opt/genarrative/releases/<version>/api-server。 - 更新
/opt/genarrative/current。 - 重启
genarrative-api.service。 - 检查本机
/healthz。 - 导出产物归档成功后解除维护模式。
- 失败时保留维护模式并发邮件。
Stdb Module Build / Publish
构建:
- 先按
SOURCE_BRANCH/COMMIT_HASH解析并 checkout 目标源码,默认构建origin/master最新 commit。 - 使用
spacetime build构建spacetime_module.wasm。 - 归档 wasm、发布脚本和
release-manifest.json。
发布:
- 先按
SOURCE_BRANCH/COMMIT_HASH解析并 checkout 发布脚本源码,默认使用origin/master最新 commit;上游构建触发时使用上游传入的实际构建 commit。 - 进入维护模式。
- 将 wasm 上传到生产实例。
- 在生产实例本机执行
spacetime publish -s local --bin-path spacetime_module.wasm <database-name>。 - 成功后执行必要 smoke test。
- 成功后解除维护模式。
- 失败时保留维护模式并发邮件。
Full Build-And-Deploy
- 先解析一次最终
SOURCE_COMMIT,所有下游构建和发布都使用同一个分支与 commit。 - 并行执行 Web / API / Stdb 三条构建流水线。
- 并行构建阶段必须开启 fail-fast:任一构建流水线失败时,立即中断其他仍在执行的并行构建分支,本次全量编排不再继续进入发布阶段。
- 构建全部成功后,按顺序执行 Stdb publish、API deploy、Web deploy,并把同一个
DEPLOY_TARGET透传给三条发布流水线。 - 每条下游构建都只消费自己的归档产物,不直接复用别的 workspace。
- 生产 Web 发布只处理
web.tar.gz与 checksum,API 发布只处理api-server与 checksum,Stdb 发布只处理spacetime_module.wasm与 checksum。
数据库导出与导入
导出
Genarrative-Database-Export 用于人工导出生产数据:
- 已落地 Jenkinsfile:
jenkins/Jenkinsfile.production-database-export。 - 通过
DEPLOY_TARGET选择逻辑导出目标;development映射到linux && genarrative-build,release映射到linux && genarrative-release-deploy。 release导出必须勾选CONFIRM_RELEASE_DEPLOY_AGENT,避免当前开发/构建/开发部署 agent 冒充 release 部署机。- 进入维护模式,避免导出期间继续写入。
- 从目标机器本机 SpacetimeDB 导出指定数据库数据,默认连接
SPACETIME_SERVER=local,自托管root-dir默认/stdb。 - 产物归档到 Jenkins,并可额外保存到
SERVER_BACKUP_DIRECTORY。 - 敏感 token 与 bootstrap secret 只通过 Jenkins Secret Text 凭据 ID 注入,不作为明文 Job 参数。
- 成功后解除维护模式。
- 失败时保留维护模式并邮件通知。
导入
Genarrative-Database-Import 用于人工导入或恢复数据:
- 已落地 Jenkinsfile:
jenkins/Jenkinsfile.production-database-import。 - 通过
DEPLOY_TARGET选择逻辑导入目标;development映射到linux && genarrative-build,release映射到linux && genarrative-release-deploy。 release导入必须勾选CONFIRM_RELEASE_DEPLOY_AGENT,避免当前开发/构建/开发部署 agent 冒充 release 部署机。- 通过
INPUT_SOURCE选择数据源,pipeline_archive从Genarrative-Database-Export归档复制INPUT_FILE,manual_upload使用本次构建上传的MANUAL_INPUT_FILE;两种方式互斥,Prepare阶段会直接拦截混填。 DRY_RUN默认开启;真正写入数据时必须勾选CONFIRM_IMPORT,并让CONFIRM_DATABASE与CONFIRM_INPUT_FILE分别完全匹配DATABASE与实际输入文件。REPLACE_EXISTING=true且DRY_RUN=false时必须额外勾选CONFIRM_REPLACE_EXISTING。- 进入维护模式。
- 导入前先生成一次安全备份。
- 执行导入。
- 执行数据校验和服务 smoke test。
- 成功后解除维护模式。
- 失败时保留维护模式并邮件通知。
pipeline_archive模式默认使用导出流水线Genarrative-Database-Export;只需要填写EXPORT_BUILD_NUMBER_TO_IMPORT时,归档输入路径自动解析为database-exports/spacetime-migration-<导出构建号>.json。如果导出时覆盖过WORKSPACE_EXPORT_DIRECTORY或EXPORT_NAME,再显式填写归档内相对路径INPUT_FILE。manual_upload模式需要上传MANUAL_INPUT_FILE,并在CONFIRM_INPUT_FILE中填写原始文件名;此模式不再填写EXPORT_BUILD_NUMBER_TO_IMPORT或INPUT_FILE,EXPORT_JOB_NAME的默认值会被忽略。- 敏感 token 与 bootstrap secret 只通过 Jenkins Secret Text 凭据 ID 注入,不作为明文 Job 参数。
数据库表结构变更必须同步检查并更新 migration.rs,不能只发布 wasm。
全量构建并发布
Genarrative-Full-Build-And-Deploy 编排:
- 按
SOURCE_BRANCH/COMMIT_HASH解析一次最终SOURCE_COMMIT,默认origin/master最新 commit。 - 并行触发
Genarrative-Web-Build、Genarrative-Api-Build、Genarrative-Stdb-Module-Build,三条构建都必须使用同一个SOURCE_BRANCH和SOURCE_COMMIT;并行阶段开启 fail-fast,任一构建失败就中断其他仍在执行的构建分支。 - 三条构建全部成功后,按顺序触发
Genarrative-Stdb-Module-Publish、Genarrative-Api-Deploy、Genarrative-Web-Deploy,同样继续透传同一个SOURCE_BRANCH、SOURCE_COMMIT和DEPLOY_TARGET。 - 最后执行生产 smoke test。
网站最后发布,避免后台或主站提前指向尚未完成发布的后端能力。
这种编排能够在不牺牲版本一致性的前提下缩短总耗时,同时避免 Web / API / Stdb 彼此等待构建资源。并行构建时仍要保证每条构建流水线独立归档自己的产物,发布阶段只能消费归档结果,不能回到构建 workspace 重新取文件。
Genarrative-Full-Build-And-Deploy 额外作为定时任务运行,Jenkins 在每天凌晨 4 点自动触发一次,默认按 SOURCE_BRANCH=master 解析最新提交,并以 DEPLOY_TARGET=development 把全量构建结果部署到开发机。该定时任务不改写手动触发参数语义,只是给主线全量发布补一个固定的夜间全量刷新入口。
回滚
- 网站回滚:将
/srv/genarrative/web或/opt/genarrative/current切回上一版本并 reload Nginx。 api-server回滚:将/opt/genarrative/current切回上一版本并重启genarrative-api.service。- SpacetimeDB 模块回滚:发布上一版本
spacetime_module.wasm。 - 数据回滚:使用导入流水线恢复指定备份,必须进入维护模式。
待落地文件
后续工程落地时需要新增或改造:
deploy/systemd/spacetimedb.servicedeploy/systemd/genarrative-api.servicedeploy/nginx/genarrative.confdeploy/nginx/genarrative-dev-http.confdeploy/nginx/snippets/genarrative-maintenance.confdeploy/env/api-server.env.examplescripts/deploy/maintenance-on.shscripts/deploy/maintenance-off.shscripts/deploy/maintenance-status.shscripts/build-production-release.shscripts/jenkins-checkout-source.shscripts/deploy/production-web-deploy.shscripts/deploy/production-api-deploy.shscripts/deploy/production-stdb-publish.shjenkins/Jenkinsfile.production-web-buildjenkins/Jenkinsfile.production-web-deployjenkins/Jenkinsfile.production-api-buildjenkins/Jenkinsfile.production-api-deployjenkins/Jenkinsfile.production-stdb-module-buildjenkins/Jenkinsfile.production-stdb-module-publishjenkins/Jenkinsfile.production-full-build-and-deployjenkins/Jenkinsfile.production-server-provision- 删除旧 Jenkinsfile:
Jenkinsfile.build-and-deploy、Jenkinsfile.deploy、Jenkinsfile.database-export、Jenkinsfile.database-import - 更新旧部署文档,标记旧一体化脚本为废弃或迁移对象。
jenkins/Jenkinsfile.production-database-exportjenkins/Jenkinsfile.production-database-importjenkins/Jenkinsfile.production-notify-email- 所有生产流水线通过
NOTIFICATION_EMAILS参数在结束时触发Genarrative-Notify-Email
参考
- SpacetimeDB 官方自托管文档:https://spacetimedb.com/docs/how-to/deploy/self-hosting/
- 该文档建议 Ubuntu 24.04、
spacetimedb专用用户、/stdbroot-dir、systemd 托管,以及 Nginx/HTTPS。 - 默认公网路由只开放 TypeScript SDK 必需的
/v1/identity与/v1/database/<database>/subscribe,其他发布、SQL、管理类入口保持本机可用。
- 该文档建议 Ubuntu 24.04、