Files
Genarrative/scripts/deploy/production-stdb-publish.sh
2026-05-28 01:42:01 +08:00

261 lines
7.9 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
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] [--root-dir /stdb] [--run-as-user spacetimedb] [--clear-database] [--backup-mode async|sync|skip]
说明:
进入维护模式,校验 spacetime_module.wasm.sha256并在生产实例本机执行 spacetime publish。
默认使用 http://127.0.0.1:3101避免与部署机本机 Git/Web 服务的 3000 端口冲突。
默认使用 /stdb 作为 spacetime CLI root-dir并以 spacetimedb 用户发布,避免 root CLI 身份污染自托管实例。
发布时固定追加 --no-config只使用显式参数避免工作区或用户目录里的 spacetime 配置干扰目标。
默认在 publish 成功后异步触发 genarrative-database-backup.service避免低带宽 OSS 上传阻塞部署。
如需强制等待备份完成并在失败时阻断 publish传入 --backup-mode sync。
失败时保留维护模式。
EOF
}
require_argument() {
local value="$1"
local label="$2"
if [[ -z "${value}" ]]; then
echo "[production-stdb-publish] 缺少参数: ${label}" >&2
exit 1
fi
}
validate_spacetime_database_name() {
local database="$1"
if [[ ! "${database}" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; then
echo "[production-stdb-publish] --database 必须匹配 ^[a-z0-9]+(-[a-z0-9]+)*$: ${database}" >&2
exit 1
fi
}
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
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
BACKUP_MODE="${GENARRATIVE_STDB_PUBLISH_BACKUP_MODE:-async}"
DEPLOY_COMPLETED=0
PUBLISH_TMP_DIR=""
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
exit 0
;;
--source-dir)
SOURCE_DIR="${2:?缺少 --source-dir 的值}"
shift 2
;;
--database)
DATABASE="${2:?缺少 --database 的值}"
shift 2
;;
--server)
SERVER_ALIAS="${2:?缺少 --server 的值}"
SERVER_URL=""
shift 2
;;
--server-url)
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
;;
--skip-backup)
BACKUP_MODE="skip"
shift
;;
--sync-backup)
BACKUP_MODE="sync"
shift
;;
--backup-mode)
BACKUP_MODE="${2:?缺少 --backup-mode 的值}"
shift 2
;;
*)
echo "[production-stdb-publish] 未知参数: $1" >&2
usage >&2
exit 1
;;
esac
done
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 [[ ! "${BACKUP_MODE}" =~ ^(async|sync|skip)$ ]]; then
echo "[production-stdb-publish] --backup-mode 只能是 async、sync 或 skip: ${BACKUP_MODE}" >&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
fi
SOURCE_DIR="$(cd "${SOURCE_DIR}" && pwd)"
if [[ ! -f "${SOURCE_DIR}/spacetime_module.wasm" || ! -f "${SOURCE_DIR}/spacetime_module.wasm.sha256" ]]; then
echo "[production-stdb-publish] 缺少 spacetime_module.wasm 或 spacetime_module.wasm.sha256: ${SOURCE_DIR}" >&2
exit 1
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
exit "${exit_code}"
}
trap on_exit EXIT
trigger_async_backup() {
# Jenkins 发布路径不能被低带宽 OSS 上传长时间占住;默认只把已安装的 systemd
# oneshot 备份任务排队启动。必须放在 publish 成功后,避免冷备份停止 SpacetimeDB
# 与 spacetime publish 同时争用 spacetimedb.service。
if command -v systemctl >/dev/null 2>&1 && systemctl list-unit-files genarrative-database-backup.service --no-legend | grep -q '^genarrative-database-backup\.service'; then
echo "[production-stdb-publish] 异步触发数据库 OSS 备份,不等待上传完成"
if ! systemctl start --no-block genarrative-database-backup.service; then
echo "[production-stdb-publish] 警告:异步触发数据库备份失败;继续发布,请检查 genarrative-database-backup.service 日志" >&2
fi
else
echo "[production-stdb-publish] 警告:未找到 genarrative-database-backup.service跳过异步备份触发" >&2
fi
}
"${SCRIPT_DIR}/maintenance-on.sh" "spacetime module publish ${DATABASE}"
case "${BACKUP_MODE}" in
async)
echo "[production-stdb-publish] 将在 publish 成功后异步触发数据库 OSS 备份"
;;
sync)
BACKUP_SCRIPT="${SCRIPT_DIR}/../database-backup-to-oss.mjs"
if [[ ! -f "${BACKUP_SCRIPT}" ]]; then
BACKUP_SCRIPT="${SOURCE_DIR}/scripts/database-backup-to-oss.mjs"
fi
if [[ ! -f "${BACKUP_SCRIPT}" ]]; then
echo "[production-stdb-publish] 缺少 publish 前数据库备份脚本: ${BACKUP_SCRIPT}" >&2
exit 1
fi
echo "[production-stdb-publish] publish 前同步执行 OSS 冷备份,失败会阻断发布"
node "${BACKUP_SCRIPT}" \
--env-file /etc/genarrative/api-server.env \
--data-dir "${SPACETIME_ROOT_DIR}" \
--database "${DATABASE}" \
--stop-service spacetimedb.service
;;
skip)
echo "[production-stdb-publish] 已按参数跳过 publish 前数据库备份"
;;
esac
echo "[production-stdb-publish] 校验 wasm"
(
cd "${SOURCE_DIR}"
sha256sum -c spacetime_module.wasm.sha256
)
PUBLISH_ARGS=(
--root-dir="${SPACETIME_ROOT_DIR}"
publish
"${DATABASE}"
--bin-path "${SOURCE_DIR}/spacetime_module.wasm"
--yes
--no-config
)
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
if [[ -n "${SERVER_URL}" ]]; then
echo "[production-stdb-publish] 发布 SpacetimeDB module: ${DATABASE} -> ${SERVER_URL}, root=${SPACETIME_ROOT_DIR}"
else
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
--no-config
)
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
if [[ "${BACKUP_MODE}" == "async" ]]; then
trigger_async_backup
fi
echo "[production-stdb-publish] 完成"