@@ -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/<version>/spacetimedb-cli` 或 `$HOME/.local/share/spacetime/bin/<version>/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/<version>/spacetimedb-cli` 或 `$HOME/.local/share/spacetime/bin/<version>/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 构建并部署
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ cd build/<timestamp>
|
||||
./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/<timestamp>
|
||||
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/<database>/`;可通过 `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 实例。
|
||||
|
||||
@@ -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 可能无法授权导出。
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user