Harden production publish flow #5
@@ -452,7 +452,8 @@ WASM_SOURCE="${CARGO_TARGET_DIR}/wasm32-unknown-unknown/release/spacetime_module
|
||||
- 先按 `SOURCE_BRANCH` / `COMMIT_HASH` 解析并 checkout 发布脚本源码,默认使用 `origin/master` 最新 commit;上游构建触发时使用上游传入的实际构建 commit。
|
||||
- 进入维护模式。
|
||||
- 将 wasm 上传到生产实例。
|
||||
- 在生产实例本机执行 `spacetime publish -s local --bin-path spacetime_module.wasm <database-name>`。
|
||||
- 在生产实例本机执行 `spacetime --root-dir=/stdb publish <database-name> --server http://127.0.0.1:3101 --bin-path spacetime_module.wasm --yes`。
|
||||
- 发布动作默认以 `spacetimedb` 服务用户执行,避免 root 默认 CLI 身份对自托管数据库验签失败,也避免 root 写入 `/stdb/config` 造成后续服务用户启动权限错误。
|
||||
- 成功后执行必要 smoke test。
|
||||
- 成功后解除维护模式。
|
||||
- 失败时保留维护模式并发邮件。
|
||||
@@ -463,6 +464,7 @@ WASM_SOURCE="${CARGO_TARGET_DIR}/wasm32-unknown-unknown/release/spacetime_module
|
||||
- 并行执行 Web / API / Stdb 三条构建流水线。
|
||||
- 并行构建阶段必须开启 fail-fast:任一构建流水线失败时,立即中断其他仍在执行的并行构建分支,本次全量编排不再继续进入发布阶段。
|
||||
- 构建全部成功后,按顺序执行 Stdb publish、API deploy、Web deploy,并把同一个 `DEPLOY_TARGET` 透传给三条发布流水线。
|
||||
- Stdb publish 同时透传 `SPACETIME_SERVER_URL`、`SPACETIME_ROOT_DIR` 与 `SPACETIME_RUN_AS_USER`,默认分别为 `http://127.0.0.1:3101`、`/stdb`、`spacetimedb`。
|
||||
- 每条下游构建都只消费自己的归档产物,不直接复用别的 workspace。
|
||||
- 生产 Web 发布只处理 `web.tar.gz` 与 checksum,API 发布只处理 `api-server` 与 checksum,Stdb 发布只处理 `spacetime_module.wasm` 与 checksum。
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ pipeline {
|
||||
booleanParam(name: 'CONFIRM_RELEASE_DEPLOY_AGENT', defaultValue: false, description: '确认 release 目标已有独立 release 部署 agent;当前 Linux 开发/构建/开发部署 agent 不可冒充 release 部署机')
|
||||
string(name: 'DATABASE', defaultValue: 'genarrative-prod', description: '生产 SpacetimeDB database')
|
||||
string(name: 'SPACETIME_SERVER_URL', defaultValue: 'http://127.0.0.1:3101', description: 'Stdb 发布目标 URL;默认避开本机 Git/Web 使用的 3000 端口')
|
||||
string(name: 'SPACETIME_ROOT_DIR', defaultValue: '/stdb', description: 'Stdb 发布使用的 spacetime CLI root-dir')
|
||||
string(name: 'SPACETIME_RUN_AS_USER', defaultValue: 'spacetimedb', description: 'Stdb 发布使用的本机用户')
|
||||
}
|
||||
|
||||
stages {
|
||||
@@ -136,6 +138,8 @@ pipeline {
|
||||
string(name: 'NOTIFICATION_EMAILS', value: params.NOTIFICATION_EMAILS ?: ''),
|
||||
string(name: 'DATABASE', value: params.DATABASE),
|
||||
string(name: 'SPACETIME_SERVER_URL', value: params.SPACETIME_SERVER_URL ?: ''),
|
||||
string(name: 'SPACETIME_ROOT_DIR', value: params.SPACETIME_ROOT_DIR ?: '/stdb'),
|
||||
string(name: 'SPACETIME_RUN_AS_USER', value: params.SPACETIME_RUN_AS_USER ?: 'spacetimedb'),
|
||||
string(name: 'DEPLOY_TARGET', value: params.DEPLOY_TARGET),
|
||||
booleanParam(name: 'CONFIRM_RELEASE_DEPLOY_AGENT', value: params.CONFIRM_RELEASE_DEPLOY_AGENT),
|
||||
string(name: 'BUILD_JOB_NAME', value: params.STDB_BUILD_JOB_NAME),
|
||||
|
||||
@@ -23,6 +23,8 @@ pipeline {
|
||||
string(name: 'DATABASE', defaultValue: 'genarrative-prod', description: '生产 SpacetimeDB database')
|
||||
string(name: 'SPACETIME_SERVER', defaultValue: 'local', description: 'SpacetimeDB server alias')
|
||||
string(name: 'SPACETIME_SERVER_URL', defaultValue: 'http://127.0.0.1:3101', description: '显式 SpacetimeDB server URL,填写后优先于 SPACETIME_SERVER')
|
||||
string(name: 'SPACETIME_ROOT_DIR', defaultValue: '/stdb', description: 'spacetime CLI root-dir;需与自托管 spacetimedb.service 一致')
|
||||
string(name: 'SPACETIME_RUN_AS_USER', defaultValue: 'spacetimedb', description: '执行 spacetime publish 的本机用户,默认使用自托管服务用户')
|
||||
booleanParam(name: 'CLEAR_DATABASE', defaultValue: false, description: '是否清空数据库后发布')
|
||||
}
|
||||
|
||||
@@ -51,6 +53,14 @@ pipeline {
|
||||
if (!params.SPACETIME_SERVER?.trim() && !params.SPACETIME_SERVER_URL?.trim()) {
|
||||
error('SPACETIME_SERVER 与 SPACETIME_SERVER_URL 不能同时为空。')
|
||||
}
|
||||
def spacetimeRootDir = params.SPACETIME_ROOT_DIR?.trim() ? params.SPACETIME_ROOT_DIR.trim() : '/stdb'
|
||||
if (!(spacetimeRootDir ==~ /^\/(?!.*\.\.)[A-Za-z0-9._\/-]+$/)) {
|
||||
error("SPACETIME_ROOT_DIR 必须是 Linux 绝对路径且不能包含 ..: ${spacetimeRootDir}")
|
||||
}
|
||||
def spacetimeRunAsUser = params.SPACETIME_RUN_AS_USER?.trim()
|
||||
if (spacetimeRunAsUser && !(spacetimeRunAsUser ==~ /^[A-Za-z_][A-Za-z0-9_-]*$/)) {
|
||||
error("SPACETIME_RUN_AS_USER 只能是本机用户名: ${spacetimeRunAsUser}")
|
||||
}
|
||||
def spacetimeServerUrl = params.SPACETIME_SERVER_URL?.trim()
|
||||
if (spacetimeServerUrl && !(spacetimeServerUrl ==~ /^https?:\/\/[A-Za-z0-9._:-]+$/)) {
|
||||
error("SPACETIME_SERVER_URL 只能是 http(s) URL,且不能包含路径或 shell 特殊字符: ${spacetimeServerUrl}")
|
||||
@@ -111,6 +121,10 @@ pipeline {
|
||||
steps {
|
||||
script {
|
||||
def clearArg = params.CLEAR_DATABASE ? '--clear-database' : ''
|
||||
def rootArg = "--root-dir \"${params.SPACETIME_ROOT_DIR?.trim() ? params.SPACETIME_ROOT_DIR.trim() : '/stdb'}\""
|
||||
def runAsArg = params.SPACETIME_RUN_AS_USER?.trim()
|
||||
? "--run-as-user \"${params.SPACETIME_RUN_AS_USER.trim()}\""
|
||||
: ''
|
||||
def serverArg = params.SPACETIME_SERVER_URL?.trim()
|
||||
? "--server-url \"${params.SPACETIME_SERVER_URL.trim()}\""
|
||||
: "--server \"${params.SPACETIME_SERVER}\""
|
||||
@@ -121,6 +135,8 @@ pipeline {
|
||||
scripts/deploy/production-stdb-publish.sh \\
|
||||
--source-dir "build/${params.BUILD_VERSION}" \\
|
||||
--database "${params.DATABASE}" \\
|
||||
${rootArg} \\
|
||||
${runAsArg} \\
|
||||
${serverArg} \\
|
||||
${clearArg}
|
||||
'
|
||||
|
||||
@@ -5,11 +5,12 @@ set -euo pipefail
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
用法:
|
||||
./scripts/deploy/production-stdb-publish.sh --source-dir build/<version> --database <database> [--server-url http://127.0.0.1:3101] [--server local] [--clear-database]
|
||||
./scripts/deploy/production-stdb-publish.sh --source-dir build/<version> --database <database> [--server-url http://127.0.0.1:3101] [--server local] [--root-dir /stdb] [--run-as-user spacetimedb] [--clear-database]
|
||||
|
||||
说明:
|
||||
进入维护模式,校验 spacetime_module.wasm.sha256,并在生产实例本机执行 spacetime publish。
|
||||
默认使用 http://127.0.0.1:3101,避免与部署机本机 Git/Web 服务的 3000 端口冲突。
|
||||
默认使用 /stdb 作为 spacetime CLI root-dir,并以 spacetimedb 用户发布,避免 root CLI 身份污染自托管实例。
|
||||
失败时保留维护模式。
|
||||
EOF
|
||||
}
|
||||
@@ -38,8 +39,11 @@ SOURCE_DIR=""
|
||||
DATABASE=""
|
||||
SERVER_ALIAS="local"
|
||||
SERVER_URL="http://127.0.0.1:3101"
|
||||
SPACETIME_ROOT_DIR="/stdb"
|
||||
RUN_AS_USER="spacetimedb"
|
||||
CLEAR_DATABASE=0
|
||||
DEPLOY_COMPLETED=0
|
||||
PUBLISH_TMP_DIR=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
@@ -64,6 +68,14 @@ while [[ $# -gt 0 ]]; do
|
||||
SERVER_URL="${2:?缺少 --server-url 的值}"
|
||||
shift 2
|
||||
;;
|
||||
--root-dir)
|
||||
SPACETIME_ROOT_DIR="${2:?缺少 --root-dir 的值}"
|
||||
shift 2
|
||||
;;
|
||||
--run-as-user)
|
||||
RUN_AS_USER="${2:?缺少 --run-as-user 的值}"
|
||||
shift 2
|
||||
;;
|
||||
--clear-database)
|
||||
CLEAR_DATABASE=1
|
||||
shift
|
||||
@@ -80,6 +92,16 @@ require_argument "${SOURCE_DIR}" "--source-dir"
|
||||
require_argument "${DATABASE}" "--database"
|
||||
validate_spacetime_database_name "${DATABASE}"
|
||||
|
||||
if [[ ! "${SPACETIME_ROOT_DIR}" == /* || "${SPACETIME_ROOT_DIR}" == *".."* ]]; then
|
||||
echo "[production-stdb-publish] --root-dir 必须是 Linux 绝对路径且不能包含 ..: ${SPACETIME_ROOT_DIR}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "${RUN_AS_USER}" && ! "${RUN_AS_USER}" =~ ^[A-Za-z_][A-Za-z0-9_-]*$ ]]; then
|
||||
echo "[production-stdb-publish] --run-as-user 只能是本机用户名: ${RUN_AS_USER}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -d "${SOURCE_DIR}" ]]; then
|
||||
echo "[production-stdb-publish] 发布目录不存在: ${SOURCE_DIR}" >&2
|
||||
exit 1
|
||||
@@ -94,6 +116,9 @@ fi
|
||||
|
||||
on_exit() {
|
||||
local exit_code=$?
|
||||
if [[ -n "${PUBLISH_TMP_DIR}" && -d "${PUBLISH_TMP_DIR}" ]]; then
|
||||
rm -rf "${PUBLISH_TMP_DIR}"
|
||||
fi
|
||||
if [[ "${exit_code}" -ne 0 && "${DEPLOY_COMPLETED}" -ne 1 ]]; then
|
||||
echo "[production-stdb-publish] 发布失败,保持维护模式。" >&2
|
||||
fi
|
||||
@@ -111,6 +136,7 @@ echo "[production-stdb-publish] 校验 wasm"
|
||||
)
|
||||
|
||||
PUBLISH_ARGS=(
|
||||
--root-dir="${SPACETIME_ROOT_DIR}"
|
||||
publish
|
||||
"${DATABASE}"
|
||||
--bin-path "${SOURCE_DIR}/spacetime_module.wasm"
|
||||
@@ -128,11 +154,38 @@ if [[ "${CLEAR_DATABASE}" -eq 1 ]]; then
|
||||
fi
|
||||
|
||||
if [[ -n "${SERVER_URL}" ]]; then
|
||||
echo "[production-stdb-publish] 发布 SpacetimeDB module: ${DATABASE} -> ${SERVER_URL}"
|
||||
echo "[production-stdb-publish] 发布 SpacetimeDB module: ${DATABASE} -> ${SERVER_URL}, root=${SPACETIME_ROOT_DIR}"
|
||||
else
|
||||
echo "[production-stdb-publish] 发布 SpacetimeDB module: ${DATABASE} -> ${SERVER_ALIAS}"
|
||||
echo "[production-stdb-publish] 发布 SpacetimeDB module: ${DATABASE} -> ${SERVER_ALIAS}, root=${SPACETIME_ROOT_DIR}"
|
||||
fi
|
||||
|
||||
if [[ -n "${RUN_AS_USER}" && "$(id -u)" -eq 0 ]]; then
|
||||
if ! id "${RUN_AS_USER}" >/dev/null 2>&1; then
|
||||
echo "[production-stdb-publish] 发布用户不存在: ${RUN_AS_USER}" >&2
|
||||
exit 1
|
||||
fi
|
||||
PUBLISH_TMP_DIR="$(mktemp -d /tmp/genarrative-stdb-publish.XXXXXX)"
|
||||
install -m 0644 "${SOURCE_DIR}/spacetime_module.wasm" "${PUBLISH_TMP_DIR}/spacetime_module.wasm"
|
||||
chown -R "${RUN_AS_USER}:${RUN_AS_USER}" "${PUBLISH_TMP_DIR}"
|
||||
PUBLISH_ARGS=(
|
||||
--root-dir="${SPACETIME_ROOT_DIR}"
|
||||
publish
|
||||
"${DATABASE}"
|
||||
--bin-path "${PUBLISH_TMP_DIR}/spacetime_module.wasm"
|
||||
--yes
|
||||
)
|
||||
if [[ -n "${SERVER_URL}" ]]; then
|
||||
PUBLISH_ARGS+=(--server "${SERVER_URL}")
|
||||
else
|
||||
PUBLISH_ARGS+=(--server "${SERVER_ALIAS}")
|
||||
fi
|
||||
if [[ "${CLEAR_DATABASE}" -eq 1 ]]; then
|
||||
PUBLISH_ARGS+=(--clear-database)
|
||||
fi
|
||||
runuser -u "${RUN_AS_USER}" -- spacetime "${PUBLISH_ARGS[@]}"
|
||||
else
|
||||
spacetime "${PUBLISH_ARGS[@]}"
|
||||
fi
|
||||
|
||||
"${SCRIPT_DIR}/maintenance-off.sh"
|
||||
DEPLOY_COMPLETED=1
|
||||
|
||||
Reference in New Issue
Block a user