diff --git a/docs/technical/JENKINS_RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md b/docs/technical/JENKINS_RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md index d48609bc..d653c97d 100644 --- a/docs/technical/JENKINS_RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md +++ b/docs/technical/JENKINS_RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md @@ -101,7 +101,7 @@ scripts/jenkins-deploy-release.sh \ 如果 `RUN_DEPLOY_HOOKS_WITH_SUDO=true`,第 1 步和第 4 步会改为 `sudo -n` 调用;这要求 Jenkins 运行用户提前配置免密 sudo,否则部署会直接失败,不会进入交互式密码提示。 -这样可以满足“发布文件直接覆盖”的要求,同时保留部署目录里像 `spacetimedb-data/`、`logs/`、`run/` 这类运行态目录,不会因为部署被整体删除。发布白名单内的 `.env`、`.env.local` 会先以构建产物中的文件为准;部署脚本会在启动 hook 前移除这些环境文件中的 UTF-8 BOM 与 CRLF,并把 Jenkins 部署参数 `WEB_PORT` 写入 `.env.local` 的 `GENARRATIVE_WEB_PORT`,避免 `start.sh` 在 Bash 下把首行变量名误解析成命令,也避免端口配置只停留在上游构建阶段。 +这样可以满足“发布文件直接覆盖”的要求,同时保留部署目录里像 `.spacetimedb/`、`logs/`、`run/` 这类运行态目录,不会因为部署被整体删除。发布白名单内的 `.env`、`.env.local` 会先以构建产物中的文件为准;部署脚本会在启动 hook 前移除这些环境文件中的 UTF-8 BOM 与 CRLF,并把 Jenkins 部署参数 `WEB_PORT` 写入 `.env.local` 的 `GENARRATIVE_WEB_PORT`,避免 `start.sh` 在 Bash 下把首行变量名误解析成命令,也避免端口配置只停留在上游构建阶段。`start.sh` 会先执行 Ubuntu 专用 `sync_ubuntu_spacetime_install`,优先从 `/usr/.local/share/spacetime/bin//spacetimedb-cli` 或 `$HOME/.local/share/spacetime/bin//spacetimedb-cli` 同步到部署目录 `.spacetimedb/bin/current/spacetimedb-cli`,后续启动、探活和 root-dir 占用判定都使用部署目录内 `.spacetimedb/`,且不再额外设置 `--data-dir`,避免 Jenkins 机器全局 `spacetime login` 变化影响本地库更新;如遇 `403 Forbidden`,按 `SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md` 排查数据库所有者与 CLI 身份。 ### 4.3 构建并部署 diff --git a/docs/technical/README.md b/docs/technical/README.md index 9e61e28c..e96ef0a5 100644 --- a/docs/technical/README.md +++ b/docs/technical/README.md @@ -6,6 +6,7 @@ - [RPG_AND_AGENT_CHAT_TRUE_SSE_STREAMING_2026-04-26.md](./RPG_AND_AGENT_CHAT_TRUE_SSE_STREAMING_2026-04-26.md):记录 RPG 运行时 NPC 聊天、RPG/自定义世界 Agent 与大鱼 Agent 从“拼完整 SSE 字符串后一次性返回”改为 `mpsc + Sse` 真流式输出的后端落地口径。 - [RPG_BATTLE_HEALTHBAR_AND_ACTION_PRESENTATION_FIX_2026-04-26.md](./RPG_BATTLE_HEALTHBAR_AND_ACTION_PRESENTATION_FIX_2026-04-26.md):记录 RPG 战斗血条安全锚点、服务端战斗回包前端短表现,以及 `battle_use_skill` 指定技能兜底结算的修复口径。 +- [SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md](./SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md):记录发布包 `start.sh` 执行 `spacetime publish` 遇到 `403 Forbidden` 的身份根因、`.spacetimedb/` root-dir 隔离修复和排查步骤。 - [SPACETIMEDB_TABLE_CATALOG.md](./SPACETIMEDB_TABLE_CATALOG.md):持续维护当前 SpacetimeDB 表目录,按领域说明每张表的作用、字段结构、索引和常用 `spacetime sql` 查询模板。 - [RPG_OPENING_SCENE_ACT_IMAGE_PRESENTATION_SYNC_2026-04-26.md](./RPG_OPENING_SCENE_ACT_IMAGE_PRESENTATION_SYNC_2026-04-26.md):记录开局场景与普通场景复用同一场景展示解析服务,修复列表幕缩略图和详情幕背景预览图片不一致的问题。 - [FRONTEND_FIRST_LOAD_PERFORMANCE_FIX_2026-04-26.md](./FRONTEND_FIRST_LOAD_PERFORMANCE_FIX_2026-04-26.md):记录网站启动后首次加载约三分钟的前端根因,收口 `RouteImageReadyGate` 首屏图片门控和 Vite dev server 无关文件监听范围。 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 3a8c0dd8..4d2c9a01 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 @@ -142,7 +142,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/` 下;复制后统一移除 UTF-8 BOM 与 CRLF,避免目标服务器 Bash 加载环境文件失败。 7. 在目标目录写入 `web-server.mjs`,用于托管 `web/` 并把 `/api/*`、`/generated-*`、`/healthz` 反代到本包内的 `api-server`。 -8. 在目标目录写入 `start.sh` 与 `stop.sh`;`start.sh` 会先按 `KEY=value` 子集加载发布目录根部的 `.env`、`.env.local`,兼容 UTF-8 BOM 与 CRLF,再回退到构建时通过 `--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 冲突时删除旧模块数据。 +8. 在目标目录写入 `start.sh` 与 `stop.sh`;`start.sh` 会先按 `KEY=value` 子集加载发布目录根部的 `.env`、`.env.local`,兼容 UTF-8 BOM 与 CRLF,再回退到构建时通过 `--database`、`--api-port`、`--web-port`、`--spacetime-host`、`--spacetime-port` 写入的默认值,并默认导出 `NO_COLOR=1` 与 `CARGO_TERM_COLOR=never`,避免 ANSI 控制码写入日志文件;同时按 Ubuntu 发布环境使用发布目录内 `.spacetimedb/` 作为 root-dir,不再额外设置 `--data-dir`,启动前先执行 `sync_ubuntu_spacetime_install`,优先从 `/usr/.local/share/spacetime/bin//spacetimedb-cli` 或 `$HOME/.local/share/spacetime/bin//spacetimedb-cli` 同步到 `.spacetimedb/bin/current/spacetimedb-cli`,当前线上 `spacetime` 入口为 `/usr/local/bin/spacetime`;启动参数为 `spacetime --root-dir ./.spacetimedb start --edition standalone --listen-addr :`,探活必须确认 `server ping` 输出包含 `Server is online:`;如果以 `--clear-database` 启动,则内部 `spacetime publish` 会追加 `-c=on-conflict`,仅在 schema 冲突时删除旧模块数据。 9. 默认执行 `scp -r -i ~\.ssh\dsk.pem build/ ubuntu@82.157.175.59:/home/ubuntu/genarrative/` 上传发布包。 发布包结构: @@ -178,7 +178,7 @@ cd build/ ./stop.sh ``` -如果后续通过 Jenkins 的部署脚本把发布包覆盖到固定部署目录,部署阶段默认只替换 `web/`、`api-server`、`spacetime_module.wasm`、`.env*`、`start.sh`、`stop.sh`、`web-server.mjs`、`README.md` 等发布产物,不会删除部署目录中的 `spacetimedb-data/`、`logs/`、`run/` 这类运行态目录。 +如果后续通过 Jenkins 的部署脚本把发布包覆盖到固定部署目录,部署阶段默认只替换 `web/`、`api-server`、`spacetime_module.wasm`、`.env*`、`start.sh`、`stop.sh`、`web-server.mjs`、`README.md` 等发布产物,不会删除部署目录中的 `.spacetimedb/`、`logs/`、`run/` 这类运行态目录。 安全边界: @@ -187,8 +187,10 @@ cd build/ 3. `start.sh` 只解析合法 `KEY=value` 环境行,支持不加引号、双引号和单引号;不执行复杂 shell 表达式,避免把环境文件变成脚本入口。 4. `start.sh` 默认不追加清理参数;只有显式执行 `./start.sh --clear-database` 才追加 `-c=on-conflict`,在 schema 冲突时清理旧模块数据后重发。 5. `start.sh` 使用 `spacetime publish --bin-path spacetime_module.wasm --yes` 发布当前包内 wasm;清库模式下会追加 `-c=on-conflict`,仅在 schema 冲突时删除旧模块数据。 -6. 当前脚本是单目录进程启动方案,不替代生产 systemd、Nginx、TLS、日志轮转与守护进程配置。 -7. 如只需要本地生成发布包,可传 `--skip-upload` 跳过默认 scp 上传。 +6. `start.sh` 会先复用已经按目标地址就绪的 SpacetimeDB;如果同一个 `.spacetimedb/` root-dir 已被其他未就绪实例占用,则按 dev 脚本逻辑输出占用进程并失败,避免误连错端口。 +7. 如果 `spacetime publish` 报 `403 Forbidden`,优先确认 `spacetime --root-dir ./.spacetimedb login show` 输出的身份是否有权更新目标库;`--clear-database` 不能绕过身份授权。 +8. 当前脚本是单目录进程启动方案,不替代生产 systemd、Nginx、TLS、日志轮转与守护进程配置。 +9. 如只需要本地生成发布包,可传 `--skip-upload` 跳过默认 scp 上传。 目标服务器最小要求: diff --git a/docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md b/docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md new file mode 100644 index 00000000..e8fdd394 --- /dev/null +++ b/docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md @@ -0,0 +1,75 @@ +# start.sh 发布 SpacetimeDB 遇到 403 的处理方案 + +日期:`2026-04-26` + +## 1. 问题 + +执行发布包内 `start.sh` 时,`spacetime publish` 可能在 `Checking for breaking changes...` 后失败: + +```text +Error: Pre-publish check failed with status 403 Forbidden: is not authorized to perform action on database : update database +``` + +这不是 wasm 构建失败,也不是 schema 冲突。错误含义是:当前 `spacetime` CLI 使用的身份无权更新目标数据库。 + +## 2. 根因 + +发布包 `start.sh` 会启动本地 SpacetimeDB,再把当前包内的 `spacetime_module.wasm` 发布到 `GENARRATIVE_SPACETIME_DATABASE`。 + +SpacetimeDB 的数据库更新权限绑定到创建或被授权的身份。只要出现以下情况之一,就会触发 403: + +1. 部署机上执行 `start.sh` 的用户切换过 `spacetime login` 身份。 +2. 固定部署目录保留了旧 `.spacetimedb/`,但当前 CLI 身份不是旧数据库创建者。 +3. `GENARRATIVE_SPACETIME_SERVER_URL` 指向 Maincloud,而当前 CLI 身份不是该 Maincloud 数据库的所有者或授权成员。 +4. `.env.local` 中的 `GENARRATIVE_SPACETIME_DATABASE` 指向了另一个环境的数据库名或数据库 identity。 + +## 3. 落地修复 + +发布包生成的 `start.sh` 使用发布目录下的 `.spacetimedb/` 作为 SpacetimeDB root: + +```bash +GENARRATIVE_SPACETIME_ROOT_DIR="${SCRIPT_DIR}/.spacetimedb" +``` + +启动、探活和发布统一使用: + +```bash +spacetime --root-dir="${GENARRATIVE_SPACETIME_ROOT_DIR}" ... +``` + +`spacetime start` 不再额外设置 `--data-dir`,启动前会先执行 Ubuntu 专用 `sync_ubuntu_spacetime_install`,优先从 `/usr/.local/share/spacetime/bin//spacetimedb-cli` 或 `$HOME/.local/share/spacetime/bin//spacetimedb-cli` 同步到 `.spacetimedb/bin/current/spacetimedb-cli`;当前线上 `spacetime` 入口为 `/usr/local/bin/spacetime`。启动参数、探活和 root-dir 占用判定都使用同一个 `.spacetimedb/`。这样可以把发布包与部署机全局 `~/.spacetime` 隔离,避免后续人工 `spacetime login` 影响本地发布包。但如果旧 `.spacetimedb/` 已经由另一个身份创建,仍需要按第 4 节处理。 + +## 4. 排查与处理 + +先在执行 `start.sh` 的同一台机器、同一用户下确认身份: + +```bash +spacetime --root-dir ./.spacetimedb login show +spacetime --root-dir ./.spacetimedb list --server http://127.0.0.1:3101 +``` + +如果目标是本地部署库,且允许清空本地数据: + +```bash +./stop.sh +mv .spacetimedb ".spacetimedb.backup.$(date +%Y%m%d-%H%M%S)" +./start.sh +``` + +如果目标是本地部署库,但必须保留数据: + +1. 不要删除 `.spacetimedb/`。 +2. 找到创建该数据库的 SpacetimeDB 身份。 +3. 用该身份对应的 CLI root 执行发布,或在 SpacetimeDB 侧补授权后再发布。 + +如果目标是 Maincloud: + +1. 执行 `spacetime login show` 确认当前身份。 +2. 确认该身份对 `GENARRATIVE_SPACETIME_DATABASE` 有更新权限。 +3. 如果只是连错库,修正 `.env.local` 中的 `GENARRATIVE_SPACETIME_DATABASE` / `GENARRATIVE_SPACETIME_SERVER_URL`。 + +## 5. 约束 + +1. `--clear-database` 只处理 schema 冲突时的数据清理,不会绕过 SpacetimeDB 身份授权。 +2. 不要通过切回旧 `server-node` 或 PostgreSQL 绕过发布错误。 +3. 前端与 `api-server` 的数据库名必须和 `start.sh` 发布的库名一致,否则后续接口会连到未发布或无权限的库。 diff --git a/scripts/deploy-rust-remote.sh b/scripts/deploy-rust-remote.sh index cdda41ed..7e60da0a 100644 --- a/scripts/deploy-rust-remote.sh +++ b/scripts/deploy-rust-remote.sh @@ -518,11 +518,12 @@ done load_env_file "${SCRIPT_DIR}/.env" load_env_file "${SCRIPT_DIR}/.env.local" -SPACETIME_DATA_DIR="${GENARRATIVE_SPACETIME_DATA_DIR:-${SCRIPT_DIR}/spacetimedb-data}" +SPACETIME_ROOT_DIR="${GENARRATIVE_SPACETIME_ROOT_DIR:-${SCRIPT_DIR}/.spacetimedb}" SPACETIME_HOST="${GENARRATIVE_SPACETIME_HOST:-__GENARRATIVE_DEFAULT_SPACETIME_HOST__}" SPACETIME_PORT="${GENARRATIVE_SPACETIME_PORT:-__GENARRATIVE_DEFAULT_SPACETIME_PORT__}" SPACETIME_SERVER_URL="${GENARRATIVE_SPACETIME_SERVER_URL:-http://${SPACETIME_HOST}:${SPACETIME_PORT}}" SPACETIME_DATABASE="${GENARRATIVE_SPACETIME_DATABASE:-__GENARRATIVE_DEFAULT_SPACETIME_DATABASE__}" +SPACETIME_TIMEOUT_SECONDS="${GENARRATIVE_SPACETIME_TIMEOUT_SECONDS:-60}" API_HOST="${GENARRATIVE_API_HOST:-__GENARRATIVE_DEFAULT_API_HOST__}" API_PORT="${GENARRATIVE_API_PORT:-__GENARRATIVE_DEFAULT_API_PORT__}" API_LOG="${GENARRATIVE_API_LOG:-info,tower_http=info}" @@ -543,12 +544,19 @@ require_command() { } wait_for_spacetime() { - local deadline=$((SECONDS + 60)) + local process_pid="${1:-}" + local deadline=$((SECONDS + SPACETIME_TIMEOUT_SECONDS)) while ((SECONDS < deadline)); do - if spacetime server ping "${SPACETIME_SERVER_URL}" >/dev/null 2>&1; then + if [[ -n "${process_pid}" ]] && ! kill -0 "${process_pid}" 2>/dev/null; then + echo "[start] SpacetimeDB 进程在就绪前退出。" >&2 + exit 1 + fi + + if is_spacetime_ready; then return fi + sleep 0.5 done @@ -556,6 +564,98 @@ wait_for_spacetime() { exit 1 } +is_spacetime_ready() { + local output + + if ! output="$(spacetime --root-dir="${SPACETIME_ROOT_DIR}" server ping "${SPACETIME_SERVER_URL}" 2>&1)"; then + return 1 + fi + + # SpacetimeDB CLI 2.1.0 在 502 Bad Gateway 时仍可能返回 0,不能只依赖退出码。 + [[ "${output}" == *"Server is online:"* ]] +} + +describe_spacetime_root_owner() { + if command -v ps >/dev/null 2>&1; then + ps -ef 2>/dev/null | grep '[s]pacetime' | grep -F "${SPACETIME_ROOT_DIR}" || true + fi +} + +sync_ubuntu_spacetime_install() { + local root_dir="$1" + local target_cli="${root_dir}/bin/current/spacetimedb-cli" + local spacetime_command="" + local resolved_command="" + local install_dir="" + local root_bin="${root_dir}/bin" + local parent_dir="" + local share_bin_dir="" + local version_dir="" + + if [[ -x "${target_cli}" ]]; then + return + fi + + spacetime_command="$(command -v spacetime || true)" + if [[ -z "${spacetime_command}" ]]; then + echo "[start] 缺少 spacetime 命令,无法同步 SpacetimeDB 安装。" >&2 + exit 1 + fi + + resolved_command="${spacetime_command}" + if command -v readlink >/dev/null 2>&1; then + resolved_command="$(readlink -f "${spacetime_command}" 2>/dev/null || echo "${spacetime_command}")" + fi + + install_dir="$(cd -- "$(dirname -- "${resolved_command}")" && pwd)" + mkdir -p "${root_bin}" + + for share_bin_dir in \ + "/usr/.local/share/spacetime/bin" \ + "${HOME:-}/.local/share/spacetime/bin"; do + if [[ -d "${share_bin_dir}" ]]; then + version_dir="$(find "${share_bin_dir}" -mindepth 1 -maxdepth 1 -type d | sort -V | tail -n 1)" + if [[ -n "${version_dir}" && -x "${version_dir}/spacetimedb-cli" ]]; then + echo "[start] 同步 Ubuntu SpacetimeDB CLI: ${version_dir}/spacetimedb-cli -> ${target_cli}" + mkdir -p "${root_bin}/current" + cp -f "${version_dir}/spacetimedb-cli" "${target_cli}" + chmod +x "${target_cli}" + return + fi + fi + done + + if [[ -d "${install_dir}/bin" ]]; then + echo "[start] 同步 Ubuntu SpacetimeDB 安装: ${install_dir}/bin -> ${root_bin}" + cp -a "${install_dir}/bin/." "${root_bin}/" + elif [[ -x "${install_dir}/current/spacetimedb-cli" ]]; then + echo "[start] 同步 Ubuntu SpacetimeDB 安装: ${install_dir} -> ${root_bin}" + cp -a "${install_dir}/." "${root_bin}/" + elif [[ -x "${install_dir}/spacetimedb-cli" ]]; then + echo "[start] 同步 Ubuntu SpacetimeDB CLI: ${install_dir}/spacetimedb-cli -> ${target_cli}" + mkdir -p "${root_bin}/current" + cp -f "${install_dir}/spacetimedb-cli" "${target_cli}" + chmod +x "${target_cli}" + elif [[ -f "${resolved_command}" ]]; then + parent_dir="$(cd -- "${install_dir}/.." && pwd)" + if [[ -d "${parent_dir}/bin" && -x "${parent_dir}/bin/current/spacetimedb-cli" ]]; then + echo "[start] 同步 Ubuntu SpacetimeDB 安装: ${parent_dir}/bin -> ${root_bin}" + cp -a "${parent_dir}/bin/." "${root_bin}/" + else + echo "[start] 同步 Ubuntu SpacetimeDB 命令: ${resolved_command} -> ${target_cli}" + mkdir -p "${root_bin}/current" + cp -f "${resolved_command}" "${target_cli}" + chmod +x "${target_cli}" + fi + fi + + if [[ ! -x "${target_cli}" ]]; then + echo "[start] 同步 SpacetimeDB 安装后仍未找到 ${target_cli}。" >&2 + echo "[start] 请确认 Ubuntu 上的 spacetime 安装目录包含 bin/current/spacetimedb-cli,或提供可执行的 spacetime 命令。" >&2 + exit 1 + fi +} + start_process() { local name="$1" shift @@ -575,16 +675,32 @@ start_process() { require_command node require_command spacetime -mkdir -p "${PID_DIR}" "${LOG_DIR}" "${SPACETIME_DATA_DIR}" +mkdir -p "${PID_DIR}" "${LOG_DIR}" "${SPACETIME_ROOT_DIR}" +sync_ubuntu_spacetime_install "${SPACETIME_ROOT_DIR}" -start_process spacetimedb \ - spacetime \ - start \ - --data-dir "${SPACETIME_DATA_DIR}" \ - --listen-addr "${SPACETIME_HOST}:${SPACETIME_PORT}" \ - --non-interactive +SPACETIME_PID="" +if is_spacetime_ready; then + echo "[start] 复用已运行的 SpacetimeDB: ${SPACETIME_SERVER_URL}" +else + SPACETIME_ROOT_OWNER="$(describe_spacetime_root_owner)" + if [[ -n "${SPACETIME_ROOT_OWNER}" ]]; then + echo "[start] 当前 root-dir 已被其他 SpacetimeDB 实例占用,无法再次启动。" >&2 + echo "[start] 目标地址未就绪: ${SPACETIME_SERVER_URL}" >&2 + echo "[start] 如需复用,请把 GENARRATIVE_SPACETIME_PORT 改为占用实例实际端口;如需重启,请先停止下列进程。" >&2 + echo "${SPACETIME_ROOT_OWNER}" >&2 + exit 1 + fi -wait_for_spacetime + start_process spacetimedb \ + spacetime \ + --root-dir="${SPACETIME_ROOT_DIR}" \ + start \ + --edition standalone \ + --listen-addr "${SPACETIME_HOST}:${SPACETIME_PORT}" + SPACETIME_PID="$(cat "${PID_DIR}/spacetimedb.pid")" +fi + +wait_for_spacetime "${SPACETIME_PID}" PUBLISH_ARGS=( publish @@ -600,7 +716,15 @@ if [[ "${CLEAR_DATABASE}" -eq 1 ]]; then fi echo "[start] 发布 SpacetimeDB wasm: ${SPACETIME_DATABASE}" -spacetime "${PUBLISH_ARGS[@]}" +if ! spacetime --root-dir="${SPACETIME_ROOT_DIR}" "${PUBLISH_ARGS[@]}"; then + echo "[start] SpacetimeDB 发布失败。" >&2 + echo "[start] 如果错误包含 403 Forbidden 或 is not authorized,通常是当前 CLI 身份无权更新目标数据库。" >&2 + echo "[start] 当前 start.sh 使用的 CLI root: ${SPACETIME_ROOT_DIR}" >&2 + spacetime --root-dir="${SPACETIME_ROOT_DIR}" login show >&2 || true + echo "[start] 如果目标是本地库且可以清空数据:先执行 ./stop.sh,备份或删除 ${SPACETIME_ROOT_DIR},再重新执行 ./start.sh。" >&2 + echo "[start] 如果目标是 Maincloud 或必须保留数据:请切换到创建该数据库的 SpacetimeDB 身份,或把 GENARRATIVE_SPACETIME_DATABASE 改为当前身份有权限的库。" >&2 + exit 1 +fi export GENARRATIVE_API_HOST="${API_HOST}" export GENARRATIVE_API_PORT="${API_PORT}" @@ -707,7 +831,8 @@ cat >"${TARGET_DIR}/README.md" <