Files
Genarrative/docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md
kdletters a560e4e6c1
Some checks failed
CI / verify (push) Has been cancelled
Fix dev Rust SpacetimeDB readiness probe
2026-04-30 16:54:18 +08:00

17 KiB
Raw Blame History

Rust 本地联调与远端发布脚本方案

日期:2026-04-22

1. 目标

本方案补齐 server-rs 在 M7 切流前需要的两类工程脚本:

  1. 本地一键联调脚本:同时启动本地 SpacetimeDB、Rust api-server 与 Web 前端,并通过现有 Vite 代理开关把运行时 API 指向 Rust。
  2. Ubuntu 发布包构建脚本:在仓库根目录生成 build/<当前时间>/ 发布目录,内含前端 release、Linux api-server、SpacetimeDB wasm、启动脚本、停止脚本以及从仓库根目录复制的 .env / .env.local,并默认通过 scp 上传到目标服务器。

脚本只做部署与联调编排,不改变 HTTP contract、SpacetimeDB schema 命名和对象存储键规划。

2. 本地脚本

入口:

npm run dev:rust

默认入口直接执行 Bash 版 scripts/dev-rust-stack.sh。Windows 下 dev:rustdev:rust:logsdeploy:rust:remotebuild:rust:ubuntu 会通过 scripts/run-bash-script.mjs 优先查找 Git Bash如安装路径不标准可用 GENARRATIVE_BASH 指定 bash 可执行文件。

默认端口:

  1. Web 前端:http://127.0.0.1:3000
  2. Rust api-serverhttp://127.0.0.1:8082
  3. SpacetimeDB standalonehttp://127.0.0.1:3101
  4. SpacetimeDB database优先读取仓库根目录 spacetime.local.jsondatabase 字段;没有该字段时才回退到 genarrative-dev
  5. SpacetimeDB 本地数据与日志目录:server-rs/.spacetimedb/local

默认流程:

  1. 检查 cargonodespacetime CLI。
  2. Windows Git Bash 下如 server-rs/.spacetimedb/local/bin/current/spacetimedb-cli.exe 不存在,先把本机 spacetime 所在安装目录的 bin/spacetime.exe 同步到 server-rs/.spacetimedb/local/
  3. 启动 spacetime --root-dir=server-rs/.spacetimedb/local start --edition standalone --listen-addr 127.0.0.1:3101,确保本地数据库与 SpacetimeDB 内部日志不会落到开发者全局目录。
  4. 等待 SpacetimeDB 就绪:优先接受 spacetime --root-dir=server-rs/.spacetimedb/local server ping http://127.0.0.1:3101 输出中的 Server is online:;如果 Windows 下 SpacetimeDB CLI 2.1.0 对已经监听的 standalone 仍打印 502 Bad Gateway,脚本会兜底请求 http://127.0.0.1:3101/v1/ping,只有该健康端点返回 2xx 时才放行。不能只依赖 CLI 退出码,因为 CLI 在 502 Bad Gateway 时也可能返回退出码 0
  5. 执行 spacetime --root-dir=server-rs/.spacetimedb/local publish <本地数据库名> --server http://127.0.0.1:3101 --module-path server-rs/crates/spacetime-module -c=on-conflict --yes,确保 publish 的签名身份与 standalone 的本地控制库一致,并在当前开发阶段允许新版模块表结构变化且发生 schema 冲突时清除旧模块数据。
  6. 注入 GENARRATIVE_API_*GENARRATIVE_SPACETIME_* 后启动 cargo run -p api-server;直接运行 api-server 时,如未显式设置 GENARRATIVE_SPACETIME_DATABASE,服务端也会向上查找 spacetime.local.json 作为本地默认库名。
  7. 等待 http://127.0.0.1:<api-port>/healthz 返回 HTTP 响应后再启动 Vite避免前端初始化请求早于 Rust api-server 监听完成并在终端刷出 ECONNREFUSED 127.0.0.1:<api-port>
  8. 注入 RUST_SERVER_TARGETGENARRATIVE_RUNTIME_SERVER_TARGET 后启动 Vite。
  9. 任一子进程退出时,脚本回收其余子进程。

Vite 代理覆盖范围:

  1. /api/runtime/* 会在 Rust 栈下代理到 Rust api-server,覆盖旧 runtime story 兼容接口。
  2. /api/story/* 会在 Rust 栈下代理到 Rust api-server,覆盖新 story session、battle 查询与 NPC battle 切片接口。
  3. 其他 /api/auth/api/assets/api/custom-world/api/llm 等路径仍由同一个 GENARRATIVE_RUNTIME_SERVER_TARGET 控制,便于 M7 按服务能力逐项做对比 smoke。

安全边界:

  1. 当前开发阶段默认执行 -c=on-conflict,允许本地开发库在表结构变化时清除旧模块数据后重发。
  2. 只有显式传入 --preserve-database 时,才跳过 -c=on-conflict 并保留现有数据。
  3. 如需要复用已经启动的 SpacetimeDB可传 --skip-spacetime
  4. 如只想启动进程不发布模块,可传 --skip-publish
  5. 后续进入正式版本前,涉及表结构变化时必须在开发阶段补齐迁移表与迁移函数,不能依赖清库发布作为正式升级策略。

常用示例:

npm run dev:rust
./scripts/dev-rust-stack.sh
./scripts/dev-rust-stack.sh --api-port 8090 --spacetime-port 3110 --database genarrative-dev
./scripts/dev-rust-stack.sh --skip-spacetime --skip-publish
./scripts/dev-rust-stack.sh --preserve-database

bindings 生成:

npm run spacetime:generate
npm run spacetime:generate -- --rust-only

生成规则:

  1. npm run spacetime:generate 是本仓库刷新 SpacetimeDB Rust client bindings 的唯一推荐入口。
  2. 前端不直连 SpacetimeDB不生成 TypeScript bindings前端只通过 Rust api-server 暴露的 HTTP / SSE contract 访问后端。
  3. Rust bindings 不生成私有表绑定,不追加 --include-private,生成到短临时目录后同步到 server-rs/crates/spacetime-client/src/module_bindings
  4. Windows 下 SpacetimeDB CLI 2.1.0 会在生成结束后把所有生成文件路径一次性传给 formatterRust bindings 文件较多,若直接输出到仓库深目录,可能触发 Could not format generated files: 文件名或扩展名太长。。该错误不是单个最长文件路径超过限制,而是 formatter 子进程参数总长超过 Windows CreateProcess 限制。
  5. CLI 在上述 formatter 失败时仍可能返回退出码 0;脚本会捕获输出,只要出现 Could not format generated files 就视为失败。
  6. 根目录 spacetime.json 不配置 generate 目标,避免裸 spacetime generate 在 Windows 上直接碰到 Rust formatter 路径总长限制,也避免误生成前端 bindings。
  7. 不直接手写或局部补 server-rs/crates/spacetime-client/src/module_bindings 下的生成文件schema 变化后重新执行本脚本,并补跑编码检查与对应类型检查。

日志提取:

npm run dev:rust:logs
npm run dev:rust:logs -- --follow
./scripts/spacetime-logs-local.sh --lines 500 --output logs/spacetime/local.log

日志提取规则:

  1. SpacetimeDB 模块日志以 spacetime --root-dir=server-rs/.spacetimedb/local logs <database> 为唯一提取入口,脚本不直接读取内部日志文件结构。
  2. 默认读取 spacetime.local.jsondatabase 字段,默认 server 为 http://127.0.0.1:3101
  3. 默认输出到 logs/spacetime/<database>-<timestamp>.log,并通过 tee 同步显示在终端。
  4. --follow 仅用于本地追踪,会持续追加到同一个输出文件;停止时用 Ctrl+C

联调排错补充:

  1. 如果首页公开广场出现 上游服务请求失败,优先检查 api-server 错误详情里的 ws://.../v1/database/<database>/subscribe 是否指向了未发布的库。
  2. spacetime --root-dir=server-rs/.spacetimedb/local list --server http://127.0.0.1:3101 应能看到 spacetime.local.json 中的库名;若没有,执行 spacetime --root-dir=server-rs/.spacetimedb/local publish <本地数据库名> --server http://127.0.0.1:3101 --module-path server-rs/crates/spacetime-module -c=on-conflict --yes
  3. 发布库名与 GENARRATIVE_SPACETIME_DATABASE 不一致时,/api/runtime/custom-world-gallery 会从 Rust api-server 返回 502,前端首页只能展示空态或错误提示,无法自行修复。
  4. 如果 Vite 输出 /api/auth/refresh/api/auth/login-options/api/runtime/custom-world-galleryECONNREFUSED,先确认当前脚本是否已经打印 等待 api-server 就绪 并通过;正常情况下 Vite 只会在 /healthz 可访问后启动,不应再因为 Rust 监听未完成而代理失败。
  5. 如果 spacetime server ping 打印 Server could not be reached (502 Bad Gateway),即使命令退出码为 0 也不能直接视为已就绪;本地脚本会继续探测 /v1/ping。若 /v1/ping 返回 200,说明 standalone 已经可用,可以继续发布模块;若 /v1/ping 也失败,脚本会继续等待新启动实例,或在 root-dir 已被其他实例占用时输出占用进程。

编译警告治理:

  1. Rust 本地栈启动日志应保持可行动,运行态未使用函数不应长期保留为普通编译警告。
  2. 仅供测试断言使用的辅助函数使用 #[cfg(test)] 限定,避免进入 cargo run -p api-server 的普通二进制编译。
  3. 已无调用入口且无迁移价值的映射函数直接删除;如果后续新增同类 SpacetimeDB 记录映射,再按实际调用路径补回,避免提前保留死代码。

Maincloud API 重启补充:

  1. npm run api-server:maincloud 会先读取 .env.env.local,把 GENARRATIVE_SPACETIME_MAINCLOUD_* 映射为 api-server 使用的 GENARRATIVE_SPACETIME_*,再运行 cargo run -p api-server --manifest-path server-rs/Cargo.toml
  2. Windows 下脚本会尽力停止本仓库 server-rs/target/debug/api-server.exe 对应的旧进程,避免 cargo 重新编译时 exe 被占用。
  3. 旧进程已经退出或清理过程中出现瞬时等待失败时,不应阻断新的 api-server 启动;脚本只记录清理失败并继续启动。

3. Ubuntu 发布包脚本

入口:

npm run build:rust:ubuntu

兼容入口:

npm run deploy:rust:remote

保留 deploy:rust:remote 是为了不打断既有命令习惯;当前语义已调整为“生成 Ubuntu 发布包”,不再通过 SSH 进入服务器执行部署。

默认流程:

  1. 在仓库根目录创建 build/
  2. build/ 下创建当前时间命名的目标目录,例如 build/20260422-153000/
  3. 使用 Vite 构建前端 release 到目标目录的 web/
  4. 执行 cargo build -p api-server --release --target x86_64-unknown-linux-gnu --manifest-path server-rs/Cargo.toml,并把 api-server 复制到目标目录。
  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并把 GENARRATIVE_SPACETIME_DATABASE 覆盖为本次 --database 参数,避免 Jenkins 工作区里残留的旧 .env.local 覆盖发布包目标库。
  7. 在目标目录写入 web-server.mjs,用于托管 web/ 并把 /api/*/generated-*/healthz 反代到本包内的 api-server
  8. 在目标目录写入 start.shstop.shstart.sh 会先按 KEY=value 子集加载发布目录根部的 .env.env.local,兼容 UTF-8 BOM 与 CRLF再回退到构建时通过 --database--api-port--web-port--spacetime-host--spacetime-port 写入的默认值,并默认导出 NO_COLOR=1CARGO_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/ 上传发布包。

SpacetimeDB database 名称必须匹配 ^[a-z0-9]+(-[a-z0-9]+)*$:只能使用小写字母、数字,并用单个短横线分隔;大写字母、点号、下划线、首尾短横线和连续短横线都会触发 spacetime publishinvalid characters in database name。发布包构建脚本和 start.sh 都会提前拦截这类非法名称。

发布包构建日志会输出 SpacetimeDB 发布数据库: <database>;目标服务器执行 start.sh 时会在发布前输出最终加载后的 database/server/root-dir,用于确认 .env.local 或 Jenkins 参数覆盖后的实际发布目标。

发布包结构:

build/<timestamp>/
├─ .env
├─ .env.local
├─ web/
│  ├─ .env
│  └─ .env.local
├─ api-server
├─ spacetime_module.wasm
├─ migration-bootstrap-secret.txt
├─ scripts/
│  └─ spacetime-*.mjs
├─ web-server.mjs
├─ start.sh
├─ stop.sh
└─ README.md

常用示例:

npm run build:rust:ubuntu -- --name 20260422-153000
npm run build:rust:ubuntu -- --database genarrative-dev --web-port 3000 --api-port 8082 --spacetime-port 3101
npm run build:rust:ubuntu -- --skip-upload

目标服务器启动:

cd build/<timestamp>
./start.sh
./stop.sh

如果后续通过 Jenkins 的部署脚本把发布包覆盖到固定部署目录,部署阶段默认只替换 web/api-serverspacetime_module.wasmmigration-bootstrap-secret.txtscripts/.env*start.shstop.shweb-server.mjsREADME.md 等发布产物;文件产物使用普通复制,web/scripts/ 等目录产物递归复制,不会删除部署目录中的 .spacetimedb/logs/run/deploy-state/database-migrations/ 这类运行态目录。

安全边界:

  1. 构建脚本会把仓库根目录已有的 .env.env.local 一并复制进发布包,因此运行前必须确认这些文件内容适合被带入目标环境。
  2. 如果仓库根目录不存在 .env.env.local,脚本会打印跳过日志,但不会因此失败;此时 start.sh 仅使用构建时写入的默认值与运行时显式传入的环境变量。
  3. start.sh 只解析合法 KEY=value 环境行,支持不加引号、双引号和单引号;不执行复杂 shell 表达式,避免把环境文件变成脚本入口。
  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. 自动迁移导出旧库时优先读取 deploy-state/migration-bootstrap-secret.previous.txt,导入新库时读取当前发布包 migration-bootstrap-secret.txtJenkins 部署脚本会在覆盖发布包前保存旧密钥。该快照属于部署状态,不放入 run/,避免启停 hook 通过 sudo 运行后把部署阶段要写的文件变成 root 私有。手工覆盖发布包时,也应在覆盖前保留旧模块的引导密钥,否则旧库导出可能无法授权。
  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 已被其他未就绪实例占用,则只输出命令名为 spacetimespacetimedb-cli 且命令行包含当前 root-dir 的真实占用进程并失败,避免把排查用的 grep / awk 误判为 SpacetimeDB 实例。
  9. 如果 spacetime publish403 Forbidden,优先确认 spacetime --root-dir ./.spacetimedb login show 输出的身份是否有权更新目标库;--clear-database 不能绕过身份授权。
  10. 当前脚本是单目录进程启动方案,不替代生产 systemd、Nginx、TLS、日志轮转与守护进程配置。
  11. 如只需要本地生成发布包,可传 --skip-upload 跳过默认 scp 上传。

目标服务器最小要求:

  1. Ubuntu x86_64。
  2. 已安装 node,用于运行发布包内的 web-server.mjs
  3. 已安装 spacetime CLIstart.sh 会启动本地 SpacetimeDB 并发布 wasm。
  4. 业务密钥通过目标服务器环境变量或发布包同目录 .env.local 提供。

4. 与 M7 的关系

这套脚本补齐 M7 的部署执行入口但不等价于完成灰度切流。M7 后续仍需要在真实 OSS、LLM、短信、微信、SpacetimeDB 数据库和反向代理环境下完成全链路 smoke、关键 SSE 联调和灰度切流验收。