fix(dev): accept spacetime port fallback
Some checks failed
CI / verify (pull_request) Has been cancelled
Some checks failed
CI / verify (pull_request) Has been cancelled
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
# `npm run dev` / `scripts/dev-rust-stack.sh` 启动修复记录
|
# `npm run dev` / `scripts/dev-rust-stack.sh` 启动修复记录
|
||||||
|
|
||||||
## 症状
|
## 症状
|
||||||
- `npm run dev` 的 SpacetimeDB standalone 启动需要把数据留在项目本地,避免污染用户级 SpacetimeDB 数据目录。
|
- 多个 worktree 同时本地开发时,SpacetimeDB 数据库名可能相同,早期用项目级 `--root-dir` 隔离 CLI 状态来规避冲突。
|
||||||
|
- 实测后确认:真正需要隔离的是 standalone 的 `data-dir`,不需要把 publish 也绑到项目级 root-dir。
|
||||||
- 早期脚本曾通过把用户级 SpacetimeDB 可执行文件目录同步到 `server-rs/.spacetimedb/local/bin/current` 来满足 standalone 回调需求,但这会把整套可执行文件复制进项目本地目录,维护成本高,也容易和用户级 CLI 版本漂移。
|
- 早期脚本曾通过把用户级 SpacetimeDB 可执行文件目录同步到 `server-rs/.spacetimedb/local/bin/current` 来满足 standalone 回调需求,但这会把整套可执行文件复制进项目本地目录,维护成本高,也容易和用户级 CLI 版本漂移。
|
||||||
|
- 多个 worktree 同时启动时,SpacetimeDB 端口可能冲突;CLI 会询问是否使用最近的可用端口。
|
||||||
- `api-server` 首次冷编译时,默认 300 秒超时不够,容易在就绪前被回收。
|
- `api-server` 首次冷编译时,默认 300 秒超时不够,容易在就绪前被回收。
|
||||||
|
|
||||||
## 当前方案
|
## 当前方案
|
||||||
@@ -11,12 +13,17 @@
|
|||||||
- `spacetime start` 不再通过工程内 `--root-dir` 寻找可执行文件。
|
- `spacetime start` 不再通过工程内 `--root-dir` 寻找可执行文件。
|
||||||
2. 数据目录显式指定到项目本地
|
2. 数据目录显式指定到项目本地
|
||||||
- 默认 `SPACETIME_DATA_DIR=${SERVER_RS_DIR}/.spacetimedb/local/data`。
|
- 默认 `SPACETIME_DATA_DIR=${SERVER_RS_DIR}/.spacetimedb/local/data`。
|
||||||
- 启动命令使用 `spacetime start --data-dir "${SPACETIME_DATA_DIR}" --non-interactive --listen-addr ...`。
|
- 启动命令使用 `spacetime start --data-dir "${SPACETIME_DATA_DIR}" --listen-addr ...`。
|
||||||
- 如需临时切换数据目录,可传 `--spacetime-data-dir <path>`。
|
- 如需临时切换数据目录,可传 `--spacetime-data-dir <path>`。
|
||||||
3. CLI 身份/登录状态仍保留在 root-dir
|
3. 端口冲突时自动接受 SpacetimeDB 建议端口
|
||||||
- 发布模块和 CLI 管理命令继续使用 `spacetime --root-dir="${SPACETIME_ROOT_DIR}" publish ...`。
|
- 启动时不传 `--non-interactive`。
|
||||||
- 这样可以保留项目级 CLI 登录/token 隔离,同时不再把可执行文件复制到 root-dir。
|
- 脚本向 `spacetime start` 发送回车,接受“最近可用端口”的默认建议。
|
||||||
4. 提高 api-server 就绪等待时间
|
- 随后从启动日志中的 `Starting SpacetimeDB listening on ...` 解析实际端口。
|
||||||
|
- 解析出的实际端口会覆盖 `SPACETIME_SERVER`,后续 publish、api-server、前端代理统一使用这个端口。
|
||||||
|
4. publish 不再使用项目级 root-dir
|
||||||
|
- 发布模块改为 `spacetime publish ... --server "${SPACETIME_SERVER}" ...`。
|
||||||
|
- 这样 publish 使用用户级 CLI 默认身份/配置,不再依赖 worktree 内 root-dir。
|
||||||
|
5. 提高 api-server 就绪等待时间
|
||||||
- `API_SERVER_TIMEOUT_SECONDS` 保持 600,降低首次冷编译误判失败概率。
|
- `API_SERVER_TIMEOUT_SECONDS` 保持 600,降低首次冷编译误判失败概率。
|
||||||
|
|
||||||
## 复现 / 验证
|
## 复现 / 验证
|
||||||
@@ -25,7 +32,8 @@
|
|||||||
- 运行 `npm run dev` 后观察日志:
|
- 运行 `npm run dev` 后观察日志:
|
||||||
- 输出 `spacetime data: .../server-rs/.spacetimedb/local/data`。
|
- 输出 `spacetime data: .../server-rs/.spacetimedb/local/data`。
|
||||||
- 不再出现同步/复制本机 SpacetimeDB 安装到项目 root-dir 的日志。
|
- 不再出现同步/复制本机 SpacetimeDB 安装到项目 root-dir 的日志。
|
||||||
- SpacetimeDB 能正常监听 `127.0.0.1:3101`。
|
- SpacetimeDB 能正常监听,并输出 `spacetime actual: http://127.0.0.1:<实际端口>`。
|
||||||
|
- 若默认端口被占用,脚本应自动接受 SpacetimeDB 建议端口,并用实际端口发布模块、启动 api-server 和前端代理。
|
||||||
- 模块发布成功。
|
- 模块发布成功。
|
||||||
- api-server 进入健康检查等待并最终可访问 `/healthz`。
|
- api-server 进入健康检查等待并最终可访问 `/healthz`。
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ usage() {
|
|||||||
1. 默认同时启动 SpacetimeDB standalone、Rust api-server、主站 Vite 与后台 Vite。
|
1. 默认同时启动 SpacetimeDB standalone、Rust api-server、主站 Vite 与后台 Vite。
|
||||||
2. 当前开发阶段默认 publish server-rs/crates/spacetime-module 时追加 -c=on-conflict 在结构冲突时清理旧模块数据。
|
2. 当前开发阶段默认 publish server-rs/crates/spacetime-module 时追加 -c=on-conflict 在结构冲突时清理旧模块数据。
|
||||||
3. 只有显式传入 --preserve-database 时,才会跳过 -c=on-conflict。
|
3. 只有显式传入 --preserve-database 时,才会跳过 -c=on-conflict。
|
||||||
4. SpacetimeDB 默认使用 server-rs/.spacetimedb/local/data 作为本地数据目录。
|
4. SpacetimeDB 默认使用 server-rs/.spacetimedb/local/data 作为本地数据目录;端口被占用时自动接受 SpacetimeDB 建议的最近可用端口。
|
||||||
5. 默认在发布模块前随机生成迁移引导密钥,注入 GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET 并显示在控制台。
|
5. 默认在发布模块前随机生成迁移引导密钥,注入 GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET 并显示在控制台。
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
@@ -95,6 +95,47 @@ wait_for_spacetime() {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wait_for_spacetime_listen_addr() {
|
||||||
|
local log_file="$1"
|
||||||
|
local timeout_seconds="$2"
|
||||||
|
local process_pid="${3:-}"
|
||||||
|
local deadline=$((SECONDS + timeout_seconds))
|
||||||
|
local listen_addr=""
|
||||||
|
|
||||||
|
while ((SECONDS < deadline)); do
|
||||||
|
if [[ -f "${log_file}" ]]; then
|
||||||
|
listen_addr="$(sed -n 's/^.*Starting SpacetimeDB listening on \([^[:space:]]\+\).*$/\1/p' "${log_file}" | tail -n 1)"
|
||||||
|
if [[ -n "${listen_addr}" ]]; then
|
||||||
|
echo "${listen_addr}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${process_pid}" ]] && ! kill -0 "${process_pid}" 2>/dev/null; then
|
||||||
|
echo "[dev:rust] SpacetimeDB 进程在输出监听地址前退出。" >&2
|
||||||
|
if [[ -f "${log_file}" ]]; then
|
||||||
|
echo "[dev:rust] 最近 SpacetimeDB 启动日志: ${log_file}" >&2
|
||||||
|
tail -n 80 "${log_file}" >&2 || true
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 0.2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "[dev:rust] 等待 SpacetimeDB 输出监听地址超时。" >&2
|
||||||
|
if [[ -f "${log_file}" ]]; then
|
||||||
|
echo "[dev:rust] 最近 SpacetimeDB 启动日志: ${log_file}" >&2
|
||||||
|
tail -n 80 "${log_file}" >&2 || true
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
port_from_listen_addr() {
|
||||||
|
local listen_addr="$1"
|
||||||
|
echo "${listen_addr##*:}"
|
||||||
|
}
|
||||||
|
|
||||||
is_spacetime_ready() {
|
is_spacetime_ready() {
|
||||||
local server="$1"
|
local server="$1"
|
||||||
local output
|
local output
|
||||||
@@ -150,7 +191,9 @@ describe_spacetime_root_owner() {
|
|||||||
|
|
||||||
# Windows 本地开发最常见的失败是同一个 data-dir 下已有 standalone 持有 spacetime.pid;
|
# Windows 本地开发最常见的失败是同一个 data-dir 下已有 standalone 持有 spacetime.pid;
|
||||||
# 启动前先打印占用进程,避免用户只看到底层 os error 33 而不知道该停哪个实例。
|
# 启动前先打印占用进程,避免用户只看到底层 os error 33 而不知道该停哪个实例。
|
||||||
if command -v powershell.exe >/dev/null 2>&1; then
|
# 只有 Windows/Git Bash 风格路径才交给 PowerShell 查 Windows 进程;
|
||||||
|
# WSL/Linux 的 /tmp、/home 路径不能直接拿去匹配 Windows CommandLine,容易误命中无关 spacetime 进程。
|
||||||
|
if command -v powershell.exe >/dev/null 2>&1 && [[ "${data_dir}" =~ ^/([a-zA-Z])/ ]]; then
|
||||||
DATA_DIR_FOR_POWERSHELL="${windows_data_dir}" powershell.exe -NoProfile -Command '
|
DATA_DIR_FOR_POWERSHELL="${windows_data_dir}" powershell.exe -NoProfile -Command '
|
||||||
$dataDir = $env:DATA_DIR_FOR_POWERSHELL
|
$dataDir = $env:DATA_DIR_FOR_POWERSHELL
|
||||||
$normalized = $dataDir.Replace("/", "\")
|
$normalized = $dataDir.Replace("/", "\")
|
||||||
@@ -449,29 +492,34 @@ echo "[dev:rust] api timeout: ${API_SERVER_TIMEOUT_SECONDS}s"
|
|||||||
|
|
||||||
if [[ "${SKIP_SPACETIME}" -ne 1 ]]; then
|
if [[ "${SKIP_SPACETIME}" -ne 1 ]]; then
|
||||||
mkdir -p "${SPACETIME_ROOT_DIR}" "${SPACETIME_DATA_DIR}"
|
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_DATA_DIR}")"
|
SPACETIME_ROOT_OWNER="$(describe_spacetime_root_owner "${SPACETIME_DATA_DIR}")"
|
||||||
if [[ -n "${SPACETIME_ROOT_OWNER}" ]]; then
|
if [[ -n "${SPACETIME_ROOT_OWNER}" ]]; then
|
||||||
echo "[dev:rust] 当前 data-dir 已被其他 SpacetimeDB 实例占用,无法再次启动。" >&2
|
echo "[dev:rust] 当前 data-dir 已被其他 SpacetimeDB 实例占用,无法再次启动。" >&2
|
||||||
echo "[dev:rust] 目标地址未就绪: ${SPACETIME_SERVER}" >&2
|
echo "[dev:rust] 如需复用,请传入占用实例实际端口并追加 --skip-spacetime;如需重启,请先停止下列进程。" >&2
|
||||||
echo "[dev:rust] 如需复用,请传入占用实例实际端口,例如 --spacetime-port 3199;如需重启,请先停止下列进程。" >&2
|
|
||||||
echo "${SPACETIME_ROOT_OWNER}" >&2
|
echo "${SPACETIME_ROOT_OWNER}" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
SPACETIME_START_LOG="${SPACETIME_DATA_DIR}/logs/dev-rust-spacetime-start.log"
|
||||||
|
mkdir -p "$(dirname -- "${SPACETIME_START_LOG}")"
|
||||||
|
: >"${SPACETIME_START_LOG}"
|
||||||
echo "[dev:rust] 启动 spacetimedb"
|
echo "[dev:rust] 启动 spacetimedb"
|
||||||
(
|
(
|
||||||
cd "${SERVER_RS_DIR}"
|
cd "${SERVER_RS_DIR}"
|
||||||
exec spacetime \
|
# 当目标端口被占用时,SpacetimeDB 会询问是否使用最近的可用端口;
|
||||||
|
# 这里直接发送回车接受默认建议,再从启动日志解析实际监听端口。
|
||||||
|
printf '\n' | spacetime \
|
||||||
start \
|
start \
|
||||||
--data-dir "${SPACETIME_DATA_DIR}" \
|
--data-dir "${SPACETIME_DATA_DIR}" \
|
||||||
--non-interactive \
|
|
||||||
--listen-addr "${SPACETIME_HOST}:${SPACETIME_PORT}"
|
--listen-addr "${SPACETIME_HOST}:${SPACETIME_PORT}"
|
||||||
) &
|
) 2>&1 | tee "${SPACETIME_START_LOG}" &
|
||||||
PIDS+=("$!")
|
PIDS+=("$!")
|
||||||
NAMES+=("spacetimedb")
|
NAMES+=("spacetimedb")
|
||||||
fi
|
|
||||||
|
SPACETIME_LISTEN_ADDR="$(wait_for_spacetime_listen_addr "${SPACETIME_START_LOG}" "${SPACETIME_TIMEOUT_SECONDS}" "${PIDS[0]:-}")"
|
||||||
|
SPACETIME_PORT="$(port_from_listen_addr "${SPACETIME_LISTEN_ADDR}")"
|
||||||
|
SPACETIME_SERVER="http://${SPACETIME_HOST}:${SPACETIME_PORT}"
|
||||||
|
echo "[dev:rust] spacetime actual: ${SPACETIME_SERVER}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${SKIP_PUBLISH}" -ne 1 ]]; then
|
if [[ "${SKIP_PUBLISH}" -ne 1 ]]; then
|
||||||
@@ -493,7 +541,7 @@ if [[ "${SKIP_PUBLISH}" -ne 1 ]]; then
|
|||||||
PUBLISH_ARGS+=(--yes)
|
PUBLISH_ARGS+=(--yes)
|
||||||
|
|
||||||
echo "[dev:rust] 发布 SpacetimeDB 模块: ${DATABASE}"
|
echo "[dev:rust] 发布 SpacetimeDB 模块: ${DATABASE}"
|
||||||
spacetime --root-dir="${SPACETIME_ROOT_DIR}" "${PUBLISH_ARGS[@]}"
|
spacetime "${PUBLISH_ARGS[@]}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[dev:rust] 启动 api-server"
|
echo "[dev:rust] 启动 api-server"
|
||||||
|
|||||||
Reference in New Issue
Block a user