master #14
@@ -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 <path>`。
|
||||
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/`
|
||||
|
||||
@@ -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=(
|
||||
|
||||
Reference in New Issue
Block a user