From 26a3c89d1de2daa596c7e0ce379b270932efb133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8E=86=E5=86=B0=E9=83=81-hermes=E7=89=88?= Date: Fri, 8 May 2026 17:57:03 +0800 Subject: [PATCH] fix(dev): use local spacetime data dir --- .../dev-rust-stack-startup-2026-05-08.md | 48 ++++---- scripts/dev-rust-stack.sh | 116 ++++++------------ 2 files changed, 61 insertions(+), 103 deletions(-) diff --git a/.hermes/skills/genarrative-admin-backoffice/references/dev-rust-stack-startup-2026-05-08.md b/.hermes/skills/genarrative-admin-backoffice/references/dev-rust-stack-startup-2026-05-08.md index aa78daa7..650a7368 100644 --- a/.hermes/skills/genarrative-admin-backoffice/references/dev-rust-stack-startup-2026-05-08.md +++ b/.hermes/skills/genarrative-admin-backoffice/references/dev-rust-stack-startup-2026-05-08.md @@ -1,34 +1,34 @@ # `npm run dev` / `scripts/dev-rust-stack.sh` 启动修复记录 ## 症状 -- `npm run dev` 在 WSL/Linux 下直接失败: - - `It seems like the spacetime version set as current may not exist` - - `exec failed for .../.spacetimedb/local/bin/current/spacetimedb-cli` -- 失败位置通常在 `sync_local_spacetime_install` 后、等待 SpacetimeDB 就绪阶段。 - -## 根因 -- `server-rs/.spacetimedb/local` 是空 root-dir 时,`spacetime start` 仍会尝试回调 `bin/current/spacetimedb-cli`。 -- 旧脚本只按 Windows/Git Bash 思路同步 `spacetimedb-cli.exe`,WSL/Linux 下没有把用户级安装同步到项目 root-dir。 +- `npm run dev` 的 SpacetimeDB standalone 启动需要把数据留在项目本地,避免污染用户级 SpacetimeDB 数据目录。 +- 早期脚本曾通过把用户级 SpacetimeDB 可执行文件目录同步到 `server-rs/.spacetimedb/local/bin/current` 来满足 standalone 回调需求,但这会把整套可执行文件复制进项目本地目录,维护成本高,也容易和用户级 CLI 版本漂移。 - `api-server` 首次冷编译时,默认 300 秒超时不够,容易在就绪前被回收。 -## 修复要点 -1. 同步本机 SpacetimeDB 安装到项目 root-dir - - 从 `spacetime --version` 解析真实 CLI 路径。 - - 将对应版本目录复制到 `server-rs/.spacetimedb/local/bin/`。 - - 重新建立 `bin/current` 指向版本目录。 -2. 兼容 WSL/Linux 与 Windows - - 不再只判断 `OSTYPE=msys*|cygwin*`。 - - 同时检查 `spacetimedb-cli` 与 `spacetimedb-cli.exe`。 -3. 提高 api-server 就绪等待时间 - - `API_SERVER_TIMEOUT_SECONDS` 从 300 提升到 600。 +## 当前方案 +1. SpacetimeDB 可执行文件继续使用用户环境里的 `spacetime` 命令 + - 启动 standalone 时不再复制 `spacetimedb-cli`、版本目录或 `bin/current`。 + - `spacetime start` 不再通过工程内 `--root-dir` 寻找可执行文件。 +2. 数据目录显式指定到项目本地 + - 默认 `SPACETIME_DATA_DIR=${SERVER_RS_DIR}/.spacetimedb/local/data`。 + - 启动命令使用 `spacetime start --data-dir "${SPACETIME_DATA_DIR}" --non-interactive --listen-addr ...`。 + - 如需临时切换数据目录,可传 `--spacetime-data-dir `。 +3. CLI 身份/登录状态仍保留在 root-dir + - 发布模块和 CLI 管理命令继续使用 `spacetime --root-dir="${SPACETIME_ROOT_DIR}" publish ...`。 + - 这样可以保留项目级 CLI 登录/token 隔离,同时不再把可执行文件复制到 root-dir。 +4. 提高 api-server 就绪等待时间 + - `API_SERVER_TIMEOUT_SECONDS` 保持 600,降低首次冷编译误判失败概率。 ## 复现 / 验证 -- 运行 `npm run dev`。 -- 观察日志: - - SpacetimeDB 能正常启动到 `Listening on 127.0.0.1:3101` - - 模块发布成功 - - api-server 进入健康检查等待并最终可访问 `/healthz` +- 运行脚本语法检查:`bash -n scripts/dev-rust-stack.sh`。 +- 运行帮助检查:`bash scripts/dev-rust-stack.sh --help`,确认有 `--spacetime-data-dir`。 +- 运行 `npm run dev` 后观察日志: + - 输出 `spacetime data: .../server-rs/.spacetimedb/local/data`。 + - 不再出现同步/复制本机 SpacetimeDB 安装到项目 root-dir 的日志。 + - SpacetimeDB 能正常监听 `127.0.0.1:3101`。 + - 模块发布成功。 + - api-server 进入健康检查等待并最终可访问 `/healthz`。 ## 相关文件 - `scripts/dev-rust-stack.sh` -- `server-rs/.spacetimedb/local/` +- `server-rs/.spacetimedb/local/data/` diff --git a/scripts/dev-rust-stack.sh b/scripts/dev-rust-stack.sh index 367da582..200c5b4c 100644 --- a/scripts/dev-rust-stack.sh +++ b/scripts/dev-rust-stack.sh @@ -7,6 +7,7 @@ usage() { 用法: npm run dev:rust ./scripts/dev-rust-stack.sh --api-port 8090 --spacetime-port 3110 + ./scripts/dev-rust-stack.sh --spacetime-data-dir server-rs/.spacetimedb/local/data ./scripts/dev-rust-stack.sh --admin-web-port 3102 ./scripts/dev-rust-stack.sh --api-timeout-seconds 600 ./scripts/dev-rust-stack.sh --skip-spacetime --skip-publish @@ -18,7 +19,7 @@ usage() { 1. 默认同时启动 SpacetimeDB standalone、Rust api-server、主站 Vite 与后台 Vite。 2. 当前开发阶段默认 publish server-rs/crates/spacetime-module 时追加 -c=on-conflict 在结构冲突时清理旧模块数据。 3. 只有显式传入 --preserve-database 时,才会跳过 -c=on-conflict。 - 4. SpacetimeDB 默认使用 server-rs/.spacetimedb/local 作为本地数据与日志目录。 + 4. SpacetimeDB 默认使用 server-rs/.spacetimedb/local/data 作为本地数据目录。 5. 默认在发布模块前随机生成迁移引导密钥,注入 GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET 并显示在控制台。 EOF } @@ -71,18 +72,18 @@ cleanup() { wait_for_spacetime() { local server="$1" local timeout_seconds="$2" - local root_dir="$3" + local data_dir="$3" local process_pid="${4:-}" local deadline=$((SECONDS + timeout_seconds)) while ((SECONDS < deadline)); do if [[ -n "${process_pid}" ]] && ! kill -0 "${process_pid}" 2>/dev/null; then echo "[dev:rust] SpacetimeDB 进程在就绪前退出。" >&2 - print_spacetime_start_failure_diagnostics "${root_dir}" + print_spacetime_start_failure_diagnostics "${data_dir}" exit 1 fi - if is_spacetime_ready "${server}" "${root_dir}"; then + if is_spacetime_ready "${server}"; then return fi @@ -90,16 +91,15 @@ wait_for_spacetime() { done echo "[dev:rust] 等待 SpacetimeDB 就绪超时: ${server}" >&2 - print_spacetime_start_failure_diagnostics "${root_dir}" + print_spacetime_start_failure_diagnostics "${data_dir}" exit 1 } is_spacetime_ready() { local server="$1" - local root_dir="$2" local output - if output="$(spacetime --root-dir="${root_dir}" server ping "${server}" 2>&1)" && + if output="$(spacetime server ping "${server}" 2>&1)" && [[ "${output}" == *"Server is online:"* ]]; then return 0 fi @@ -119,10 +119,10 @@ request.on("error", () => process.exit(1)); } print_spacetime_start_failure_diagnostics() { - local root_dir="$1" - local log_file="${root_dir}/data/logs/spacetime-standalone.log" + local data_dir="$1" + local log_file="${data_dir}/logs/spacetime-standalone.log" - echo "[dev:rust] SpacetimeDB root-dir: ${root_dir}" >&2 + echo "[dev:rust] SpacetimeDB data-dir: ${data_dir}" >&2 if [[ ! -f "${log_file}" ]]; then echo "[dev:rust] 未找到 SpacetimeDB standalone 日志: ${log_file}" >&2 @@ -134,26 +134,26 @@ print_spacetime_start_failure_diagnostics() { if grep -q "mismatched database identity" "${log_file}" 2>/dev/null; then echo "[dev:rust] 检测到本地 replica 与当前数据库 identity 不一致。" >&2 - echo "[dev:rust] 常见原因是同一个 root-dir 保留了旧库 data/replicas/1,但 control-db 已指向新库。" >&2 - echo "[dev:rust] 若这是可丢弃的本地开发库,请先停止 SpacetimeDB,再备份或移走 ${root_dir}/data 后重新启动。" >&2 - echo "[dev:rust] 若需要保留数据,不要清理目录;请改回创建旧库的 database/root-dir,或先走迁移导出。" >&2 + echo "[dev:rust] 常见原因是同一个 data-dir 保留了旧库 replicas/1,但 control-db 已指向新库。" >&2 + echo "[dev:rust] 若这是可丢弃的本地开发库,请先停止 SpacetimeDB,再备份或移走 ${data_dir} 后重新启动。" >&2 + echo "[dev:rust] 若需要保留数据,不要清理目录;请改回创建旧库的 database/data-dir,或先走迁移导出。" >&2 fi } describe_spacetime_root_owner() { - local root_dir="$1" - local windows_root_dir="${root_dir}" + local data_dir="$1" + local windows_data_dir="${data_dir}" - if [[ "${windows_root_dir}" =~ ^/([a-zA-Z])/(.*)$ ]]; then - windows_root_dir="${BASH_REMATCH[1]}:/${BASH_REMATCH[2]}" + if [[ "${windows_data_dir}" =~ ^/([a-zA-Z])/(.*)$ ]]; then + windows_data_dir="${BASH_REMATCH[1]}:/${BASH_REMATCH[2]}" fi - # Windows 本地开发最常见的失败是同一个 root-dir 下已有 standalone 持有 spacetime.pid; + # Windows 本地开发最常见的失败是同一个 data-dir 下已有 standalone 持有 spacetime.pid; # 启动前先打印占用进程,避免用户只看到底层 os error 33 而不知道该停哪个实例。 if command -v powershell.exe >/dev/null 2>&1; then - ROOT_DIR_FOR_POWERSHELL="${windows_root_dir}" powershell.exe -NoProfile -Command ' -$rootDir = $env:ROOT_DIR_FOR_POWERSHELL -$normalized = $rootDir.Replace("/", "\") + DATA_DIR_FOR_POWERSHELL="${windows_data_dir}" powershell.exe -NoProfile -Command ' +$dataDir = $env:DATA_DIR_FOR_POWERSHELL +$normalized = $dataDir.Replace("/", "\") Get-CimInstance Win32_Process | Where-Object { $_.Name -match "spacetime" -and $_.CommandLine -and $_.CommandLine.Replace("/", "\") -like "*$normalized*" } | ForEach-Object { "pid=$($_.ProcessId) name=$($_.Name) command=$($_.CommandLine)" } @@ -162,7 +162,7 @@ Get-CimInstance Win32_Process | fi if command -v ps >/dev/null 2>&1; then - ps -eo user=,pid=,ppid=,stat=,comm=,args= 2>/dev/null | awk -v root_dir="${root_dir}" ' + ps -eo user=,pid=,ppid=,stat=,comm=,args= 2>/dev/null | awk -v data_dir="${data_dir}" ' { user = $1 pid = $2 @@ -175,7 +175,7 @@ Get-CimInstance Win32_Process | sub(/^.*\//, "", name) # 只认真实的 SpacetimeDB 启动进程,避免 .spacetimedb 路径让 grep/awk 自身误命中。 - if ((name == "spacetime" || name == "spacetimedb-cli") && index(args, root_dir) > 0) { + if ((name == "spacetime" || name == "spacetimedb-cli") && index(args, data_dir) > 0) { print user " " pid " " ppid " " stat " " name " " args } } @@ -216,54 +216,6 @@ request.on("error", () => process.exit(1)); exit 1 } -sync_local_spacetime_install() { - local root_dir="$1" - - # SpacetimeDB standalone 会在 --root-dir 下回调 bin/current/spacetimedb-cli; - # 使用工程内 root-dir 时,需要把用户级安装目录同步进来。WSL/Linux 下同样需要, - # 否则 spacetime start 会在空 root-dir 内查找 current/spacetimedb-cli 并启动失败。 - local target_cli="${root_dir}/bin/current/spacetimedb-cli" - local target_cli_exe="${root_dir}/bin/current/spacetimedb-cli.exe" - if [[ -f "${target_cli}" || -f "${target_cli_exe}" ]]; then - return - fi - - local spacetime_path - spacetime_path="$(command -v spacetime || true)" - if [[ -z "${spacetime_path}" ]]; then - return - fi - - local spacetime_info - spacetime_info="$(spacetime --version 2>/dev/null || true)" - - local cli_path="" - cli_path="$(printf '%s\n' "${spacetime_info}" | sed -n 's/^spacetime Path: //p' | head -n 1)" - if [[ -z "${cli_path}" || ! -f "${cli_path}" ]]; then - cli_path="${spacetime_path}" - fi - - local version_dir - version_dir="$(cd -- "$(dirname -- "${cli_path}")" && pwd)" - if [[ ! -d "${version_dir}" ]]; then - return - fi - - echo "[dev:rust] 同步本机 SpacetimeDB 安装到 ${root_dir}" - mkdir -p "${root_dir}/bin" - cp -a "${version_dir}" "${root_dir}/bin/" - local version_name - version_name="$(basename -- "${version_dir}")" - rm -rf "${root_dir}/bin/current" - ln -s "${version_name}" "${root_dir}/bin/current" - - # Windows/Git Bash 安装可能还需要根目录 spacetime.exe;WSL/Linux 下通常没有该文件。 - local install_root - install_root="$(cd -- "${version_dir}/../.." && pwd)" - if [[ -f "${install_root}/spacetime.exe" ]]; then - cp -f "${install_root}/spacetime.exe" "${root_dir}/spacetime.exe" - fi -} generate_migration_bootstrap_secret() { node -e 'const crypto = require("crypto"); process.stdout.write(crypto.randomBytes(32).toString("hex"));' @@ -312,6 +264,7 @@ ADMIN_WEB_PORT="3102" SPACETIME_HOST="127.0.0.1" SPACETIME_PORT="3101" SPACETIME_ROOT_DIR="${SERVER_RS_DIR}/.spacetimedb/local" +SPACETIME_DATA_DIR="${SPACETIME_ROOT_DIR}/data" DATABASE="" API_LOG="info,tower_http=info" SPACETIME_TIMEOUT_SECONDS="60" @@ -385,6 +338,11 @@ while [[ $# -gt 0 ]]; do ;; --spacetime-root-dir) SPACETIME_ROOT_DIR="${2:?缺少 --spacetime-root-dir 的值}" + SPACETIME_DATA_DIR="${SPACETIME_ROOT_DIR}/data" + shift 2 + ;; + --spacetime-data-dir) + SPACETIME_DATA_DIR="${2:?缺少 --spacetime-data-dir 的值}" shift 2 ;; --database) @@ -486,17 +444,17 @@ echo "[dev:rust] rust api: ${RUST_SERVER_TARGET}" echo "[dev:rust] spacetime: ${SPACETIME_SERVER}" echo "[dev:rust] database: ${DATABASE}" echo "[dev:rust] spacetime root: ${SPACETIME_ROOT_DIR}" +echo "[dev:rust] spacetime data: ${SPACETIME_DATA_DIR}" echo "[dev:rust] api timeout: ${API_SERVER_TIMEOUT_SECONDS}s" if [[ "${SKIP_SPACETIME}" -ne 1 ]]; then - mkdir -p "${SPACETIME_ROOT_DIR}" - sync_local_spacetime_install "${SPACETIME_ROOT_DIR}" - if is_spacetime_ready "${SPACETIME_SERVER}" "${SPACETIME_ROOT_DIR}"; then + mkdir -p "${SPACETIME_ROOT_DIR}" "${SPACETIME_DATA_DIR}" + if is_spacetime_ready "${SPACETIME_SERVER}"; then echo "[dev:rust] 复用已运行的 SpacetimeDB: ${SPACETIME_SERVER}" else - SPACETIME_ROOT_OWNER="$(describe_spacetime_root_owner "${SPACETIME_ROOT_DIR}")" + SPACETIME_ROOT_OWNER="$(describe_spacetime_root_owner "${SPACETIME_DATA_DIR}")" if [[ -n "${SPACETIME_ROOT_OWNER}" ]]; then - echo "[dev:rust] 当前 root-dir 已被其他 SpacetimeDB 实例占用,无法再次启动。" >&2 + echo "[dev:rust] 当前 data-dir 已被其他 SpacetimeDB 实例占用,无法再次启动。" >&2 echo "[dev:rust] 目标地址未就绪: ${SPACETIME_SERVER}" >&2 echo "[dev:rust] 如需复用,请传入占用实例实际端口,例如 --spacetime-port 3199;如需重启,请先停止下列进程。" >&2 echo "${SPACETIME_ROOT_OWNER}" >&2 @@ -506,9 +464,9 @@ if [[ "${SKIP_SPACETIME}" -ne 1 ]]; then ( cd "${SERVER_RS_DIR}" exec spacetime \ - --root-dir="${SPACETIME_ROOT_DIR}" \ start \ - --edition standalone \ + --data-dir "${SPACETIME_DATA_DIR}" \ + --non-interactive \ --listen-addr "${SPACETIME_HOST}:${SPACETIME_PORT}" ) & PIDS+=("$!") @@ -518,7 +476,7 @@ fi if [[ "${SKIP_PUBLISH}" -ne 1 ]]; then echo "[dev:rust] 等待 SpacetimeDB 就绪" - wait_for_spacetime "${SPACETIME_SERVER}" "${SPACETIME_TIMEOUT_SECONDS}" "${SPACETIME_ROOT_DIR}" "${PIDS[0]:-}" + wait_for_spacetime "${SPACETIME_SERVER}" "${SPACETIME_TIMEOUT_SECONDS}" "${SPACETIME_DATA_DIR}" "${PIDS[0]:-}" prepare_migration_bootstrap_secret PUBLISH_ARGS=(