Prune stale docs and update .hermes content
Delete a large set of outdated documentation (many files under docs/ and .hermes/plans/, including audits, design, prd, technical, planning, assets, and todos). Update and consolidate .hermes content: refresh shared-memory pages (decision-log, development-workflow, document-map, pitfalls, project-overview, team-conventions) and several skills/references under .hermes/skills. Also modify AGENTS.md, README.md, UI_CODING_STANDARD.md, docs/README.md and .encoding-check-ignore. Purpose: clean up stale planning/audit material and keep current hermes documentation and related top-level docs in sync.
This commit is contained in:
@@ -1,647 +0,0 @@
|
||||
# 生产部署计划
|
||||
|
||||
更新时间:2026-05-02
|
||||
|
||||
## 当前落地进度
|
||||
|
||||
已落地生产基础设施骨架与首批生产 Jenkinsfile:
|
||||
|
||||
- `deploy/systemd/spacetimedb.service`
|
||||
- `deploy/systemd/genarrative-api.service`
|
||||
- `deploy/nginx/genarrative.conf`
|
||||
- `deploy/nginx/genarrative-dev-http.conf`
|
||||
- `deploy/nginx/snippets/genarrative-maintenance.conf`
|
||||
- `deploy/env/api-server.env.example`
|
||||
- `scripts/deploy/maintenance-on.sh`
|
||||
- `scripts/deploy/maintenance-off.sh`
|
||||
- `scripts/deploy/maintenance-status.sh`
|
||||
- `scripts/build-production-release.sh`
|
||||
- `scripts/jenkins-checkout-source.sh`
|
||||
- `scripts/jenkins-server-provision.sh`
|
||||
- `scripts/deploy/production-web-deploy.sh`
|
||||
- `scripts/deploy/production-api-deploy.sh`
|
||||
- `scripts/deploy/production-stdb-publish.sh`
|
||||
- `jenkins/Jenkinsfile.production-web-build`
|
||||
- `jenkins/Jenkinsfile.production-web-deploy`
|
||||
- `jenkins/Jenkinsfile.production-api-build`
|
||||
- `jenkins/Jenkinsfile.production-api-deploy`
|
||||
- `jenkins/Jenkinsfile.production-stdb-module-build`
|
||||
- `jenkins/Jenkinsfile.production-stdb-module-publish`
|
||||
- `jenkins/Jenkinsfile.production-full-build-and-deploy`
|
||||
- `jenkins/Jenkinsfile.production-server-provision`
|
||||
- `jenkins/Jenkinsfile.production-database-export`
|
||||
- `jenkins/Jenkinsfile.production-database-import`
|
||||
- `jenkins/Jenkinsfile.production-notify-email`
|
||||
- `npm 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/` 反向代理,以及临时兼容期的主站 `/api/*` 反向代理。
|
||||
- SpacetimeDB 作为系统服务运行,监听 `127.0.0.1:3101`,数据根目录为 `/stdb`。`3000` 保留给部署机本机 Git/Web 服务,禁止再让 SpacetimeDB 占用该端口。
|
||||
- Rust `api-server` 作为系统服务运行,监听 `127.0.0.1:8082`。当前临时兼容期仍允许 Nginx 将主站 `/api/*` 与 `/admin/api/` 反向代理到该服务;前端完成 SpacetimeDB SDK / bindings 直连迁移后,再删除公网 `/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:3101`
|
||||
- 对外暴露:默认不直接暴露公网端口。
|
||||
|
||||
该方案与 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`。
|
||||
|
||||
全量发布流水线的 `DATABASE` 参数必须同时传给 Stdb 发布和 API 发布:Stdb 发布负责把 wasm 发布到目标数据库,API 发布必须在重启 `genarrative-api.service` 前把同一个库名写入 `/etc/genarrative/api-server.env` 的 `GENARRATIVE_SPACETIME_DATABASE`,并同步 `GENARRATIVE_SPACETIME_SERVER_URL`。否则 api-server 会继续读取环境文件中的旧库名,出现 wasm 已发布到新库但 HTTP facade 仍访问旧库的错位。
|
||||
|
||||
API 发布阶段只使用上游 API 构建产物,不应回退到上游源码 commit 执行部署脚本;部署脚本应始终取 `SOURCE_BRANCH` 最新提交。否则全量流水线在修复部署脚本后仍可能按旧 `COMMIT_HASH` checkout,继续执行不认识新参数的旧版 `production-api-deploy.sh`。
|
||||
|
||||
### 服务器配置流水线
|
||||
|
||||
`Genarrative-Server-Provision` 的 Jenkinsfile 只负责参数、节点路由与调用脚本;服务器配置主体逻辑放在 `scripts/jenkins-server-provision.sh`。不要再把数百行 Bash 内联进 Jenkins `sh ''' ... '''` 或 `bash -lc '...'`,否则 Jenkins/Groovy/sh/bash 多层转义会把 `\"`、`${...}`、sed 表达式等内容二次改写,容易在运行时出现 `syntax error near unexpected token '}'` 这类难定位错误。
|
||||
|
||||
该脚本负责安装构建依赖、同步 SpacetimeDB current 目录、安装 systemd/Nginx 配置、创建或保留 `/etc/genarrative/api-server.env`、维护模式配置以及首次服务启动前的 SpacetimeDB client token 初始化。修改后应至少执行:
|
||||
|
||||
```bash
|
||||
bash -n scripts/jenkins-server-provision.sh
|
||||
git diff --check
|
||||
```
|
||||
|
||||
## Nginx 规则
|
||||
|
||||
生产正式入口只保留必要路由:
|
||||
|
||||
- `/`:主站静态页面。
|
||||
- `/admin/`:后台前端静态页面,后台构建时使用 `/admin/` 作为 base path。
|
||||
- `/admin/api/`:反向代理到 `http://127.0.0.1:8082/admin/api/`。
|
||||
- `/api` 与 `/api/*`:临时反向代理到 `http://127.0.0.1:8082`,保留原始请求 URI,用于兼容当前主站前端仍在使用的 HTTP facade。
|
||||
- HTTP 到 HTTPS:`production-https` 模式只保留 301 重定向。
|
||||
- `/maintenance.html`:维护中页面。
|
||||
|
||||
当前仍需移除这些公网反向代理:
|
||||
|
||||
- `/generated-*`
|
||||
- 公网 `/healthz`
|
||||
- 其他旧的一体化 web server 代理入口。
|
||||
|
||||
`/api/*` 仅作为临时兼容债务保留,不代表生产长期 API 暴露策略。后续主站前端完成 SpacetimeDB SDK / bindings 接入后,必须从 Nginx 模板中删除 `/api` 与 `/api/*` 反代,并恢复其公网 404 行为。
|
||||
|
||||
SpacetimeDB 公网路由默认保持收敛,只按实际前端 SDK 需要暴露最小集合。禁止开放可远程发布数据库或管理实例的通用入口。
|
||||
|
||||
Nginx 配置文件分为两类:
|
||||
|
||||
- `deploy/nginx/genarrative.conf`:生产正式域名 HTTPS 配置,`genarrative.example.com` 只是占位域名,安装时必须替换为真实 `SERVER_NAME`,并要求 `/etc/letsencrypt/live/<SERVER_NAME>/fullchain.pem` 与 `privkey.pem` 已存在。`SERVER_NAME` 只填证书主目录名对应的单个域名;`www` 等额外域名通过 `SERVER_ALIASES` 写入 Nginx `server_name`,不参与证书目录拼接。
|
||||
- `deploy/nginx/genarrative-dev-http.conf`:开发服无域名时的 HTTP-only 配置,只允许 `DEPLOY_TARGET=development` 使用。没有域名时,`SERVER_NAME` 填开发机 IP 或临时主机名;如有多个入口,额外域名或 IP 填 `SERVER_ALIASES`。它仍复用同一套静态目录、后台 API 反代、临时主站 `/api/*` 反代和 SpacetimeDB SDK 最小公网路由,不恢复旧 `/generated-*` 或公网 `/healthz`。
|
||||
|
||||
## 维护模式
|
||||
|
||||
维护模式由 `/var/lib/genarrative/maintenance/enabled` 控制:
|
||||
|
||||
- 文件存在:进入维护模式。
|
||||
- 文件不存在:退出维护模式。
|
||||
|
||||
行为:
|
||||
|
||||
- 网站静态资源发布不进入维护模式。
|
||||
- `api-server` 发布、SpacetimeDB 模块发布、数据库导入、服务器配置变更必须进入维护模式。
|
||||
- 普通页面在维护模式下展示 `/maintenance.html`。
|
||||
- `/admin/api/*` 在维护模式下返回 503。
|
||||
- `/v1/database/<database>/subscribe` 与 `/v1/identity` 在维护模式下返回 503,阻断已打开前端继续通过 SpacetimeDB SDK 访问运行时数据。
|
||||
- 静态资源仍允许访问,避免维护页样式和资源加载失败。
|
||||
- 发布成功后自动解除维护模式。
|
||||
- 发布失败时保持维护模式,并通过邮件通知人工处理。
|
||||
|
||||
## 构建产物
|
||||
|
||||
生产发布包构建入口:
|
||||
|
||||
```bash
|
||||
npm run build:production-release -- --name <version>
|
||||
```
|
||||
|
||||
每次构建产物按版本号归档:
|
||||
|
||||
```text
|
||||
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 与人工排查使用。Web Build 必须生成 `web.tar.gz` 与 `web.tar.gz.sha256`,但 `web.tar.gz` 不作为 Jenkins controller 默认归档对象,避免每次把约数百 MB 的 Web 大包从 Linux agent 拉回本地 controller。Web 大包保存在构建机稳定目录 `/var/cache/genarrative-build/web-artifacts/<job>/<build>/<version>/`,Jenkins 只归档 `web.tar.gz.sha256`、`release-manifest.json` 与 `web-artifact-pointer.txt`。`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 使用脱敏节点名 `genarrative-build-01`,必须同时配置 `linux` 与 `genarrative-build` 两个标签;非 Linux 节点不能承担构建或部署。
|
||||
- 构建机 agent 启动方式统一改为 inbound agent + systemd 自守护,不再依赖 Jenkins controller 通过 SSH launcher 长期拉起。SSH 只作为首次登录和安装 systemd 服务的运维通道。
|
||||
- 用途:拉代码、安装依赖、构建主站、构建后台、构建 `api-server`、构建 SpacetimeDB wasm、归档产物,并执行 `DEPLOY_TARGET=development` 的开发环境部署。
|
||||
|
||||
### 生产/发布实例
|
||||
|
||||
- Jenkins Job 参数不暴露真实节点名、IP 或带 IP 的标签。
|
||||
- 生产机已作为独立 Linux Jenkins agent 接入,节点名使用脱敏名称 `genarrative-release-deploy-01`,调度标签只使用 `linux` 与 `genarrative-release-deploy`。
|
||||
- 生产机 agent 启动方式统一改为 inbound agent + systemd 自守护,不再依赖 Jenkins controller 通过 SSH launcher 长期拉起。SSH 只作为首次登录和安装 systemd 服务的运维通道。
|
||||
- 生产机真实连接地址只允许保存在 Jenkins 节点连接配置或人工运维 SSH 配置中,不能写入节点名、调度标签、Job 参数默认值或文档推荐命令。
|
||||
- 发布 Job 通过 `DEPLOY_TARGET` 选择逻辑部署目标,再在 Jenkinsfile 内部映射到 Linux-only 脱敏调度表达式:`development -> linux && genarrative-build`,`release -> linux && genarrative-release-deploy`。
|
||||
- 用途:服务器配置、发布静态网站、发布 `api-server`、发布 SpacetimeDB 模块、数据库导入导出、维护模式切换。
|
||||
|
||||
### Jenkins inbound agent 自恢复
|
||||
|
||||
构建 agent 与发布 agent 都必须由目标 Linux 机器主动连接 Jenkins controller,并由 systemd 托管:
|
||||
|
||||
- Jenkins 节点 Launch method 使用 inbound agent,优先启用 WebSocket。这样目标机只需要能访问 Jenkins Web 地址,不依赖 controller 每次 SSH 拉起 agent。
|
||||
- 目标机安装 `deploy/systemd/jenkins-agent@.service`、`scripts/deploy/jenkins-inbound-agent-start.sh` 与 `scripts/deploy/install-jenkins-inbound-agent.sh`。
|
||||
- systemd 服务名采用 `jenkins-agent@<node-name>.service`,例如 `jenkins-agent@genarrative-build-01.service`、`jenkins-agent@genarrative-release-deploy-01.service`。
|
||||
- systemd 自身 `WorkingDirectory` 保持 `/var/lib/jenkins/agent/<node-name>`;Jenkins remoting `-workDir` 可按节点拆分,例如构建机使用 `/root/jenkins-agent-build`、发布机继续使用旧 SSH agent 的 `/root/jenkins-agent`,避免多 agent 共用 remoting 根目录,同时减少发布机迁移时 workspace 和缓存路径漂移。
|
||||
- inbound secret 只能放在目标机 `/etc/jenkins-agent/<node-name>.secret` 或等价 Secret Text 注入位置,不能提交到 Git,也不能写入 Jenkinsfile 默认参数。
|
||||
- systemd unit 使用 `Restart=always` 和 `RestartSec=10`;agent Java 进程退出、网络短断或机器重启后由 systemd 自动恢复,不需要人工盯着 Jenkins 页面手动重启。
|
||||
- 当前 `Genarrative-Server-Provision` 仍负责 systemd、Nginx、`/opt/genarrative`、`/etc/genarrative` 等特权写入,因此 inbound agent 默认仍按现有 root 执行口径迁移。若后续改为 `jenkins` 用户运行 agent,必须先把生产流水线需要的特权命令收敛为精确 `NOPASSWD` sudoers 白名单。
|
||||
|
||||
如果 Jenkins controller 只运行在本地 Windows,不直接对目标机暴露公网地址,需要在本地控制机启动 `scripts/deploy/jenkins-agent-reverse-tunnel.ps1`。该脚本通过同一条 SSH 会话把远端 `127.0.0.1:18080` 转到本地 Jenkins Web `127.0.0.1:8080`,把远端 `127.0.0.1:50000` 转到本地 Jenkins inbound TCP agent port `127.0.0.1:50000`,并在隧道断开后自动重试。此时远端 agent 的 `JENKINS_URL` 固定写 `http://127.0.0.1:18080/`,不写本地 Windows 的 `127.0.0.1:8080`。
|
||||
|
||||
本地反向隧道脚本不内置目标机地址;注册 Windows 计划任务时必须显式传入 `-RemoteHost <agent-host>`,真实 IP 或主机名只保存在本地计划任务配置中,不提交到 Git。同一台 Linux 机器上同时运行构建与发布 agent 时,两者共用这一条反向隧道,不为每个 Jenkins 节点重复注册本地隧道任务。
|
||||
|
||||
当 Jenkins controller 以本地 Windows `java -jar jenkins.war` 方式运行时,使用 `scripts/deploy/jenkins-local-controller-watchdog.ps1` 作为本地守护脚本。该脚本只保存本机 Java、`jenkins.war`、`JENKINS_HOME` 和端口路径,不保存 Jenkins 账号、密码、token 或 agent secret;注册 Windows 计划任务后,脚本会在登录后检查 `8080` 是否已有 Jenkins 监听,若已有则监控现有 PID,若进程退出或端口空闲则重新启动 Jenkins,并固定 `--agentPort=50000` 供远端 inbound agent 连接。
|
||||
|
||||
首次迁移示例:
|
||||
|
||||
```bash
|
||||
sudo install -m 0600 /tmp/genarrative-build-01.secret /etc/jenkins-agent/genarrative-build-01.secret
|
||||
sudo scripts/deploy/install-jenkins-inbound-agent.sh \
|
||||
--agent-name genarrative-build-01 \
|
||||
--jenkins-url http://127.0.0.1:18080/ \
|
||||
--secret-file /etc/jenkins-agent/genarrative-build-01.secret \
|
||||
--workdir /root/jenkins-agent-build \
|
||||
--java-bin /usr/bin/java
|
||||
sudo systemctl status jenkins-agent@genarrative-build-01.service --no-pager -l
|
||||
|
||||
sudo install -m 0600 /tmp/genarrative-release-deploy-01.secret /etc/jenkins-agent/genarrative-release-deploy-01.secret
|
||||
sudo scripts/deploy/install-jenkins-inbound-agent.sh \
|
||||
--agent-name genarrative-release-deploy-01 \
|
||||
--jenkins-url http://127.0.0.1:18080/ \
|
||||
--secret-file /etc/jenkins-agent/genarrative-release-deploy-01.secret \
|
||||
--workdir /root/jenkins-agent \
|
||||
--java-bin /usr/bin/java
|
||||
sudo systemctl status jenkins-agent@genarrative-release-deploy-01.service --no-pager -l
|
||||
journalctl -u 'jenkins-agent@*.service' -f
|
||||
```
|
||||
|
||||
如果 Jenkins controller 暂时仍配置为 SSH launcher,只能作为过渡方案使用:需要把 SSH launch timeout 拉长、增加 retry 和 retry wait、固定 Java 路径,并确认 `ssh user@host 'java -version'` 稳定返回。最终仍要切到 inbound + systemd,避免 SSH 连接卡住时阻塞发布队列。
|
||||
|
||||
### Git 仓库访问
|
||||
|
||||
Jenkins controller 与 Linux agent 看到的 Git 服务地址不同,必须拆成两层配置:
|
||||
|
||||
- Jenkins Job 的 `Pipeline script from SCM` 由 controller 执行,SCM URL 使用 controller 可访问的公网域名:`https://git.genarrative.world/GenarrativeAI/Genarrative.git`。
|
||||
- Jenkinsfile 内部的源码、脚本 checkout 在 Linux agent 上执行,`GIT_REMOTE_URL` 优先使用 agent 本机可访问地址:`http://127.0.0.1:3000/GenarrativeAI/Genarrative.git`。
|
||||
- 若 `127.0.0.1` Git 服务在当前 Linux agent 上不可达,发布、数据库和服务器配置类 Jenkinsfile 会用 `GIT_REMOTE_FALLBACK_URL=https://git.genarrative.world/GenarrativeAI/Genarrative.git` 重新 checkout。该首次 checkout 只拉 `SOURCE_BRANCH` 单分支、`depth=1` 且不拉 tags,避免 release agent 通过公网备用地址拉取全仓库历史时被 Jenkins Git checkout timeout 杀掉;`scripts/jenkins-checkout-source.sh` 后续 fetch 也会按主地址、域名备用地址顺序重试,并在日志中输出最终使用的远端。
|
||||
- 构建类 Jenkinsfile 和 `Genarrative-Full-Build-And-Deploy` 的源码解析阶段即使只用域名 Git,也必须同样使用 `+refs/heads/<SOURCE_BRANCH>:refs/remotes/origin/<SOURCE_BRANCH>` 与 `CloneOption honorRefspec=true`。否则 Jenkins Git 插件会退回 `+refs/heads/*:refs/remotes/origin/*`,在 `genarrative-build-01` 这类公网 HTTPS 链路上可能以 `curl 56 GnuTLS recv error (-9)`、`early EOF`、`invalid index-pack output` 失败。
|
||||
- 这里的 `3000` 是 Git/Web 服务端口,不是 SpacetimeDB 端口;生产 SpacetimeDB 固定使用 `http://127.0.0.1:3101`,避免流水线部署时与本机 Git 服务抢端口。
|
||||
|
||||
因此生产 Jenkinsfile 不使用 `checkout scm` 作为构建源码入口,而是显式 `checkout([$class: 'GitSCM', userRemoteConfigs: [[url: remoteUrl, refspec: "+refs/heads/<SOURCE_BRANCH>:refs/remotes/origin/<SOURCE_BRANCH>"]], ...])`。首次 checkout 先尝试 `GIT_REMOTE_URL`,失败后尝试 `GIT_REMOTE_FALLBACK_URL`,两次都必须保持单分支浅克隆和 `noTags=true`;后续 `scripts/jenkins-checkout-source.sh` 会继续把 `origin` 设置为实际可用远端,并按 `SOURCE_BRANCH` / `COMMIT_HASH` 拉取和校验目标提交。
|
||||
|
||||
`127.0.0.1` 只代表当前执行该阶段的 Linux agent 自身;如果 release agent 与 Git 服务不在同一台机器,`GIT_REMOTE_FALLBACK_URL` 统一使用 `https://git.genarrative.world/GenarrativeAI/Genarrative.git`,不要再配置内网 IP 备用地址。
|
||||
|
||||
### 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 目标流水线:
|
||||
|
||||
1. `Genarrative-Server-Provision`
|
||||
2. `Genarrative-Web-Build`
|
||||
3. `Genarrative-Web-Deploy`
|
||||
4. `Genarrative-Api-Build`
|
||||
5. `Genarrative-Api-Deploy`
|
||||
6. `Genarrative-Stdb-Module-Build`
|
||||
7. `Genarrative-Stdb-Module-Publish`
|
||||
8. `Genarrative-Database-Export`
|
||||
9. `Genarrative-Database-Import`
|
||||
10. `Genarrative-Full-Build-And-Deploy`
|
||||
11. `Genarrative-Notify-Email`
|
||||
|
||||
已落地的生产流水线脚本文件:
|
||||
|
||||
- `jenkins/Jenkinsfile.production-web-build`
|
||||
- `jenkins/Jenkinsfile.production-web-deploy`
|
||||
- `jenkins/Jenkinsfile.production-api-build`
|
||||
- `jenkins/Jenkinsfile.production-api-deploy`
|
||||
- `jenkins/Jenkinsfile.production-stdb-module-build`
|
||||
- `jenkins/Jenkinsfile.production-stdb-module-publish`
|
||||
- `jenkins/Jenkinsfile.production-full-build-and-deploy`
|
||||
- `jenkins/Jenkinsfile.production-server-provision`
|
||||
- `jenkins/Jenkinsfile.production-database-export`
|
||||
- `jenkins/Jenkinsfile.production-database-import`
|
||||
- `jenkins/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 插件。Web 大包例外:`Genarrative-Web-Build` 只把轻量元数据归档到 Jenkins controller,`web.tar.gz` 保留在 Linux 构建机稳定目录 `/var/cache/genarrative-build/web-artifacts/`,`Genarrative-Web-Deploy` 在部署目标机器上按构建 Job、构建号和版本号读取该目录。development 目标天然共享当前 Linux 开发/构建/开发部署机;release 目标若不是同一台机器,发布流水线默认在本地缓存缺少 `web.tar.gz` 时通过 `rsync` 从 SSH Host `genarrative-build-internal` 拉取同一路径内容。该 Host 必须配置在 release 服务器上 Jenkins 运行用户的 SSH config 中,真实内网 IP、用户和私钥路径只保存在服务器本机;如需改名或指定 config,可通过 `WEB_ARTIFACT_SYNC_HOST` / `WEB_ARTIFACT_SYNC_SSH_CONFIG` 参数覆盖。也可以提前通过共享存储或其它内网同步方式提供该目录。数据库导入流水线的手动上传模式使用 `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/`:
|
||||
|
||||
```bash
|
||||
mkdir -p /var/cache/genarrative-build/{api-server,stdb-module}
|
||||
chmod 700 /var/cache/genarrative-build
|
||||
```
|
||||
|
||||
API 构建流水线建议设置:
|
||||
|
||||
```groovy
|
||||
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 构建流水线建议设置:
|
||||
|
||||
```groovy
|
||||
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'
|
||||
}
|
||||
```
|
||||
|
||||
如使用数据盘,则把上述路径替换为:
|
||||
|
||||
```groovy
|
||||
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 证书;同时负责在缺失时通过 `cargo install sccache --locked` 补齐 `sccache`,让 API/Stdb 构建流水线的 `RUSTC_WRAPPER=sccache` 真正生效。API/Stdb 构建流水线在执行 Cargo 前必须检查 `clang` 与 `lld`,缺失时直接失败并提示先运行 Server-Provision。
|
||||
|
||||
`scripts/build-production-release.sh` 必须尊重 `CARGO_TARGET_DIR`,不能硬编码从 `server-rs/target/` 拷贝 Rust 产物。脚本中的产物路径应按以下口径计算:
|
||||
|
||||
```bash
|
||||
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/Windows agent 未安装 `sccache`,或 `sccache --version` 无法实际执行,应先补齐缓存工具;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 --no-tags --prune origin "+refs/heads/<SOURCE_BRANCH>:refs/remotes/origin/<SOURCE_BRANCH>"`;`COMMIT_HASH` 为空时追加 `--depth=1`。
|
||||
- 如果工作区是浅克隆,只有在 `COMMIT_HASH` 非空、需要验证指定提交属于目标分支时,流水线才尝试 `git fetch --unshallow --no-tags`。`COMMIT_HASH` 为空时只需要目标分支 HEAD,必须保持 `--depth=1 --no-tags`,避免普通发布或服务器配置任务拉取全仓库历史。
|
||||
- `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` 和组件类型,供发布、回滚和审计使用。
|
||||
- Windows 构建 Job 写入 `.jenkins-source-commit` 时必须使用 UTF-8 无 BOM;部署脚本在校验 `COMMIT_HASH` 前也会剥离 UTF-8 BOM 和 CRLF,避免上游 PowerShell 5.1 `Set-Content -Encoding UTF8` 产生的不可见 BOM 让下游发布误判 commit hash 非法。
|
||||
|
||||
构建流水线使用上述参数决定实际构建源码。发布流水线也暴露同名参数,但只用于选择本次发布使用的部署脚本、配置模板和 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。
|
||||
- 安装 SpacetimeDB 时不能只复制 `/usr/local/bin/spacetime` wrapper,还必须把 `spacetimedb-cli` 与 `spacetimedb-standalone` 同步到 `<SPACETIME_ROOT>/bin/current/`。否则 `spacetime --root-dir=<SPACETIME_ROOT> start` 会回调缺失的 `<SPACETIME_ROOT>/bin/current/spacetimedb-cli`,导致 `spacetimedb.service` 循环重启但 provision 表面已经执行过 `systemctl restart`。
|
||||
- 安装 systemd unit。
|
||||
- 可选安装 Nginx 配置和维护模式 snippet。
|
||||
- 安装 Nginx 配置时执行 `nginx -t`,通过后必须执行 `nginx -s reload`,确保新配置对当前 Nginx master/worker 生效。
|
||||
- 启用并启动 `spacetimedb.service` 与 `genarrative-api.service`;重启 `spacetimedb.service` 后必须等待 `http://127.0.0.1:3101/v1/ping` 确认就绪,不能只依赖 `systemctl restart` 的返回码。`<SPACETIME_ROOT>` 下所有运行态文件必须归属 `spacetimedb:spacetimedb`。不要在 root 身份下对同一个 `<SPACETIME_ROOT>` 执行 `spacetime --root-dir=<SPACETIME_ROOT> server ping`,否则会生成 root-owned CLI 配置,导致 `spacetimedb` 服务用户后续启动时遇到权限错误。
|
||||
- 首次初始化时,如果 `/etc/genarrative/api-server.env` 里还没有 `GENARRATIVE_SPACETIME_TOKEN`,流水线会在 `spacetimedb.service` 就绪后调用本机 `POST http://127.0.0.1:3101/v1/identity` 生成 client identity/token,只把 token 写入环境文件,并只在日志里显示 identity 前缀。随后流水线会以 `spacetimedb` 用户执行 `<SPACETIME_ROOT>/bin/current/spacetimedb-cli --root-dir <SPACETIME_ROOT> login --token [REDACTED]`,确保后续首次 `Stdb publish` 使用同一个 client identity 创建数据库;这个 identity 才会成为后台读取 private 表所需的 owner。若环境文件已有 `GENARRATIVE_SPACETIME_TOKEN`,初始化必须保留该值,只同步 CLI 登录态,不重新生成或覆盖。
|
||||
|
||||
该流水线属于高风险操作,默认要求人工确认后执行。
|
||||
已落地的 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 配置。如果同一张证书同时覆盖根域名和 `www` 域名,`SERVER_NAME` 仍只填证书目录名,例如 `genarrative.world`,`SERVER_ALIASES` 填 `www.genarrative.world`。流水线会拒绝 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` 复制到 `/var/cache/genarrative-build/web-artifacts/<job>/<build>/<version>/`;Jenkins 只归档 `web.tar.gz.sha256`、`release-manifest.json` 和 `web-artifact-pointer.txt`,不把 `web.tar.gz` 拉回 controller。
|
||||
|
||||
发布:
|
||||
|
||||
- 先按 `SOURCE_BRANCH` / `COMMIT_HASH` 解析并 checkout 部署脚本源码,默认使用 `origin/master` 最新 commit;上游构建触发时使用上游传入的实际构建 commit。
|
||||
- 通过 Jenkins 归档获取 `web.tar.gz.sha256`、`release-manifest.json` 和 `web-artifact-pointer.txt`,再从 `/var/cache/genarrative-build/web-artifacts/<job>/<build>/<version>/` 读取 `web.tar.gz`;先校验 checksum,再解压到 `/opt/genarrative/releases/<version>/web`。
|
||||
- 当 `DEPLOY_TARGET=release` 且 release 服务器本地缓存缺少 `web.tar.gz` 时,默认先执行 `rsync -av --progress <WEB_ARTIFACT_SYNC_HOST>:/var/cache/genarrative-build/web-artifacts/<job>/<build>/<version>/ /var/cache/genarrative-build/web-artifacts/<job>/<build>/<version>/`,再继续校验 checksum;默认 Host 为 `genarrative-build-internal`,由 release 服务器本机 SSH config 解析。
|
||||
- 更新 `/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_module.wasm` 前默认生成 32 字节随机 hex 迁移引导密钥,注入 `GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET`,并把同一份密钥写入 `build/<version>/migration-bootstrap-secret.txt`。构建日志只输出密钥来源和长度,不打印明文。
|
||||
- `Genarrative-Stdb-Module-Build` 提供 `MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID` 参数:留空时自动生成新密钥;填写 Jenkins Secret Text 凭据 ID 时,构建环境注入 `GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET` 并复用该值。仅在明确传 `--no-migration-bootstrap-secret` 时才构建不带引导密钥的 wasm。
|
||||
- 使用 Rust wasm target 构建 `spacetime_module.wasm`。
|
||||
- 归档 wasm、`migration-bootstrap-secret.txt` 和 `release-manifest.json`。`migration-bootstrap-secret.txt` 属于敏感产物,只用于创建首个迁移操作员或录入数据库导入/导出流水线的 `BOOTSTRAP_SECRET_CREDENTIAL_ID` 指向的 Jenkins Secret Text;授权完成后不要把明文留在公开归档或聊天记录中。
|
||||
|
||||
发布:
|
||||
|
||||
- 先按 `SOURCE_BRANCH` / `COMMIT_HASH` 解析并 checkout 发布脚本源码,默认使用 `origin/master` 最新 commit;上游构建触发时使用上游传入的实际构建 commit。
|
||||
- 进入维护模式。
|
||||
- 将 wasm 上传到生产实例。
|
||||
- 在生产实例本机执行 `spacetime --root-dir=/stdb publish <database-name> --server http://127.0.0.1:3101 --bin-path spacetime_module.wasm --yes --no-config`。
|
||||
- 发布动作默认以 `spacetimedb` 服务用户执行,避免 root 默认 CLI 身份对自托管数据库验签失败,也避免 root 写入 `/stdb/config` 造成后续服务用户启动权限错误。
|
||||
- `Stdb publish` 固定追加 `--no-config`,只依赖显式传入的 `--root-dir`、`--server`、`--bin-path` 与数据库名,避免 agent 工作区、本机用户目录或仓库内 `spacetime` 配置干扰发布目标。
|
||||
- 首次迁移操作员授权时,使用本次 Stdb module 构建归档的 `migration-bootstrap-secret.txt` 创建 Jenkins Secret Text,然后在 `Genarrative-Database-Export` / `Genarrative-Database-Import` 的 `BOOTSTRAP_SECRET_CREDENTIAL_ID` 中填写该凭据 ID。后续已有迁移操作员时优先改用 `TOKEN_CREDENTIAL_ID`。
|
||||
- 成功后执行必要 smoke test。
|
||||
- 成功后解除维护模式。
|
||||
- 失败时保留维护模式并发邮件。
|
||||
|
||||
### Full Build-And-Deploy
|
||||
|
||||
- 先解析一次最终 `SOURCE_COMMIT`,所有下游构建和发布都使用同一个分支与 commit。
|
||||
- 并行执行 Web / API / Stdb 三条构建流水线。
|
||||
- 并行构建阶段必须开启 fail-fast:任一构建流水线失败时,立即中断其他仍在执行的并行构建分支,本次全量编排不再继续进入发布阶段。
|
||||
- 构建全部成功后,按顺序执行 Stdb publish、API deploy、Web deploy,并把同一个 `DEPLOY_TARGET` 透传给三条发布流水线。
|
||||
- Stdb publish 同时透传 `SPACETIME_SERVER_URL`、`SPACETIME_ROOT_DIR` 与 `SPACETIME_RUN_AS_USER`,默认分别为 `http://127.0.0.1:3101`、`/stdb`、`spacetimedb`。
|
||||
- 每条下游构建都只消费自己的归档产物,不直接复用别的 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_URL=http://127.0.0.1:3101`,自托管 `root-dir` 默认 `/stdb`。
|
||||
- 产物归档到 Jenkins,并可额外保存到 `SERVER_BACKUP_DIRECTORY`。
|
||||
- 敏感 token 与 bootstrap secret 只通过 Jenkins Secret Text 凭据 ID 注入,不作为明文 Job 参数。
|
||||
- 导出和导入流水线的 Bash 执行块启用 `set -u`;所有可选 Jenkins 参数必须先通过 `${VAR:-}` 收敛成本地默认值,再传给 Node 迁移脚本,避免空参数没有导出时触发 `unbound variable`。
|
||||
- 成功后解除维护模式。
|
||||
- 失败时保留维护模式并邮件通知。
|
||||
|
||||
### 导入
|
||||
|
||||
`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` 编排:
|
||||
|
||||
1. 按 `SOURCE_BRANCH` / `COMMIT_HASH` 解析一次最终 `SOURCE_COMMIT`,默认 `origin/master` 最新 commit。
|
||||
2. 并行触发 `Genarrative-Web-Build`、`Genarrative-Api-Build`、`Genarrative-Stdb-Module-Build`,三条构建都必须使用同一个 `SOURCE_BRANCH` 和 `SOURCE_COMMIT`;并行阶段开启 fail-fast,任一构建失败就中断其他仍在执行的构建分支。
|
||||
3. 三条构建全部成功后,按顺序触发 `Genarrative-Stdb-Module-Publish`、`Genarrative-Api-Deploy`、`Genarrative-Web-Deploy`,同样继续透传同一个 `SOURCE_BRANCH`、`SOURCE_COMMIT` 和 `DEPLOY_TARGET`。
|
||||
4. 最后执行生产 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`。
|
||||
- 数据回滚:使用导入流水线恢复指定备份,必须进入维护模式。
|
||||
|
||||
## 待落地文件
|
||||
|
||||
后续工程落地时需要新增或改造:
|
||||
|
||||
- [x] `deploy/systemd/spacetimedb.service`
|
||||
- [x] `deploy/systemd/genarrative-api.service`
|
||||
- [x] `deploy/systemd/jenkins-agent@.service`
|
||||
- [x] `deploy/nginx/genarrative.conf`
|
||||
- [x] `deploy/nginx/genarrative-dev-http.conf`
|
||||
- [x] `deploy/nginx/snippets/genarrative-maintenance.conf`
|
||||
- [x] `deploy/env/api-server.env.example`
|
||||
- [x] `scripts/deploy/maintenance-on.sh`
|
||||
- [x] `scripts/deploy/maintenance-off.sh`
|
||||
- [x] `scripts/deploy/maintenance-status.sh`
|
||||
- [x] `scripts/deploy/jenkins-local-controller-watchdog.ps1`
|
||||
- [x] `scripts/deploy/jenkins-agent-reverse-tunnel.ps1`
|
||||
- [x] `scripts/deploy/jenkins-inbound-agent-start.sh`
|
||||
- [x] `scripts/deploy/install-jenkins-inbound-agent.sh`
|
||||
- [x] `scripts/build-production-release.sh`
|
||||
- [x] `scripts/jenkins-checkout-source.sh`
|
||||
- [x] `scripts/deploy/production-web-deploy.sh`
|
||||
- [x] `scripts/deploy/production-api-deploy.sh`
|
||||
- [x] `scripts/deploy/production-stdb-publish.sh`
|
||||
- [x] `jenkins/Jenkinsfile.production-web-build`
|
||||
- [x] `jenkins/Jenkinsfile.production-web-deploy`
|
||||
- [x] `jenkins/Jenkinsfile.production-api-build`
|
||||
- [x] `jenkins/Jenkinsfile.production-api-deploy`
|
||||
- [x] `jenkins/Jenkinsfile.production-stdb-module-build`
|
||||
- [x] `jenkins/Jenkinsfile.production-stdb-module-publish`
|
||||
- [x] `jenkins/Jenkinsfile.production-full-build-and-deploy`
|
||||
- [x] `jenkins/Jenkinsfile.production-server-provision`
|
||||
- [x] 删除旧 Jenkinsfile:`Jenkinsfile.build-and-deploy`、`Jenkinsfile.deploy`、`Jenkinsfile.database-export`、`Jenkinsfile.database-import`
|
||||
- [x] 更新旧部署文档,标记旧一体化脚本为废弃或迁移对象。
|
||||
- [x] `jenkins/Jenkinsfile.production-database-export`
|
||||
- [x] `jenkins/Jenkinsfile.production-database-import`
|
||||
- [x] `jenkins/Jenkinsfile.production-notify-email`
|
||||
- [x] 所有生产流水线通过 `NOTIFICATION_EMAILS` 参数在结束时触发 `Genarrative-Notify-Email`
|
||||
|
||||
## 参考
|
||||
|
||||
- SpacetimeDB 官方自托管文档:https://spacetimedb.com/docs/how-to/deploy/self-hosting/
|
||||
- 该文档建议 Ubuntu 24.04、`spacetimedb` 专用用户、`/stdb` root-dir、systemd 托管,以及 Nginx/HTTPS。
|
||||
- 默认公网路由只开放 TypeScript SDK 必需的 `/v1/identity` 与 `/v1/database/<database>/subscribe`,其他发布、SQL、管理类入口保持本机可用。
|
||||
Reference in New Issue
Block a user