Add deploy pipeline SpacetimeDB auto migration
Some checks failed
CI / verify (pull_request) Has been cancelled

This commit is contained in:
2026-04-29 18:52:09 +08:00
parent 37a03dc994
commit 4a0faf5f51
8 changed files with 304 additions and 30 deletions

View File

@@ -88,20 +88,24 @@ scripts/jenkins-deploy-release.sh \
--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. 只删除发布产物白名单中的旧文件,例如 `web/``api-server``spacetime_module.wasm``.env*``start.sh``stop.sh``web-server.mjs``README.md`
3. 将指定版本目录中的同名发布产物复制到部署目录;文件产物使用普通复制,`web/` 等目录产物必须递归复制
4. 如果 `CLEAR_DATABASE=true`,部署脚本会以 `./start.sh --clear-database` 启动新版本;这样发布阶段的 `spacetime publish` 会追加 `-c=on-conflict`
5. 执行新版本 `start.sh`
2. 覆盖前如果旧部署目录存在 `migration-bootstrap-secret.txt`,先复制到 `run/migration-bootstrap-secret.previous.txt`,供新版本 `start.sh` 在 schema 冲突自动迁移时授权导出旧库
3. 只删除发布产物白名单中的旧文件,例如 `web/``api-server``spacetime_module.wasm``migration-bootstrap-secret.txt``scripts/``.env*``start.sh``stop.sh``web-server.mjs``README.md`
4. 将指定版本目录中的同名发布产物复制到部署目录;文件产物使用普通复制,`web/``scripts/` 等目录产物必须递归复制
5. `WEB_PORT``MIGRATE_ON_CONFLICT``MIGRATION_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`第 1 步和第 4 步会改为 `sudo -n` 调用;这要求 Jenkins 运行用户提前配置免密 sudo否则部署会直接失败不会进入交互式密码提示。
如果 `RUN_DEPLOY_HOOKS_WITH_SUDO=true`旧版本 `stop.sh` 和新版本 `start.sh` 会改为 `sudo -n` 调用;这要求 Jenkins 运行用户提前配置免密 sudo否则部署会直接失败不会进入交互式密码提示。
这样可以满足“发布文件直接覆盖”的要求,同时保留部署目录里像 `.spacetimedb/``logs/``run/` 这类运行态目录,不会因为部署被整体删除。发布白名单内的 `.env``.env.local` 会先以构建产物中的文件为准;部署脚本会在启动 hook 前移除这些环境文件中的 UTF-8 BOM 与 CRLF并把 Jenkins 部署参数 `WEB_PORT` 写入 `.env.local``GENARRATIVE_WEB_PORT`,避免 `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 身份。
这样可以满足“发布文件直接覆盖”的要求,同时保留部署目录里像 `.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 构建并部署
@@ -125,6 +129,8 @@ jenkins/Jenkinsfile.build-and-deploy
- `DEPLOY_DIRECTORY`
- `WEB_PORT`
- `CLEAR_DATABASE`
- `MIGRATE_ON_CONFLICT`
- `MIGRATION_DIRECTORY`
- `EXPECTED_UPSTREAM_JOB`
## 5. Jenkins 参数建议
@@ -137,6 +143,8 @@ jenkins/Jenkinsfile.build-and-deploy
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/*` 的额外写权限。
@@ -148,9 +156,11 @@ jenkins/Jenkinsfile.build-and-deploy
2. `SOURCE_NODE_NAME`
3. `DEPLOY_DIRECTORY`
4. `CLEAR_DATABASE`
5. `RUN_DEPLOY_HOOKS_WITH_SUDO`
6. `EXPECTED_UPSTREAM_JOB`
7. `WEB_PORT`
5. `MIGRATE_ON_CONFLICT`
6. `MIGRATION_DIRECTORY`
7. `RUN_DEPLOY_HOOKS_WITH_SUDO`
8. `EXPECTED_UPSTREAM_JOB`
9. `WEB_PORT`
其中仅 `构建并部署` 流水线还需要:
@@ -158,6 +168,8 @@ jenkins/Jenkinsfile.build-and-deploy
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 配置,例如:

View File

@@ -57,9 +57,9 @@
- [CREATION_AGENT_PUBLISH_GATE_SCHEMA_ALIGNMENT_FIX_2026-04-23.md](./CREATION_AGENT_PUBLISH_GATE_SCHEMA_ALIGNMENT_FIX_2026-04-23.md):记录发布阻断项仍按旧 `worldHook / playerPremise / sceneChapters` schema 校验的问题,以及将 Rust `publish gate` 对齐到 `anchorContent / creatorIntent / sceneChapterBlueprints` 当前主链结构的修复口径。
- [CREATION_HUB_CARD_ACTIONS_2026-04-22.md](./CREATION_HUB_CARD_ACTIONS_2026-04-22.md):冻结创作中心作品卡“体验 / 删除”入口的最小落地语义,明确 RPG 已发布作品软删除、卡片直达运行时,以及暂不扩草稿 / 拼图删除契约。
- [CREATION_CATEGORY_OPENING_TIMEOUT_GUARD_FIX_2026-04-22.md](./CREATION_CATEGORY_OPENING_TIMEOUT_GUARD_FIX_2026-04-22.md):记录创作中心点击类别后长时间停留在“正在开启”的根因与修复口径,收口前端创建会话启动超时、中文错误提示以及 Big Fish / 拼图代理上游超时兜底。
- [JENKINS_RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md](./JENKINS_RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md):冻结 Jenkins `构建 / 部署 / 构建并部署` 三条流水线的职责、版本号传递、上游触发门禁、本地目录部署脚本`/home/ubuntu/Genarrative-deploy/` 覆盖策略
- [JENKINS_RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md](./JENKINS_RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md):冻结 Jenkins `构建 / 部署 / 构建并部署` 三条流水线的职责、版本号传递、上游触发门禁、本地目录部署脚本、发布包覆盖策略,以及部署阶段 SpacetimeDB schema 冲突自动导出、清库发布、导入回灌能力
- [JENKINS_DEPLOY_ENV_BOM_FIX_2026-04-25.md](./JENKINS_DEPLOY_ENV_BOM_FIX_2026-04-25.md):记录 Jenkins 部署时 `.env.local` 首行 UTF-8 BOM 导致 `start.sh` 加载失败的根因,并冻结发布包构建、部署脚本和启动脚本的环境文件净化规则。
- [RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md](./RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md):冻结 Rust 本地一键联调脚本与 Ubuntu 发布包构建脚本的执行口径,覆盖 `npm run dev:rust``npm run build:rust:ubuntu`、Vite release、Linux `api-server`、SpacetimeDB wasm、启动停止脚本、默认 scp 上传安全清库开关。
- [RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md](./RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md):冻结 Rust 本地一键联调脚本与 Ubuntu 发布包构建脚本的执行口径,覆盖 `npm run dev:rust``npm run build:rust:ubuntu`、Vite release、Linux `api-server`、SpacetimeDB wasm、启动停止脚本、默认 scp 上传安全清库开关,以及发布包内 schema 冲突自动迁移脚本
- [RUST_API_SERVER_ROUTE_INDEX_2026-04-22.md](./RUST_API_SERVER_ROUTE_INDEX_2026-04-22.md):记录当前 Rust `api-server` 已挂载的 101 条 Axum 路由,并补充管理后台入口与管理接口索引,按 auth、assets、runtime、custom world、story、generated path 等挂载面归类,用于对照 Node 能力基线与切流 smoke 清单。
- [BACKEND_REWRITE_CROSS_CUTTING_GOVERNANCE_2026-04-22.md](./BACKEND_REWRITE_CROSS_CUTTING_GOVERNANCE_2026-04-22.md):冻结后端重写收口阶段的横向治理规则,覆盖 TypeScript contract 到 Rust DTO 映射、SpacetimeDB schema 演进、大对象 / workflow cache 存储边界和文档维护门禁。
- [PLATFORM_LLM_TEXT_GATEWAY_DESIGN_2026-04-21.md](./PLATFORM_LLM_TEXT_GATEWAY_DESIGN_2026-04-21.md)`platform-llm` 文本模型网关首版设计,冻结 OpenAI 兼容 `/chat/completions`、SSE 增量解析、错误模型与重试边界。

View File

@@ -142,7 +142,7 @@ npm run deploy:rust:remote
5. 执行 `cargo build -p spacetime-module --release --target wasm32-unknown-unknown --manifest-path server-rs/Cargo.toml`,并把 `spacetime_module.wasm` 复制到目标目录。
6. 把仓库根目录的 `.env``.env.local` 分别复制到目标目录根部和目标目录的 `web/` 下;复制后统一移除 UTF-8 BOM 与 CRLF避免目标服务器 Bash 加载环境文件失败。
7. 在目标目录写入 `web-server.mjs`,用于托管 `web/` 并把 `/api/*``/generated-*``/healthz` 反代到本包内的 `api-server`
8. 在目标目录写入 `start.sh``stop.sh``start.sh` 会先按 `KEY=value` 子集加载发布目录根部的 `.env``.env.local`,兼容 UTF-8 BOM 与 CRLF再回退到构建时通过 `--database``--api-port``--web-port``--spacetime-host``--spacetime-port` 写入的默认值,并默认导出 `NO_COLOR=1``CARGO_TERM_COLOR=never`,避免 ANSI 控制码写入日志文件;同时按 Ubuntu 发布环境使用发布目录内 `.spacetimedb/` 作为 root-dir不再额外设置 `--data-dir`,启动前先执行 `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`,当前线上 `spacetime` 入口为 `/usr/local/bin/spacetime`;启动参数为 `spacetime --root-dir ./.spacetimedb start --edition standalone --listen-addr <host>:<port>`,探活必须确认 `server ping` 输出包含 `Server is online:`;如果以 `--clear-database` 启动,则内部 `spacetime publish` 会追加 `-c=on-conflict`仅在 schema 冲突时删除旧模块数据
8. 在目标目录写入 `start.sh``stop.sh``start.sh` 会先按 `KEY=value` 子集加载发布目录根部的 `.env``.env.local`,兼容 UTF-8 BOM 与 CRLF再回退到构建时通过 `--database``--api-port``--web-port``--spacetime-host``--spacetime-port` 写入的默认值,并默认导出 `NO_COLOR=1``CARGO_TERM_COLOR=never`,避免 ANSI 控制码写入日志文件;同时按 Ubuntu 发布环境使用发布目录内 `.spacetimedb/` 作为 root-dir不再额外设置 `--data-dir`,启动前先执行 `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`,当前线上 `spacetime` 入口为 `/usr/local/bin/spacetime`;启动参数为 `spacetime --root-dir ./.spacetimedb start --edition standalone --listen-addr <host>:<port>`,探活必须确认 `server ping` 输出包含 `Server is online:`普通启动先无清库发布,若 publish 输出可判定为 schema 冲突,则自动导出旧库、清库发布新 wasm、导入回灌如果以 `--clear-database` 启动,则内部 `spacetime publish` 会追加 `-c=on-conflict`代表人工确认清库,不触发自动回灌
9. 默认执行 `scp -r -i ~\.ssh\dsk.pem build/<timestamp> ubuntu@82.157.175.59:/home/ubuntu/genarrative/` 上传发布包。
发布包结构:
@@ -156,6 +156,9 @@ build/<timestamp>/
│ └─ .env.local
├─ api-server
├─ spacetime_module.wasm
├─ migration-bootstrap-secret.txt
├─ scripts/
│ └─ spacetime-*.mjs
├─ web-server.mjs
├─ start.sh
├─ stop.sh
@@ -178,19 +181,21 @@ cd build/<timestamp>
./stop.sh
```
如果后续通过 Jenkins 的部署脚本把发布包覆盖到固定部署目录,部署阶段默认只替换 `web/``api-server``spacetime_module.wasm``.env*``start.sh``stop.sh``web-server.mjs``README.md` 等发布产物;文件产物使用普通复制,`web/` 等目录产物递归复制,不会删除部署目录中的 `.spacetimedb/``logs/``run/` 这类运行态目录。
如果后续通过 Jenkins 的部署脚本把发布包覆盖到固定部署目录,部署阶段默认只替换 `web/``api-server``spacetime_module.wasm``migration-bootstrap-secret.txt``scripts/``.env*``start.sh``stop.sh``web-server.mjs``README.md` 等发布产物;文件产物使用普通复制,`web/``scripts/` 等目录产物递归复制,不会删除部署目录中的 `.spacetimedb/``logs/``run/``database-migrations/` 这类运行态目录。
安全边界:
1. 构建脚本会把仓库根目录已有的 `.env``.env.local` 一并复制进发布包,因此运行前必须确认这些文件内容适合被带入目标环境。
2. 如果仓库根目录不存在 `.env``.env.local`,脚本会打印跳过日志,但不会因此失败;此时 `start.sh` 仅使用构建时写入的默认值与运行时显式传入的环境变量。
3. `start.sh` 只解析合法 `KEY=value` 环境行,支持不加引号、双引号和单引号;不执行复杂 shell 表达式,避免把环境文件变成脚本入口。
4. `start.sh` 默认不追加清理参数;只有显式执行 `./start.sh --clear-database` 才追加 `-c=on-conflict`,在 schema 冲突时清理旧模块数据后重发
5. `start.sh` 使用 `spacetime publish --bin-path spacetime_module.wasm --yes` 发布当前包内 wasm清库模式下会追加 `-c=on-conflict`,仅在 schema 冲突时删除旧模块数据
6. `start.sh` 会先复用已经按目标地址就绪的 SpacetimeDB如果同一个 `.spacetimedb/` root-dir 已被其他未就绪实例占用,则只输出命令名为 `spacetime``spacetimedb-cli` 且命令行包含当前 root-dir 的真实占用进程并失败,避免把排查用的 `grep` / `awk` 误判为 SpacetimeDB 实例
7. 如果 `spacetime publish``403 Forbidden`,优先确认 `spacetime --root-dir ./.spacetimedb login show` 输出的身份是否有权更新目标库;`--clear-database` 不能绕过身份授权
8. 当前脚本是单目录进程启动方案,不替代生产 systemd、Nginx、TLS、日志轮转与守护进程配置
9.只需要本地生成发布包,可传 `--skip-upload` 跳过默认 scp 上传
4. `start.sh` 默认不追加清理参数;普通发布如果遇到 SpacetimeDB schema 冲突,会调用发布包内的 `scripts/spacetime-export-migration-json.mjs` 导出旧库,再清库发布新 wasm并调用 `scripts/spacetime-import-migration-json.mjs --replace-existing` 回灌。可通过 `GENARRATIVE_SPACETIME_MIGRATE_ON_CONFLICT=false` 禁用该行为
5. 自动迁移导出旧库时优先读取 `run/migration-bootstrap-secret.previous.txt`,导入新库时读取当前发布包 `migration-bootstrap-secret.txt`Jenkins 部署脚本会在覆盖发布包前保存旧密钥。手工覆盖发布包时,也应在覆盖前保留旧模块的引导密钥,否则旧库导出可能无法授权
6. 自动迁移 JSON 默认写入发布目录下 `database-migrations/<database>/`;可通过 `GENARRATIVE_SPACETIME_MIGRATION_DIR` 改写。该目录属于运行态,不应被 Jenkins 覆盖部署删除
7. 只有显式执行 `./start.sh --clear-database` 才追加 `-c=on-conflict`,该模式代表人工确认清库,不执行导出和回灌
8. `start.sh` 会先复用已经按目标地址就绪的 SpacetimeDB如果同一个 `.spacetimedb/` root-dir 已被其他未就绪实例占用,则只输出命令名为 `spacetime``spacetimedb-cli` 且命令行包含当前 root-dir 的真实占用进程并失败,避免把排查用的 `grep` / `awk` 误判为 SpacetimeDB 实例
9.`spacetime publish``403 Forbidden`,优先确认 `spacetime --root-dir ./.spacetimedb login show` 输出的身份是否有权更新目标库;`--clear-database` 不能绕过身份授权
10. 当前脚本是单目录进程启动方案,不替代生产 systemd、Nginx、TLS、日志轮转与守护进程配置。
11. 如只需要本地生成发布包,可传 `--skip-upload` 跳过默认 scp 上传。
目标服务器最小要求:

View File

@@ -146,6 +146,31 @@ npm run spacetime:publish:maincloud -- --database xushi-p4wfr
冲突自动迁移需要发布脚本本次生成的 `GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET`。因此不要和 `--no-migration-bootstrap-secret` 同时使用。
### 部署流水线自动迁移
Ubuntu 发布包的 `start.sh` 与 Jenkins `Genarrative-Deploy` 也采用同一套迁移 procedure但迁移触发点在部署目录内
1. Jenkins 覆盖部署前,如果旧部署目录存在 `migration-bootstrap-secret.txt`,先保存到 `run/migration-bootstrap-secret.previous.txt`
2. Jenkins 复制新发布包,包含新 wasm、新 `migration-bootstrap-secret.txt``scripts/spacetime-*.mjs` 迁移脚本。
3.`start.sh` 先不清库发布当前包内 `spacetime_module.wasm`
4. 如果发布成功,流程结束。
5. 如果发布失败且输出可判定为 schema 冲突,`start.sh` 用旧密钥授权导出旧库 JSON。
6. 导出成功后,`start.sh` 清库发布新 wasm。
7. 新 wasm 发布成功后,`start.sh` 用新密钥授权导入,并以 `--replace-existing` 回灌迁移 JSON。
自动迁移 JSON 默认保存到部署目录的 `database-migrations/<database>/<timestamp>.json`。可通过 Jenkins 参数 `MIGRATION_DIRECTORY` 或环境变量 `GENARRATIVE_SPACETIME_MIGRATION_DIR` 覆盖。该目录属于运行态数据,部署脚本不会删除。
Jenkins 参数 `MIGRATE_ON_CONFLICT` 默认 `true`。如果设为 `false`,普通发布遇到 schema 冲突时会保留原始失败,不执行导出、清库发布和导入回灌。
Jenkins 参数 `CLEAR_DATABASE=true` 或手工执行 `./start.sh --clear-database` 时,语义是人工确认清库发布;此时 `spacetime publish` 追加 `-c=on-conflict`,不执行自动导出和导入回灌。
自动迁移依赖两个引导密钥:
- 导出旧库:优先使用 `run/migration-bootstrap-secret.previous.txt`,也就是旧模块编译时注入的密钥。
- 导入新库:使用当前发布包 `migration-bootstrap-secret.txt`,也就是新模块编译时注入的密钥。
如果不是通过 Jenkins 部署脚本覆盖发布包,而是手工替换文件,必须在覆盖前保留旧 `migration-bootstrap-secret.txt`;否则旧库迁移 procedure 可能无法授权导出。
### 删除表和删除字段
迁移文件来自旧模块时,可能包含新模块已经删除的表或字段。导入阶段按以下规则处理: