diff --git a/docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md b/docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md index 39a15882..a2904310 100644 --- a/docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md +++ b/docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md @@ -1,4 +1,4 @@ -# Rust 本地联调与远端发布脚本方案 +# Rust 本地联调与远端发布脚本方案 日期:`2026-04-22` @@ -32,12 +32,13 @@ npm run dev:rust 默认流程: 1. 检查 `cargo`、`node` 与 `spacetime` CLI。 -2. 启动 `spacetime --root-dir=server-rs/.spacetimedb/local start --edition standalone --listen-addr 127.0.0.1:3101`,确保本地数据库与 SpacetimeDB 内部日志不会落到开发者全局目录。 -3. 等待 `spacetime server ping http://127.0.0.1:3101` 可用。 -4. 执行 `spacetime publish <本地数据库名> --server http://127.0.0.1:3101 --module-path server-rs/crates/spacetime-module --yes`。 -5. 注入 `GENARRATIVE_API_*` 与 `GENARRATIVE_SPACETIME_*` 后启动 `cargo run -p api-server`;直接运行 `api-server` 时,如未显式设置 `GENARRATIVE_SPACETIME_DATABASE`,服务端也会向上查找 `spacetime.local.json` 作为本地默认库名。 -6. 注入 `GENARRATIVE_BACKEND_STACK=rust`、`RUST_SERVER_TARGET`、`GENARRATIVE_RUNTIME_SERVER_TARGET` 后启动 Vite。 -7. 任一子进程退出时,脚本回收其余子进程。 +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. 等待 `spacetime --root-dir=server-rs/.spacetimedb/local server ping http://127.0.0.1:3101` 可用。 +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. 注入 `GENARRATIVE_BACKEND_STACK=rust`、`RUST_SERVER_TARGET`、`GENARRATIVE_RUNTIME_SERVER_TARGET` 后启动 Vite。 +8. 任一子进程退出时,脚本回收其余子进程。 Vite 代理覆盖范围: @@ -47,10 +48,11 @@ Vite 代理覆盖范围: 安全边界: -1. 默认不执行 `--clear-database`。 -2. 只有显式传入 `--clear-database` 才允许清库重发。 +1. 当前开发阶段默认执行 `-c=on-conflict`,允许本地开发库在表结构变化时清除旧模块数据后重发。 +2. 只有显式传入 `--preserve-database` 时,才跳过 `-c=on-conflict` 并保留现有数据。 3. 如需要复用已经启动的 SpacetimeDB,可传 `--skip-spacetime`。 4. 如只想启动进程不发布模块,可传 `--skip-publish`。 +5. 后续进入正式版本前,涉及表结构变化时必须在开发阶段补齐迁移表与迁移函数,不能依赖清库发布作为正式升级策略。 常用示例: @@ -59,7 +61,7 @@ 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 --clear-database +./scripts/dev-rust-stack.sh --preserve-database ``` 日志提取: @@ -72,7 +74,7 @@ npm run dev:rust:logs -- --follow 日志提取规则: -1. SpacetimeDB 模块日志以 `spacetime logs ` 为唯一提取入口,脚本不直接读取内部日志文件结构。 +1. SpacetimeDB 模块日志以 `spacetime --root-dir=server-rs/.spacetimedb/local logs ` 为唯一提取入口,脚本不直接读取内部日志文件结构。 2. 默认读取 `spacetime.local.json` 的 `database` 字段,默认 server 为 `http://127.0.0.1:3101`。 3. 默认输出到 `logs/spacetime/-.log`,并通过 `tee` 同步显示在终端。 4. `--follow` 仅用于本地追踪,会持续追加到同一个输出文件;停止时用 `Ctrl+C`。 @@ -80,7 +82,7 @@ npm run dev:rust:logs -- --follow 联调排错补充: 1. 如果首页公开广场出现 `上游服务请求失败`,优先检查 `api-server` 错误详情里的 `ws://.../v1/database//subscribe` 是否指向了未发布的库。 -2. `spacetime list --server http://127.0.0.1:3101` 应能看到 `spacetime.local.json` 中的库名;若没有,执行 `spacetime publish <本地数据库名> --server http://127.0.0.1:3101 --module-path server-rs/crates/spacetime-module --yes`。 +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`,前端首页只能展示空态或错误提示,无法自行修复。 ## 3. Ubuntu 发布包脚本 @@ -108,7 +110,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/` 下。 7. 在目标目录写入 `web-server.mjs`,用于托管 `web/` 并把 `/api/*`、`/generated-*`、`/healthz` 反代到本包内的 `api-server`。 -8. 在目标目录写入 `start.sh` 与 `stop.sh`;`start.sh` 会先加载发布目录根部的 `.env`、`.env.local`,再回退到构建时通过 `--database`、`--api-port`、`--web-port`、`--spacetime-host`、`--spacetime-port` 写入的默认值,并默认导出 `NO_COLOR=1` 与 `CARGO_TERM_COLOR=never`,避免 ANSI 控制码写入日志文件;如果以 `--clear-database` 启动,则内部 `spacetime publish` 会追加 `-c always`。 +8. 在目标目录写入 `start.sh` 与 `stop.sh`;`start.sh` 会先加载发布目录根部的 `.env`、`.env.local`,再回退到构建时通过 `--database`、`--api-port`、`--web-port`、`--spacetime-host`、`--spacetime-port` 写入的默认值,并默认导出 `NO_COLOR=1` 与 `CARGO_TERM_COLOR=never`,避免 ANSI 控制码写入日志文件;如果以 `--clear-database` 启动,则内部 `spacetime publish` 会追加 `-c=on-conflict`,仅在 schema 冲突时删除旧模块数据。 9. 默认执行 `scp -r -i ~\.ssh\dsk.pem build/ ubuntu@82.157.175.59:/home/ubuntu/genarrative/` 上传发布包。 发布包结构: @@ -150,8 +152,8 @@ cd build/ 1. 构建脚本会把仓库根目录已有的 `.env`、`.env.local` 一并复制进发布包,因此运行前必须确认这些文件内容适合被带入目标环境。 2. 如果仓库根目录不存在 `.env` 或 `.env.local`,脚本会打印跳过日志,但不会因此失败;此时 `start.sh` 仅使用构建时写入的默认值与运行时显式传入的环境变量。 -3. `start.sh` 默认不清空 SpacetimeDB;只有显式执行 `./start.sh --clear-database` 才允许清库重发。 -4. `start.sh` 使用 `spacetime publish --bin-path spacetime_module.wasm --yes` 发布当前包内 wasm;清库模式下会追加 `-c always`。 +3. `start.sh` 默认不追加清理参数;只有显式执行 `./start.sh --clear-database` 才追加 `-c=on-conflict`,在 schema 冲突时清理旧模块数据后重发。 +4. `start.sh` 使用 `spacetime publish --bin-path spacetime_module.wasm --yes` 发布当前包内 wasm;清库模式下会追加 `-c=on-conflict`,仅在 schema 冲突时删除旧模块数据。 5. 当前脚本是单目录进程启动方案,不替代生产 systemd、Nginx、TLS、日志轮转与守护进程配置。 6. 如只需要本地生成发布包,可传 `--skip-upload` 跳过默认 scp 上传。 diff --git a/scripts/deploy-rust-remote.sh b/scripts/deploy-rust-remote.sh index 08feece4..49a33fee 100644 --- a/scripts/deploy-rust-remote.sh +++ b/scripts/deploy-rust-remote.sh @@ -448,7 +448,7 @@ usage() { 说明: 1. 启动当前发布包内的静态网站、SpacetimeDB 与 api-server。 2. 默认发布 spacetime_module.wasm 到 GENARRATIVE_SPACETIME_DATABASE,但不清库。 - 3. 只有显式传入 --clear-database 时才允许清空数据库重发。 + 3. 只有显式传入 --clear-database 时才会在 schema 冲突时清理旧模块数据后重发。 EOF } @@ -550,8 +550,8 @@ PUBLISH_ARGS=( ) if [[ "${CLEAR_DATABASE}" -eq 1 ]]; then - # 按当前 SpacetimeDB CLI 约定使用 -c,等价于 --delete-data always。 - PUBLISH_ARGS+=(-c) + # 发布包清库模式只在 schema 冲突时删除旧模块数据,避免无冲突升级误清数据。 + PUBLISH_ARGS+=(-c=on-conflict) fi echo "[start] 发布 SpacetimeDB wasm: ${SPACETIME_DATABASE}" diff --git a/scripts/dev-rust-stack.sh b/scripts/dev-rust-stack.sh index 64178ad1..1e70dda8 100644 --- a/scripts/dev-rust-stack.sh +++ b/scripts/dev-rust-stack.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env bash set -euo pipefail @@ -8,13 +8,13 @@ usage() { npm run dev:rust ./scripts/dev-rust-stack.sh --api-port 8090 --spacetime-port 3110 ./scripts/dev-rust-stack.sh --skip-spacetime --skip-publish - ./scripts/dev-rust-stack.sh --clear-database + ./scripts/dev-rust-stack.sh --preserve-database npm run dev:rust:logs -- --follow 说明: 1. 默认同时启动 SpacetimeDB standalone、Rust api-server 与 Vite 前端。 - 2. 默认会 publish server-rs/crates/spacetime-module,但不会清空数据库。 - 3. 只有显式传入 --clear-database 时,才会追加 spacetime publish --clear-database。 + 2. 当前开发阶段默认 publish server-rs/crates/spacetime-module 时追加 -c=on-conflict 在结构冲突时清理旧模块数据。 + 3. 只有显式传入 --preserve-database 时,才会跳过 -c=on-conflict。 4. SpacetimeDB 默认使用 server-rs/.spacetimedb/local 作为本地数据与日志目录。 EOF } @@ -67,7 +67,8 @@ cleanup() { wait_for_spacetime() { local server="$1" local timeout_seconds="$2" - local process_pid="${3:-}" + local root_dir="$3" + local process_pid="${4:-}" local deadline=$((SECONDS + timeout_seconds)) while ((SECONDS < deadline)); do @@ -76,7 +77,7 @@ wait_for_spacetime() { exit 1 fi - if spacetime server ping "${server}" >/dev/null 2>&1; then + if spacetime --root-dir="${root_dir}" server ping "${server}" >/dev/null 2>&1; then return fi @@ -87,6 +88,51 @@ wait_for_spacetime() { exit 1 } +sync_local_spacetime_install() { + local root_dir="$1" + + # SpacetimeDB standalone 会在 --root-dir 下回调 bin/current/spacetimedb-cli.exe; + # Windows 本地开发使用工程内 root-dir 时,需要把用户级安装目录同步进来。 + if [[ "${OSTYPE:-}" != msys* && "${OSTYPE:-}" != cygwin* ]]; then + return + fi + + local target_cli="${root_dir}/bin/current/spacetimedb-cli.exe" + if [[ -f "${target_cli}" ]]; then + return + fi + + local spacetime_command + spacetime_command="$(command -v spacetime || true)" + if [[ -z "${spacetime_command}" ]]; then + return + fi + + local install_dir + install_dir="$(cd -- "$(dirname -- "${spacetime_command}")" && pwd)" + if [[ ! -d "${install_dir}/bin" ]]; then + return + fi + + echo "[dev:rust] 同步本机 SpacetimeDB 安装到 ${root_dir}" + mkdir -p "${root_dir}" + cp -a "${install_dir}/bin" "${root_dir}/" + if [[ -f "${install_dir}/spacetime.exe" ]]; then + cp -f "${install_dir}/spacetime.exe" "${root_dir}/spacetime.exe" + fi + + # Git Bash 复制 Windows junction 时可能不会生成可执行的 current 目录; + # 若 current 缺失,则用最新版本目录复制出一个真实目录,满足 standalone 回调路径。 + if [[ ! -f "${target_cli}" ]]; then + local version_dir + version_dir="$(find "${root_dir}/bin" -mindepth 1 -maxdepth 1 -type d ! -name current | sort -V | tail -n 1)" + if [[ -n "${version_dir}" && -f "${version_dir}/spacetimedb-cli.exe" ]]; then + rm -rf "${root_dir}/bin/current" + cp -a "${version_dir}" "${root_dir}/bin/current" + fi + fi +} + SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" SERVER_RS_DIR="${REPO_ROOT}/server-rs" @@ -106,7 +152,7 @@ API_LOG="info,tower_http=info" SPACETIME_TIMEOUT_SECONDS="60" SKIP_SPACETIME=0 SKIP_PUBLISH=0 -CLEAR_DATABASE=0 +PRESERVE_DATABASE=0 PIDS=() NAMES=() @@ -186,7 +232,11 @@ while [[ $# -gt 0 ]]; do shift ;; --clear-database) - CLEAR_DATABASE=1 + PRESERVE_DATABASE=0 + shift + ;; + --preserve-database) + PRESERVE_DATABASE=1 shift ;; *) @@ -242,6 +292,7 @@ echo "[dev:rust] spacetime root: ${SPACETIME_ROOT_DIR}" if [[ "${SKIP_SPACETIME}" -ne 1 ]]; then mkdir -p "${SPACETIME_ROOT_DIR}" + sync_local_spacetime_install "${SPACETIME_ROOT_DIR}" echo "[dev:rust] 启动 spacetimedb" ( cd "${SERVER_RS_DIR}" @@ -257,7 +308,7 @@ fi if [[ "${SKIP_PUBLISH}" -ne 1 ]]; then echo "[dev:rust] 等待 SpacetimeDB 就绪" - wait_for_spacetime "${SPACETIME_SERVER}" "${SPACETIME_TIMEOUT_SECONDS}" "${PIDS[0]:-}" + wait_for_spacetime "${SPACETIME_SERVER}" "${SPACETIME_TIMEOUT_SECONDS}" "${SPACETIME_ROOT_DIR}" "${PIDS[0]:-}" PUBLISH_ARGS=( publish @@ -266,14 +317,14 @@ if [[ "${SKIP_PUBLISH}" -ne 1 ]]; then --module-path "${MODULE_PATH}" ) - if [[ "${CLEAR_DATABASE}" -eq 1 ]]; then - PUBLISH_ARGS+=(--clear-database) + if [[ "${PRESERVE_DATABASE}" -ne 1 ]]; then + PUBLISH_ARGS+=(-c=on-conflict) fi PUBLISH_ARGS+=(--yes) echo "[dev:rust] 发布 SpacetimeDB 模块: ${DATABASE}" - spacetime "${PUBLISH_ARGS[@]}" + spacetime --root-dir="${SPACETIME_ROOT_DIR}" "${PUBLISH_ARGS[@]}" fi echo "[dev:rust] 启动 api-server" diff --git a/scripts/spacetime-logs-local.sh b/scripts/spacetime-logs-local.sh index 4a4b9046..b7528570 100644 --- a/scripts/spacetime-logs-local.sh +++ b/scripts/spacetime-logs-local.sh @@ -13,6 +13,7 @@ usage() { 1. 从本地 SpacetimeDB 通过 spacetime logs 提取模块日志到本地文件。 2. 默认读取 spacetime.local.json 的 database 字段,默认 server 为 http://127.0.0.1:3101。 3. 默认输出到 logs/spacetime/-.log;--follow 会持续追加并同步写到终端。 + 4. 默认使用 server-rs/.spacetimedb/local 作为 spacetime CLI root,保持本地身份与 standalone 一致。 EOF } @@ -51,6 +52,7 @@ REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" DATABASE="" SPACETIME_SERVER="http://127.0.0.1:3101" +SPACETIME_ROOT_DIR="${REPO_ROOT}/server-rs/.spacetimedb/local" LINES="200" OUTPUT="" FOLLOW=0 @@ -69,6 +71,10 @@ while [[ $# -gt 0 ]]; do SPACETIME_SERVER="${2:?缺少 --server 的值}" shift 2 ;; + --spacetime-root-dir) + SPACETIME_ROOT_DIR="${2:?缺少 --spacetime-root-dir 的值}" + shift 2 + ;; --lines|-n) LINES="${2:?缺少 --lines 的值}" shift 2 @@ -117,6 +123,7 @@ fi echo "[stdb:logs] database: ${DATABASE}" echo "[stdb:logs] server: ${SPACETIME_SERVER}" +echo "[stdb:logs] spacetime root: ${SPACETIME_ROOT_DIR}" echo "[stdb:logs] output: ${OUTPUT}" -spacetime "${ARGS[@]}" | tee -a "${OUTPUT}" +spacetime --root-dir="${SPACETIME_ROOT_DIR}" "${ARGS[@]}" | tee -a "${OUTPUT}"