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 522850fa..84d4e2aa 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 @@ -96,7 +96,7 @@ scripts/jenkins-deploy-release.sh \ 脚本语义: 1. 若部署目录已有旧版本且存在 `stop.sh`,先执行旧版本 `stop.sh`。 -2. 覆盖前如果旧部署目录存在 `migration-bootstrap-secret.txt`,先复制到 `run/migration-bootstrap-secret.previous.txt`,供新版本 `start.sh` 在 schema 冲突自动迁移时授权导出旧库。 +2. 覆盖前如果旧部署目录存在 `migration-bootstrap-secret.txt`,先复制到 `deploy-state/migration-bootstrap-secret.previous.txt`,供新版本 `start.sh` 在 schema 冲突自动迁移时授权导出旧库。该文件属于 Jenkins 部署状态,不放入 `run/`,避免 `sudo` 启停脚本生成的 root 私有运行目录阻断后续部署写入。 3. 只删除发布产物白名单中的旧文件,例如 `web/`、`api-server`、`spacetime_module.wasm`、`migration-bootstrap-secret.txt`、`scripts/`、`.env*`、`start.sh`、`stop.sh`、`web-server.mjs`、`README.md`。 4. 将指定版本目录中的同名发布产物复制到部署目录;文件产物使用普通复制,`web/`、`scripts/` 等目录产物必须递归复制。 5. 把 `WEB_PORT`、`MIGRATE_ON_CONFLICT`、`MIGRATION_DIRECTORY` 写入部署目录 `.env.local`,确保通过 sudo 执行 `start.sh` 时仍能读取 Jenkins 参数。 @@ -105,7 +105,7 @@ scripts/jenkins-deploy-release.sh \ 如果 `RUN_DEPLOY_HOOKS_WITH_SUDO=true`,旧版本 `stop.sh` 和新版本 `start.sh` 会改为 `sudo -n` 调用;这要求 Jenkins 运行用户提前配置免密 sudo,否则部署会直接失败,不会进入交互式密码提示。 -这样可以满足“发布文件直接覆盖”的要求,同时保留部署目录里像 `.spacetimedb/`、`logs/`、`run/`、`database-migrations/` 这类运行态目录,不会因为部署被整体删除。发布白名单内的 `.env`、`.env.local` 会先以构建产物中的文件为准;部署脚本会在启动 hook 前移除这些环境文件中的 UTF-8 BOM 与 CRLF,并把 Jenkins 部署参数 `WEB_PORT` 写入 `.env.local` 的 `GENARRATIVE_WEB_PORT`,把 `MIGRATE_ON_CONFLICT` 写入 `GENARRATIVE_SPACETIME_MIGRATE_ON_CONFLICT`,把 `MIGRATION_DIRECTORY` 写入 `GENARRATIVE_SPACETIME_MIGRATION_DIR`,避免 `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 身份。 +这样可以满足“发布文件直接覆盖”的要求,同时保留部署目录里像 `.spacetimedb/`、`logs/`、`run/`、`deploy-state/`、`database-migrations/` 这类运行态目录,不会因为部署被整体删除。`run/` 只承载 pid 等启停运行状态;`deploy-state/` 承载 Jenkins 覆盖部署前保存的旧迁移引导密钥,必须由 Jenkins 用户保持可写。发布白名单内的 `.env`、`.env.local` 会先以构建产物中的文件为准;部署脚本会在启动 hook 前移除这些环境文件中的 UTF-8 BOM 与 CRLF,并把 Jenkins 部署参数 `WEB_PORT` 写入 `.env.local` 的 `GENARRATIVE_WEB_PORT`,把 `MIGRATE_ON_CONFLICT` 写入 `GENARRATIVE_SPACETIME_MIGRATE_ON_CONFLICT`,把 `MIGRATION_DIRECTORY` 写入 `GENARRATIVE_SPACETIME_MIGRATION_DIR`,避免 `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/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md b/docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md index f8d46700..259f8d48 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 @@ -181,7 +181,7 @@ cd build/ ./stop.sh ``` -如果后续通过 Jenkins 的部署脚本把发布包覆盖到固定部署目录,部署阶段默认只替换 `web/`、`api-server`、`spacetime_module.wasm`、`migration-bootstrap-secret.txt`、`scripts/`、`.env*`、`start.sh`、`stop.sh`、`web-server.mjs`、`README.md` 等发布产物;文件产物使用普通复制,`web/`、`scripts/` 等目录产物递归复制,不会删除部署目录中的 `.spacetimedb/`、`logs/`、`run/`、`database-migrations/` 这类运行态目录。 +如果后续通过 Jenkins 的部署脚本把发布包覆盖到固定部署目录,部署阶段默认只替换 `web/`、`api-server`、`spacetime_module.wasm`、`migration-bootstrap-secret.txt`、`scripts/`、`.env*`、`start.sh`、`stop.sh`、`web-server.mjs`、`README.md` 等发布产物;文件产物使用普通复制,`web/`、`scripts/` 等目录产物递归复制,不会删除部署目录中的 `.spacetimedb/`、`logs/`、`run/`、`deploy-state/`、`database-migrations/` 这类运行态目录。 安全边界: @@ -189,7 +189,7 @@ cd build/ 2. 如果仓库根目录不存在 `.env` 或 `.env.local`,脚本会打印跳过日志,但不会因此失败;此时 `start.sh` 仅使用构建时写入的默认值与运行时显式传入的环境变量。 3. `start.sh` 只解析合法 `KEY=value` 环境行,支持不加引号、双引号和单引号;不执行复杂 shell 表达式,避免把环境文件变成脚本入口。 4. `start.sh` 默认不追加清理参数;普通发布如果遇到 SpacetimeDB schema 冲突,会调用发布包内的 `scripts/spacetime-export-migration-json.mjs` 导出旧库,再清库发布新 wasm,并调用 `scripts/spacetime-import-migration-json.mjs --replace-existing` 回灌。可通过 `GENARRATIVE_SPACETIME_MIGRATE_ON_CONFLICT=false` 禁用该行为。 -5. 自动迁移导出旧库时优先读取 `run/migration-bootstrap-secret.previous.txt`,导入新库时读取当前发布包 `migration-bootstrap-secret.txt`;Jenkins 部署脚本会在覆盖发布包前保存旧密钥。手工覆盖发布包时,也应在覆盖前保留旧模块的引导密钥,否则旧库导出可能无法授权。 +5. 自动迁移导出旧库时优先读取 `deploy-state/migration-bootstrap-secret.previous.txt`,导入新库时读取当前发布包 `migration-bootstrap-secret.txt`;Jenkins 部署脚本会在覆盖发布包前保存旧密钥。该快照属于部署状态,不放入 `run/`,避免启停 hook 通过 `sudo` 运行后把部署阶段要写的文件变成 root 私有。手工覆盖发布包时,也应在覆盖前保留旧模块的引导密钥,否则旧库导出可能无法授权。 6. 自动迁移 JSON 默认写入发布目录下 `database-migrations//`;可通过 `GENARRATIVE_SPACETIME_MIGRATION_DIR` 改写。该目录属于运行态,不应被 Jenkins 覆盖部署删除。 7. 只有显式执行 `./start.sh --clear-database` 才追加 `-c=on-conflict`,该模式代表人工确认清库,不执行导出和回灌。 8. `start.sh` 会先复用已经按目标地址就绪的 SpacetimeDB;如果同一个 `.spacetimedb/` root-dir 已被其他未就绪实例占用,则只输出命令名为 `spacetime` 或 `spacetimedb-cli` 且命令行包含当前 root-dir 的真实占用进程并失败,避免把排查用的 `grep` / `awk` 误判为 SpacetimeDB 实例。 diff --git a/docs/technical/SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md b/docs/technical/SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md index aa4552c1..e23f7051 100644 --- a/docs/technical/SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md +++ b/docs/technical/SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md @@ -156,7 +156,7 @@ npm run spacetime:publish:maincloud -- --database xushi-p4wfr Ubuntu 发布包的 `start.sh` 与 Jenkins `Genarrative-Deploy` 也采用同一套迁移 procedure,但迁移触发点在部署目录内: -1. Jenkins 覆盖部署前,如果旧部署目录存在 `migration-bootstrap-secret.txt`,先保存到 `run/migration-bootstrap-secret.previous.txt`。 +1. Jenkins 覆盖部署前,如果旧部署目录存在 `migration-bootstrap-secret.txt`,先保存到 `deploy-state/migration-bootstrap-secret.previous.txt`。旧密钥快照属于部署状态,不再写入 `run/`,避免 `sudo` 启停脚本生成的 root 私有运行目录阻断后续覆盖部署。 2. Jenkins 复制新发布包,包含新 wasm、新 `migration-bootstrap-secret.txt` 和 `scripts/spacetime-*.mjs` 迁移脚本。 3. 新 `start.sh` 先不清库发布当前包内 `spacetime_module.wasm`。 4. 如果发布成功,流程结束。 @@ -172,7 +172,7 @@ Jenkins 参数 `CLEAR_DATABASE=true` 或手工执行 `./start.sh --clear-databas 自动迁移依赖两个引导密钥: -- 导出旧库:优先使用 `run/migration-bootstrap-secret.previous.txt`,也就是旧模块编译时注入的密钥。 +- 导出旧库:优先使用 `deploy-state/migration-bootstrap-secret.previous.txt`,也就是旧模块编译时注入的密钥。 - 导入新库:使用当前发布包 `migration-bootstrap-secret.txt`,也就是新模块编译时注入的密钥。 如果不是通过 Jenkins 部署脚本覆盖发布包,而是手工替换文件,必须在覆盖前保留旧 `migration-bootstrap-secret.txt`;否则旧库迁移 procedure 可能无法授权导出。 diff --git a/scripts/deploy-rust-remote.sh b/scripts/deploy-rust-remote.sh index 7e6d0f70..daa79fd3 100644 --- a/scripts/deploy-rust-remote.sh +++ b/scripts/deploy-rust-remote.sh @@ -614,7 +614,7 @@ API_LOG="${GENARRATIVE_API_LOG:-info,tower_http=info}" WEB_HOST="${GENARRATIVE_WEB_HOST:-__GENARRATIVE_DEFAULT_WEB_HOST__}" WEB_PORT="${GENARRATIVE_WEB_PORT:-__GENARRATIVE_DEFAULT_WEB_PORT__}" MIGRATION_BOOTSTRAP_SECRET_FILE="${SCRIPT_DIR}/migration-bootstrap-secret.txt" -PREVIOUS_MIGRATION_BOOTSTRAP_SECRET_FILE="${SCRIPT_DIR}/run/migration-bootstrap-secret.previous.txt" +PREVIOUS_MIGRATION_BOOTSTRAP_SECRET_FILE="${SCRIPT_DIR}/deploy-state/migration-bootstrap-secret.previous.txt" MIGRATION_SCRIPT_DIR="${SCRIPT_DIR}/scripts" MIGRATION_EXPORT_SCRIPT="${MIGRATION_SCRIPT_DIR}/spacetime-export-migration-json.mjs" MIGRATION_IMPORT_SCRIPT="${MIGRATION_SCRIPT_DIR}/spacetime-import-migration-json.mjs" diff --git a/scripts/jenkins-deploy-release.sh b/scripts/jenkins-deploy-release.sh index 40103225..136e240c 100644 --- a/scripts/jenkins-deploy-release.sh +++ b/scripts/jenkins-deploy-release.sh @@ -126,6 +126,7 @@ DEPLOY_ITEMS=( "web" "web-server.mjs" ) +PREVIOUS_MIGRATION_BOOTSTRAP_SECRET_NAME="migration-bootstrap-secret.previous.txt" while [[ $# -gt 0 ]]; do case "$1" in @@ -208,6 +209,70 @@ run_hook() { ) } +previous_migration_bootstrap_secret_file() { + printf "%s/deploy-state/%s" "${DEPLOY_DIR}" "${PREVIOUS_MIGRATION_BOOTSTRAP_SECRET_NAME}" +} + +save_previous_migration_bootstrap_secret() { + local source_file="${DEPLOY_DIR}/migration-bootstrap-secret.txt" + local state_dir="${DEPLOY_DIR}/deploy-state" + local target_file + + target_file="$(previous_migration_bootstrap_secret_file)" + mkdir -p "${state_dir}" || { + echo "[jenkins-deploy] 创建部署状态目录失败: ${state_dir}" >&2 + exit 1 + } + + # 旧迁移密钥属于部署阶段要维护的状态,不再写入 run/,避免 sudo 启停生成的 root 私有 pid 目录阻断覆盖部署。 + cp "${source_file}" "${target_file}" || { + echo "[jenkins-deploy] 保存旧模块迁移引导密钥失败: ${target_file}" >&2 + exit 1 + } + chmod 600 "${target_file}" 2>/dev/null || true + echo "[jenkins-deploy] 已保存旧模块迁移引导密钥,用于 schema 冲突时导出旧库。" +} + +clear_previous_migration_bootstrap_secret() { + local target_file + + target_file="$(previous_migration_bootstrap_secret_file)" + if [[ ! -e "${target_file}" ]]; then + return + fi + + rm -f "${target_file}" || { + echo "[jenkins-deploy] 清理旧迁移引导密钥快照失败: ${target_file}" >&2 + exit 1 + } +} + +normalize_start_previous_secret_path() { + local start_file="${DEPLOY_DIR}/start.sh" + local legacy_line='PREVIOUS_MIGRATION_BOOTSTRAP_SECRET_FILE="${SCRIPT_DIR}/run/migration-bootstrap-secret.previous.txt"' + local state_line='PREVIOUS_MIGRATION_BOOTSTRAP_SECRET_FILE="${SCRIPT_DIR}/deploy-state/migration-bootstrap-secret.previous.txt"' + local temp_file="${start_file}.tmp.$$" + + if [[ ! -f "${start_file}" ]]; then + return + fi + + if grep -Fq "${legacy_line}" "${start_file}"; then + # 兼容已经构建出的旧发布包:部署阶段统一让 start.sh 从 Jenkins 可写的部署状态目录读取旧密钥。 + awk -v legacy="${legacy_line}" -v state="${state_line}" ' + $0 == legacy { + print state + next + } + { + print + } + ' "${start_file}" >"${temp_file}" + cp "${temp_file}" "${start_file}" + rm -f "${temp_file}" + fi +} + if [[ ! -d "${SOURCE_DIR}" ]]; then echo "[jenkins-deploy] 发布目录不存在: ${SOURCE_DIR}" >&2 exit 1 @@ -232,12 +297,9 @@ else fi if [[ -f "${DEPLOY_DIR}/migration-bootstrap-secret.txt" ]]; then - mkdir -p "${DEPLOY_DIR}/run" - cp "${DEPLOY_DIR}/migration-bootstrap-secret.txt" "${DEPLOY_DIR}/run/migration-bootstrap-secret.previous.txt" - chmod 600 "${DEPLOY_DIR}/run/migration-bootstrap-secret.previous.txt" 2>/dev/null || true - echo "[jenkins-deploy] 已保存旧模块迁移引导密钥,用于 schema 冲突时导出旧库。" + save_previous_migration_bootstrap_secret else - rm -f "${DEPLOY_DIR}/run/migration-bootstrap-secret.previous.txt" 2>/dev/null || true + clear_previous_migration_bootstrap_secret fi echo "[jenkins-deploy] 清空部署目录: ${DEPLOY_DIR}" @@ -262,6 +324,8 @@ for item in "${DEPLOY_ITEMS[@]}"; do fi done +normalize_start_previous_secret_path + chmod +x "${DEPLOY_DIR}/start.sh" if [[ -f "${DEPLOY_DIR}/stop.sh" ]]; then