diff --git a/docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md b/docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md index c468db7d..4a00fab8 100644 --- a/docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md +++ b/docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md @@ -387,10 +387,11 @@ WASM_SOURCE="${CARGO_TARGET_DIR}/wasm32-unknown-unknown/release/spacetime_module - 创建 `spacetimedb`、`genarrative` 等系统用户。 - 创建 `/stdb`、`/opt/genarrative`、`/srv/genarrative`、`/etc/genarrative`、`/var/lib/genarrative/maintenance`。 - 安装或更新 SpacetimeDB。 +- 安装 SpacetimeDB 时不能只复制 `/usr/local/bin/spacetime` wrapper,还必须把 `spacetimedb-cli` 与 `spacetimedb-standalone` 同步到 `/bin/current/`。否则 `spacetime --root-dir= start` 会回调缺失的 `/bin/current/spacetimedb-cli`,导致 `spacetimedb.service` 循环重启但 provision 表面已经执行过 `systemctl restart`。 - 安装 systemd unit。 - 可选安装 Nginx 配置和维护模式 snippet。 - 安装 Nginx 配置时执行 `nginx -t`,通过后必须执行 `nginx -s reload`,确保新配置对当前 Nginx master/worker 生效。 -- 启用并启动 `spacetimedb.service` 与 `genarrative-api.service`。 +- 启用并启动 `spacetimedb.service` 与 `genarrative-api.service`;重启 `spacetimedb.service` 后必须等待 `http://127.0.0.1:3101/v1/ping` 或 `spacetime server ping` 确认就绪,不能只依赖 `systemctl restart` 的返回码。 该流水线属于高风险操作,默认要求人工确认后执行。 已落地的 Jenkinsfile 为 `jenkins/Jenkinsfile.production-server-provision`。该流水线默认 `DRY_RUN=true`,只打印将执行的初始化动作;真正写入系统用户、目录、systemd、环境文件并启动服务时,必须设置 `DRY_RUN=false` 且勾选 `CONFIRM_PROVISION`。当 `DEPLOY_TARGET=release` 时,还必须勾选 `CONFIRM_RELEASE_DEPLOY_AGENT`,并通过 `linux && genarrative-release-deploy` 调度到独立 release 部署 agent。 diff --git a/jenkins/Jenkinsfile.production-server-provision b/jenkins/Jenkinsfile.production-server-provision index 94d7cfb7..3bfefc25 100644 --- a/jenkins/Jenkinsfile.production-server-provision +++ b/jenkins/Jenkinsfile.production-server-provision @@ -174,6 +174,118 @@ pipeline { fi } + sync_spacetime_install() { + local root_dir="$1" + local target_bin_dir="${root_dir}/bin/current" + local target_cli="${target_bin_dir}/spacetimedb-cli" + local target_standalone="${target_bin_dir}/spacetimedb-standalone" + local resolved_command="${SPACETIME_BIN_SOURCE}" + local install_dir="" + local root_bin="${root_dir}/bin" + local share_bin_dir="" + local version_dir="" + local parent_dir="" + + if [[ -x "${target_cli}" && -x "${target_standalone}" ]]; then + echo "[server-provision] SpacetimeDB current 目录已存在: ${target_bin_dir}" + return + fi + + echo "[server-provision] 同步 SpacetimeDB current 目录到 ${target_bin_dir}" + if [[ "${DRY_RUN}" == "true" ]]; then + echo "+ mkdir -p ${target_bin_dir}" + echo "+ copy spacetimedb-cli and spacetimedb-standalone into ${target_bin_dir}" + return + fi + + if command -v readlink >/dev/null 2>&1; then + resolved_command="$(readlink -f "${SPACETIME_BIN_SOURCE}" 2>/dev/null || echo "${SPACETIME_BIN_SOURCE}")" + fi + install_dir="$(cd -- "$(dirname -- "${resolved_command}")" && pwd)" + mkdir -p "${root_bin}" + + for share_bin_dir in \ + "/usr/.local/share/spacetime/bin" \ + "/root/.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" && -x "${version_dir}/spacetimedb-standalone" ]]; then + echo "[server-provision] 同步 SpacetimeDB 安装: ${version_dir} -> ${target_bin_dir}" + rm -rf "${target_bin_dir}" + mkdir -p "${target_bin_dir}" + cp -a "${version_dir}/." "${target_bin_dir}/" + chmod +x "${target_cli}" "${target_standalone}" + chown -R spacetimedb:spacetimedb "${root_bin}" + return + fi + fi + done + + if [[ -d "${install_dir}/bin" ]]; then + echo "[server-provision] 同步 SpacetimeDB 安装: ${install_dir}/bin -> ${root_bin}" + cp -a "${install_dir}/bin/." "${root_bin}/" + elif [[ -x "${install_dir}/spacetimedb-cli" && -x "${install_dir}/spacetimedb-standalone" ]]; then + echo "[server-provision] 同步 SpacetimeDB 安装: ${install_dir} -> ${target_bin_dir}" + rm -rf "${target_bin_dir}" + mkdir -p "${target_bin_dir}" + cp -f "${install_dir}/spacetimedb-cli" "${target_cli}" + cp -f "${install_dir}/spacetimedb-standalone" "${target_standalone}" + chmod +x "${target_cli}" "${target_standalone}" + elif [[ -f "${resolved_command}" ]]; then + parent_dir="$(cd -- "${install_dir}/.." && pwd)" + if [[ -d "${parent_dir}/bin" && -x "${parent_dir}/bin/current/spacetimedb-cli" && -x "${parent_dir}/bin/current/spacetimedb-standalone" ]]; then + echo "[server-provision] 同步 SpacetimeDB 安装: ${parent_dir}/bin -> ${root_bin}" + cp -a "${parent_dir}/bin/." "${root_bin}/" + else + echo "[server-provision] 未能从 spacetime 命令路径推断完整 SpacetimeDB 安装目录: ${resolved_command}" >&2 + fi + fi + + if [[ ! -x "${target_cli}" || ! -x "${target_standalone}" ]]; then + echo "[server-provision] 同步 SpacetimeDB 安装后仍缺少 current 目录。" >&2 + echo "[server-provision] 需要同时存在: ${target_cli} 与 ${target_standalone}" >&2 + exit 1 + fi + + chown -R spacetimedb:spacetimedb "${root_bin}" + } + + is_spacetimedb_ready() { + local server_url="http://127.0.0.1:3101" + local output="" + + if command -v curl >/dev/null 2>&1 && curl -fsS "${server_url}/v1/ping" >/dev/null 2>&1; then + return 0 + fi + + output="$("${SPACETIME_ROOT}/spacetime" --root-dir="${SPACETIME_ROOT}" server ping "${server_url}" 2>&1 || true)" + [[ "${output}" == *"Server is online:"* ]] + } + + wait_for_spacetimedb_service() { + local deadline=$((SECONDS + 60)) + + if [[ "${DRY_RUN}" == "true" ]]; then + echo "+ wait for spacetimedb.service on http://127.0.0.1:3101" + return + fi + + while ((SECONDS < deadline)); do + if is_spacetimedb_ready; then + echo "[server-provision] spacetimedb.service 已就绪: http://127.0.0.1:3101" + return + fi + sleep 1 + done + + echo "[server-provision] 等待 spacetimedb.service 就绪超时。" >&2 + systemctl status spacetimedb.service --no-pager -l >&2 || true + journalctl -u spacetimedb.service --no-pager -n 80 >&2 || true + ss -ltnp >&2 || true + exit 1 + } + render_nginx_https_config() { sed "s/genarrative.example.com/${SERVER_NAME}/g" deploy/nginx/genarrative.conf } @@ -364,6 +476,7 @@ pipeline { install -m 0755 "${SPACETIME_BIN_SOURCE}" "${SPACETIME_ROOT}/spacetime" chown spacetimedb:spacetimedb "${SPACETIME_ROOT}/spacetime" fi + sync_spacetime_install "${SPACETIME_ROOT}" spacetimedb_service="$(mktemp)" api_service="$(mktemp)" @@ -394,6 +507,7 @@ pipeline { if [[ "${ENABLE_SERVICES}" == "true" ]]; then run_cmd systemctl enable spacetimedb.service genarrative-api.service run_cmd systemctl restart spacetimedb.service + wait_for_spacetimedb_service if [[ -x "${CURRENT_LINK}/api-server" ]]; then run_cmd systemctl restart genarrative-api.service else