chore: harden spacetime publish provisioning
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:
@@ -392,6 +392,7 @@ WASM_SOURCE="${CARGO_TARGET_DIR}/wasm32-unknown-unknown/release/spacetime_module
|
|||||||
- 可选安装 Nginx 配置和维护模式 snippet。
|
- 可选安装 Nginx 配置和维护模式 snippet。
|
||||||
- 安装 Nginx 配置时执行 `nginx -t`,通过后必须执行 `nginx -s reload`,确保新配置对当前 Nginx master/worker 生效。
|
- 安装 Nginx 配置时执行 `nginx -t`,通过后必须执行 `nginx -s reload`,确保新配置对当前 Nginx master/worker 生效。
|
||||||
- 启用并启动 `spacetimedb.service` 与 `genarrative-api.service`;重启 `spacetimedb.service` 后必须等待 `http://127.0.0.1:3101/v1/ping` 确认就绪,不能只依赖 `systemctl restart` 的返回码。`<SPACETIME_ROOT>` 下所有运行态文件必须归属 `spacetimedb:spacetimedb`。不要在 root 身份下对同一个 `<SPACETIME_ROOT>` 执行 `spacetime --root-dir=<SPACETIME_ROOT> server ping`,否则会生成 root-owned CLI 配置,导致 `spacetimedb` 服务用户后续启动时遇到权限错误。
|
- 启用并启动 `spacetimedb.service` 与 `genarrative-api.service`;重启 `spacetimedb.service` 后必须等待 `http://127.0.0.1:3101/v1/ping` 确认就绪,不能只依赖 `systemctl restart` 的返回码。`<SPACETIME_ROOT>` 下所有运行态文件必须归属 `spacetimedb:spacetimedb`。不要在 root 身份下对同一个 `<SPACETIME_ROOT>` 执行 `spacetime --root-dir=<SPACETIME_ROOT> server ping`,否则会生成 root-owned CLI 配置,导致 `spacetimedb` 服务用户后续启动时遇到权限错误。
|
||||||
|
- 首次初始化时,如果 `/etc/genarrative/api-server.env` 里还没有 `GENARRATIVE_SPACETIME_TOKEN`,流水线会在 `spacetimedb.service` 就绪后调用本机 `POST http://127.0.0.1:3101/v1/identity` 生成 client identity/token,只把 token 写入环境文件,并只在日志里显示 identity 前缀。随后流水线会以 `spacetimedb` 用户执行 `<SPACETIME_ROOT>/bin/current/spacetimedb-cli --root-dir <SPACETIME_ROOT> login --token [REDACTED]`,确保后续首次 `Stdb publish` 使用同一个 client identity 创建数据库;这个 identity 才会成为后台读取 private 表所需的 owner。若环境文件已有 `GENARRATIVE_SPACETIME_TOKEN`,初始化必须保留该值,只同步 CLI 登录态,不重新生成或覆盖。
|
||||||
|
|
||||||
该流水线属于高风险操作,默认要求人工确认后执行。
|
该流水线属于高风险操作,默认要求人工确认后执行。
|
||||||
已落地的 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。
|
已落地的 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。
|
||||||
@@ -452,8 +453,9 @@ WASM_SOURCE="${CARGO_TARGET_DIR}/wasm32-unknown-unknown/release/spacetime_module
|
|||||||
- 先按 `SOURCE_BRANCH` / `COMMIT_HASH` 解析并 checkout 发布脚本源码,默认使用 `origin/master` 最新 commit;上游构建触发时使用上游传入的实际构建 commit。
|
- 先按 `SOURCE_BRANCH` / `COMMIT_HASH` 解析并 checkout 发布脚本源码,默认使用 `origin/master` 最新 commit;上游构建触发时使用上游传入的实际构建 commit。
|
||||||
- 进入维护模式。
|
- 进入维护模式。
|
||||||
- 将 wasm 上传到生产实例。
|
- 将 wasm 上传到生产实例。
|
||||||
- 在生产实例本机执行 `spacetime --root-dir=/stdb publish <database-name> --server http://127.0.0.1:3101 --bin-path spacetime_module.wasm --yes`。
|
- 在生产实例本机执行 `spacetime --root-dir=/stdb publish <database-name> --server http://127.0.0.1:3101 --bin-path spacetime_module.wasm --yes --no-config`。
|
||||||
- 发布动作默认以 `spacetimedb` 服务用户执行,避免 root 默认 CLI 身份对自托管数据库验签失败,也避免 root 写入 `/stdb/config` 造成后续服务用户启动权限错误。
|
- 发布动作默认以 `spacetimedb` 服务用户执行,避免 root 默认 CLI 身份对自托管数据库验签失败,也避免 root 写入 `/stdb/config` 造成后续服务用户启动权限错误。
|
||||||
|
- `Stdb publish` 固定追加 `--no-config`,只依赖显式传入的 `--root-dir`、`--server`、`--bin-path` 与数据库名,避免 agent 工作区、本机用户目录或仓库内 `spacetime` 配置干扰发布目标。
|
||||||
- 成功后执行必要 smoke test。
|
- 成功后执行必要 smoke test。
|
||||||
- 成功后解除维护模式。
|
- 成功后解除维护模式。
|
||||||
- 失败时保留维护模式并发邮件。
|
- 失败时保留维护模式并发邮件。
|
||||||
|
|||||||
@@ -284,6 +284,115 @@ pipeline {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
read_env_value() {
|
||||||
|
local file="$1"
|
||||||
|
local key="$2"
|
||||||
|
local line value
|
||||||
|
|
||||||
|
if [[ ! -f "${file}" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
while IFS= read -r line || [[ -n "${line}" ]]; do
|
||||||
|
if [[ "${line}" == "${key}="* ]]; then
|
||||||
|
value="${line#*=}"
|
||||||
|
value="$(printf "%s" "${value}" | tr -d "\\r")"
|
||||||
|
if [[ "${value}" == \"* && "${value}" == *\" ]]; then
|
||||||
|
value="${value#\"}"
|
||||||
|
value="${value%\"}"
|
||||||
|
fi
|
||||||
|
printf "%s" "${value}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done <"${file}"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_env_value() {
|
||||||
|
local file="$1"
|
||||||
|
local key="$2"
|
||||||
|
local value="$3"
|
||||||
|
local tmp updated line
|
||||||
|
|
||||||
|
tmp="$(mktemp)"
|
||||||
|
updated="false"
|
||||||
|
while IFS= read -r line || [[ -n "${line}" ]]; do
|
||||||
|
if [[ "${line}" == "${key}="* ]]; then
|
||||||
|
if [[ "${updated}" != "true" ]]; then
|
||||||
|
printf "%s=%s\\n" "${key}" "${value}" >>"${tmp}"
|
||||||
|
updated="true"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
printf "%s\\n" "${line}" >>"${tmp}"
|
||||||
|
fi
|
||||||
|
done <"${file}"
|
||||||
|
if [[ "${updated}" != "true" ]]; then
|
||||||
|
printf "%s=%s\\n" "${key}" "${value}" >>"${tmp}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat "${tmp}" >"${file}"
|
||||||
|
rm -f "${tmp}"
|
||||||
|
chmod 0600 "${file}"
|
||||||
|
chown root:root "${file}"
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_json_string_field() {
|
||||||
|
local json="$1"
|
||||||
|
local key="$2"
|
||||||
|
|
||||||
|
printf "%s" "${json}" | sed -n "s/.*\\\"${key}\\\"[[:space:]]*:[[:space:]]*\\\"\\([^\\\"]*\\)\\\".*/\\1/p" | head -n 1
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_spacetime_owner_client_token() {
|
||||||
|
local server_url="http://127.0.0.1:3101"
|
||||||
|
local cli_path="${SPACETIME_ROOT}/bin/current/spacetimedb-cli"
|
||||||
|
local token identity response login_output existing_token identity_preview
|
||||||
|
|
||||||
|
if [[ "${DRY_RUN}" == "true" ]]; then
|
||||||
|
echo "+ ensure GENARRATIVE_SPACETIME_TOKEN in ${API_ENV_FILE}"
|
||||||
|
echo "+ generate SpacetimeDB client identity when token is missing"
|
||||||
|
echo "+ runuser -u spacetimedb -- ${cli_path} --root-dir ${SPACETIME_ROOT} login --token [REDACTED]"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "${API_ENV_FILE}" ]]; then
|
||||||
|
echo "[server-provision] 环境文件不存在,无法写入 GENARRATIVE_SPACETIME_TOKEN: ${API_ENV_FILE}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ ! -x "${cli_path}" ]]; then
|
||||||
|
echo "[server-provision] SpacetimeDB CLI 不存在或不可执行: ${cli_path}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
existing_token="$(read_env_value "${API_ENV_FILE}" "GENARRATIVE_SPACETIME_TOKEN")"
|
||||||
|
if [[ -n "${existing_token}" ]]; then
|
||||||
|
token="${existing_token}"
|
||||||
|
echo "[server-provision] GENARRATIVE_SPACETIME_TOKEN 已存在,保留并同步 SpacetimeDB CLI 登录态。"
|
||||||
|
else
|
||||||
|
response="$(curl -fsS -X POST "${server_url}/v1/identity")"
|
||||||
|
identity="$(parse_json_string_field "${response}" "identity")"
|
||||||
|
identity="${identity:-$(parse_json_string_field "${response}" "Identity")}"
|
||||||
|
identity="${identity:-$(parse_json_string_field "${response}" "identity_hex")}"
|
||||||
|
identity="${identity:-$(parse_json_string_field "${response}" "identityHex")}"
|
||||||
|
token="$(parse_json_string_field "${response}" "token")"
|
||||||
|
token="${token:-$(parse_json_string_field "${response}" "Token")}"
|
||||||
|
if [[ -z "${identity}" || -z "${token}" ]]; then
|
||||||
|
echo "[server-provision] 生成 SpacetimeDB client identity 失败,响应缺少 identity/token。" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
write_env_value "${API_ENV_FILE}" "GENARRATIVE_SPACETIME_TOKEN" "${token}"
|
||||||
|
identity_preview="${identity:0:12}"
|
||||||
|
echo "[server-provision] 已生成 SpacetimeDB client identity 并写入 GENARRATIVE_SPACETIME_TOKEN: ${identity_preview}..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! login_output="$(runuser -u spacetimedb -- "${cli_path}" --root-dir "${SPACETIME_ROOT}" login --token "${token}" 2>&1)"; then
|
||||||
|
echo "[server-provision] 使用 GENARRATIVE_SPACETIME_TOKEN 登录 SpacetimeDB CLI 失败。" >&2
|
||||||
|
printf "%s\\n" "${login_output}" | sed -E "s/[A-Za-z0-9_.=-]{24,}/[REDACTED]/g" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "[server-provision] 已同步 SpacetimeDB CLI 登录态;后续首次 publish 将使用同一 client identity。"
|
||||||
|
}
|
||||||
|
|
||||||
render_nginx_https_config() {
|
render_nginx_https_config() {
|
||||||
sed "s/genarrative.example.com/${SERVER_NAME}/g" deploy/nginx/genarrative.conf
|
sed "s/genarrative.example.com/${SERVER_NAME}/g" deploy/nginx/genarrative.conf
|
||||||
}
|
}
|
||||||
@@ -506,6 +615,7 @@ pipeline {
|
|||||||
run_cmd systemctl enable spacetimedb.service genarrative-api.service
|
run_cmd systemctl enable spacetimedb.service genarrative-api.service
|
||||||
run_cmd systemctl restart spacetimedb.service
|
run_cmd systemctl restart spacetimedb.service
|
||||||
wait_for_spacetimedb_service
|
wait_for_spacetimedb_service
|
||||||
|
ensure_spacetime_owner_client_token
|
||||||
if [[ -x "${CURRENT_LINK}/api-server" ]]; then
|
if [[ -x "${CURRENT_LINK}/api-server" ]]; then
|
||||||
run_cmd systemctl restart genarrative-api.service
|
run_cmd systemctl restart genarrative-api.service
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ usage() {
|
|||||||
进入维护模式,校验 spacetime_module.wasm.sha256,并在生产实例本机执行 spacetime publish。
|
进入维护模式,校验 spacetime_module.wasm.sha256,并在生产实例本机执行 spacetime publish。
|
||||||
默认使用 http://127.0.0.1:3101,避免与部署机本机 Git/Web 服务的 3000 端口冲突。
|
默认使用 http://127.0.0.1:3101,避免与部署机本机 Git/Web 服务的 3000 端口冲突。
|
||||||
默认使用 /stdb 作为 spacetime CLI root-dir,并以 spacetimedb 用户发布,避免 root CLI 身份污染自托管实例。
|
默认使用 /stdb 作为 spacetime CLI root-dir,并以 spacetimedb 用户发布,避免 root CLI 身份污染自托管实例。
|
||||||
|
发布时固定追加 --no-config,只使用显式参数,避免工作区或用户目录里的 spacetime 配置干扰目标。
|
||||||
失败时保留维护模式。
|
失败时保留维护模式。
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
@@ -141,6 +142,7 @@ PUBLISH_ARGS=(
|
|||||||
"${DATABASE}"
|
"${DATABASE}"
|
||||||
--bin-path "${SOURCE_DIR}/spacetime_module.wasm"
|
--bin-path "${SOURCE_DIR}/spacetime_module.wasm"
|
||||||
--yes
|
--yes
|
||||||
|
--no-config
|
||||||
)
|
)
|
||||||
|
|
||||||
if [[ -n "${SERVER_URL}" ]]; then
|
if [[ -n "${SERVER_URL}" ]]; then
|
||||||
@@ -173,6 +175,7 @@ if [[ -n "${RUN_AS_USER}" && "$(id -u)" -eq 0 ]]; then
|
|||||||
"${DATABASE}"
|
"${DATABASE}"
|
||||||
--bin-path "${PUBLISH_TMP_DIR}/spacetime_module.wasm"
|
--bin-path "${PUBLISH_TMP_DIR}/spacetime_module.wasm"
|
||||||
--yes
|
--yes
|
||||||
|
--no-config
|
||||||
)
|
)
|
||||||
if [[ -n "${SERVER_URL}" ]]; then
|
if [[ -n "${SERVER_URL}" ]]; then
|
||||||
PUBLISH_ARGS+=(--server "${SERVER_URL}")
|
PUBLISH_ARGS+=(--server "${SERVER_URL}")
|
||||||
|
|||||||
Reference in New Issue
Block a user