Files
Genarrative/scripts/spacetime-publish-maincloud.sh
2026-04-29 20:56:59 +08:00

270 lines
8.4 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
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
SERVER_RS_DIR="${REPO_ROOT}/server-rs"
MODULE_PATH="${SERVER_RS_DIR}/target/wasm32-unknown-unknown/release/spacetime_module.wasm"
SPACETIME_SERVER_ALIAS="maincloud"
CLEAR_DATABASE=0
MIGRATE_ON_CONFLICT=1
MIGRATION_DIR=""
MIGRATION_BOOTSTRAP_SECRET=""
MIGRATION_BOOTSTRAP_SECRET_MODE="auto"
load_env_file() {
local env_file="$1"
local line key value
if [[ ! -f "${env_file}" ]]; then
return
fi
while IFS= read -r line || [[ -n "${line}" ]]; do
line="${line%$'\r'}"
line="${line#$'\xef\xbb\xbf'}"
[[ -z "${line}" || "${line}" == \#* ]] && continue
[[ "${line}" =~ ^([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]] || continue
key="${BASH_REMATCH[1]}"
value="${BASH_REMATCH[2]}"
value="${value%\"}"
value="${value#\"}"
value="${value%\'}"
value="${value#\'}"
if [[ -z "${!key+x}" ]]; then
export "${key}=${value}"
fi
done <"${env_file}"
}
usage() {
cat <<'EOF'
用法:
npm run spacetime:publish:maincloud
npm run spacetime:publish:maincloud -- --database <database>
npm run spacetime:publish:maincloud -- --clear-database
npm run spacetime:publish:maincloud -- --no-migrate-on-conflict
npm run spacetime:publish:maincloud -- --no-migration-bootstrap-secret
说明:
发布 server-rs/crates/spacetime-module 到 SpacetimeDB Maincloud。
数据库名优先读取 --database其次读取 GENARRATIVE_SPACETIME_MAINCLOUD_DATABASE。
默认遇到 schema 冲突时会先导出迁移 JSON再清库发布并导入回灌。
默认在构建 wasm 前随机生成迁移引导密钥,注入 GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET 并显示在控制台。
EOF
}
generate_migration_bootstrap_secret() {
node -e 'const crypto = require("crypto"); process.stdout.write(crypto.randomBytes(32).toString("hex"));'
}
prepare_migration_bootstrap_secret() {
case "${MIGRATION_BOOTSTRAP_SECRET_MODE}" in
auto)
MIGRATION_BOOTSTRAP_SECRET="$(generate_migration_bootstrap_secret)"
;;
manual)
if [[ "${#MIGRATION_BOOTSTRAP_SECRET}" -lt 16 ]]; then
echo "[spacetime:maincloud] 迁移引导密钥至少需要 16 个字符。" >&2
exit 1
fi
;;
disabled)
unset GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET
echo "[spacetime:maincloud] 未启用迁移引导密钥。"
return
;;
*)
echo "[spacetime:maincloud] 未知迁移引导密钥模式: ${MIGRATION_BOOTSTRAP_SECRET_MODE}" >&2
exit 1
;;
esac
export GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET="${MIGRATION_BOOTSTRAP_SECRET}"
echo "[spacetime:maincloud] 迁移引导密钥: ${MIGRATION_BOOTSTRAP_SECRET}"
}
timestamp_slug() {
node -e 'process.stdout.write(new Date().toISOString().replace(/[:.]/g, "-"));'
}
is_publish_conflict_output() {
local output="$1"
[[ "${output}" == *"conflict"* ]] \
|| [[ "${output}" == *"schema"* && "${output}" == *"clear"* ]] \
|| [[ "${output}" == *"manual migration"* ]] \
|| [[ "${output}" == *"default value annotation"* ]] \
|| [[ "${output}" == *"delete-data"* ]]
}
run_publish() {
local output_file="$1"
shift
set +e
spacetime "$@" >"${output_file}" 2>&1
local status=$?
set -e
cat "${output_file}"
return "${status}"
}
run_conflict_migration_publish() {
local migration_root migration_file publish_log
if [[ "${MIGRATION_BOOTSTRAP_SECRET_MODE}" == "disabled" ]]; then
echo "[spacetime:maincloud] schema 冲突需要迁移引导密钥;请去掉 --no-migration-bootstrap-secret 后重试。" >&2
exit 1
fi
migration_root="${MIGRATION_DIR:-${REPO_ROOT}/tmp/spacetime-migrations/maincloud/${SPACETIME_DATABASE}}"
mkdir -p "${migration_root}"
migration_file="${migration_root}/$(timestamp_slug).json"
publish_log="$(mktemp)"
echo "[spacetime:maincloud] 检测到 schema 冲突,开始导出旧库迁移 JSON: ${migration_file}"
node "${REPO_ROOT}/scripts/spacetime-export-migration-json.mjs" \
--server "${SPACETIME_SERVER_ALIAS}" \
--server-url "${SPACETIME_SERVER_URL}" \
--database "${SPACETIME_DATABASE}" \
--bootstrap-secret "${MIGRATION_BOOTSTRAP_SECRET}" \
--out "${migration_file}" \
--note "publish conflict export $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "[spacetime:maincloud] 清库发布新 SpacetimeDB wasm"
if ! run_publish "${publish_log}" publish "${SPACETIME_DATABASE}" --server "${SPACETIME_SERVER_ALIAS}" --bin-path "${MODULE_PATH}" --clear-database --yes; then
echo "[spacetime:maincloud] 清库发布失败,迁移 JSON 已保留: ${migration_file}" >&2
rm -f "${publish_log}"
exit 1
fi
rm -f "${publish_log}"
echo "[spacetime:maincloud] 导入迁移 JSON 回灌数据"
if ! node "${REPO_ROOT}/scripts/spacetime-import-migration-json.mjs" \
--server "${SPACETIME_SERVER_ALIAS}" \
--server-url "${SPACETIME_SERVER_URL}" \
--database "${SPACETIME_DATABASE}" \
--bootstrap-secret "${MIGRATION_BOOTSTRAP_SECRET}" \
--in "${migration_file}" \
--note "publish conflict import $(date -u +%Y-%m-%dT%H:%M:%SZ)"; then
echo "[spacetime:maincloud] 导入失败,迁移 JSON 已保留: ${migration_file}" >&2
exit 1
fi
echo "[spacetime:maincloud] schema 冲突迁移完成,迁移 JSON: ${migration_file}"
}
load_env_file "${REPO_ROOT}/.env"
load_env_file "${REPO_ROOT}/.env.local"
SPACETIME_DATABASE="${GENARRATIVE_SPACETIME_MAINCLOUD_DATABASE:-}"
SPACETIME_SERVER_URL="${GENARRATIVE_SPACETIME_MAINCLOUD_SERVER_URL:-https://maincloud.spacetimedb.com}"
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
exit 0
;;
--database)
SPACETIME_DATABASE="${2:?缺少 --database 的值}"
shift 2
;;
--server-url)
SPACETIME_SERVER_URL="${2:?缺少 --server-url 的值}"
shift 2
;;
--clear-database)
CLEAR_DATABASE=1
shift
;;
--no-migrate-on-conflict)
MIGRATE_ON_CONFLICT=0
shift
;;
--migration-dir)
MIGRATION_DIR="${2:?缺少 --migration-dir 的值}"
shift 2
;;
--migration-bootstrap-secret)
MIGRATION_BOOTSTRAP_SECRET="${2:?缺少 --migration-bootstrap-secret 的值}"
MIGRATION_BOOTSTRAP_SECRET_MODE="manual"
shift 2
;;
--no-migration-bootstrap-secret)
MIGRATION_BOOTSTRAP_SECRET=""
MIGRATION_BOOTSTRAP_SECRET_MODE="disabled"
shift
;;
*)
echo "[spacetime:maincloud] 未知参数: $1" >&2
usage >&2
exit 1
;;
esac
done
if [[ -z "${SPACETIME_DATABASE}" ]]; then
echo "[spacetime:maincloud] 缺少 GENARRATIVE_SPACETIME_MAINCLOUD_DATABASE。" >&2
echo "[spacetime:maincloud] 请在 .env.local 中配置,或通过 --database <database> 传入。" >&2
exit 1
fi
if ! command -v cargo >/dev/null 2>&1; then
echo "[spacetime:maincloud] 缺少 cargo 命令。" >&2
exit 1
fi
if ! command -v node >/dev/null 2>&1; then
echo "[spacetime:maincloud] 缺少 node 命令,无法生成迁移引导密钥。" >&2
exit 1
fi
if ! command -v spacetime >/dev/null 2>&1; then
echo "[spacetime:maincloud] 缺少 spacetime CLI请先安装并登录 Maincloud。" >&2
exit 1
fi
prepare_migration_bootstrap_secret
echo "[spacetime:maincloud] 构建 spacetime-module wasm"
cargo build \
--manifest-path "${SERVER_RS_DIR}/Cargo.toml" \
-p spacetime-module \
--target wasm32-unknown-unknown \
--release
PUBLISH_ARGS=(
publish
"${SPACETIME_DATABASE}"
--server "${SPACETIME_SERVER_ALIAS}"
--bin-path "${MODULE_PATH}"
--yes
)
if [[ "${CLEAR_DATABASE}" -eq 1 ]]; then
# Maincloud 清库只在 schema 冲突时触发,避免无冲突升级误删线上数据。
PUBLISH_ARGS+=(-c=on-conflict)
fi
echo "[spacetime:maincloud] 发布 SpacetimeDB wasm: ${SPACETIME_DATABASE} -> ${SPACETIME_SERVER_ALIAS}"
PUBLISH_LOG="$(mktemp)"
if ! run_publish "${PUBLISH_LOG}" "${PUBLISH_ARGS[@]}"; then
PUBLISH_OUTPUT="$(cat "${PUBLISH_LOG}")"
rm -f "${PUBLISH_LOG}"
if [[ "${CLEAR_DATABASE}" -eq 0 && "${MIGRATE_ON_CONFLICT}" -eq 1 ]] && is_publish_conflict_output "${PUBLISH_OUTPUT}"; then
run_conflict_migration_publish
else
echo "[spacetime:maincloud] 发布失败。" >&2
exit 1
fi
else
rm -f "${PUBLISH_LOG}"
fi
cat <<EOF
[spacetime:maincloud] 发布完成。api-server 可使用以下环境:
GENARRATIVE_SPACETIME_SERVER_URL=${SPACETIME_SERVER_URL}
GENARRATIVE_SPACETIME_DATABASE=${SPACETIME_DATABASE}
GENARRATIVE_SPACETIME_TOKEN=
EOF