270 lines
8.4 KiB
Bash
270 lines
8.4 KiB
Bash
#!/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
|