Merge branch 'codex/container-simulate'
# Conflicts: # .hermes/shared-memory/decision-log.md # server-rs/crates/api-server/src/puzzle.rs # server-rs/crates/spacetime-client/src/mapper.rs
This commit is contained in:
@@ -89,7 +89,7 @@ function printHelp(isError) {
|
||||
Commands:
|
||||
container:init 生成 deploy/container/api-server.env
|
||||
container:build 构建 api-server 容器镜像
|
||||
container:up 后台启动 api-server + nginx + otelcol
|
||||
container:up 后台启动 spacetimedb + api-server + nginx + otelcol
|
||||
container:down 停止并清理容器
|
||||
container:logs 查看容器日志
|
||||
container:ps 查看容器状态
|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PROVISION_TOOLS_DIR="${PROVISION_TOOLS_DIR:-provision-tools}"
|
||||
SPACETIME_BIN_SOURCE="${SPACETIME_BIN_SOURCE:-${PROVISION_TOOLS_DIR}/spacetime/spacetime}"
|
||||
OTELCOL_BIN_SOURCE="${OTELCOL_BIN_SOURCE:-${PROVISION_TOOLS_DIR}/otelcol-contrib}"
|
||||
|
||||
require_non_root_relative_path() {
|
||||
local label="$1"
|
||||
local path="$2"
|
||||
|
||||
if [[ -z "${path}" ]]; then
|
||||
echo "[server-provision] ${label} 不能为空。" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${path}" == /* || "${path}" == *..* ]]; then
|
||||
echo "[server-provision] ${label} 只能是工作区内的相对路径: ${path}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
require_path() {
|
||||
local path="$1"
|
||||
if [[ ! -e "${path}" ]]; then
|
||||
@@ -90,16 +108,16 @@ install_sccache() {
|
||||
fi
|
||||
|
||||
echo "[server-provision] 未找到 sccache,准备通过 cargo install sccache 安装。"
|
||||
if ! command -v cargo >/dev/null 2>&1; then
|
||||
echo "[server-provision] 未找到 cargo,无法自动安装 sccache。请先安装 Rust 工具链后重跑 Server-Provision。" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${DRY_RUN}" == "true" ]]; then
|
||||
echo "+ cargo install sccache --locked"
|
||||
return
|
||||
fi
|
||||
|
||||
if ! command -v cargo >/dev/null 2>&1; then
|
||||
echo "[server-provision] 未找到 cargo,无法自动安装 sccache。请先安装 Rust 工具链后重跑 Server-Provision。" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cargo install sccache --locked
|
||||
if ! command -v sccache >/dev/null 2>&1 && [[ ! -x /root/.cargo/bin/sccache ]]; then
|
||||
echo "[server-provision] sccache 安装后仍不可用,请检查 cargo bin 目录是否在 PATH 中。" >&2
|
||||
@@ -107,6 +125,42 @@ install_sccache() {
|
||||
fi
|
||||
}
|
||||
|
||||
sync_otelcol_install() {
|
||||
local target_bin="/usr/local/bin/otelcol-contrib"
|
||||
local source_bin="${OTELCOL_BIN_SOURCE}"
|
||||
local version="${OTELCOL_VERSION:-0.151.0}"
|
||||
local resolved_source="${source_bin}"
|
||||
|
||||
if [[ "${ENABLE_OTELCOL:-true}" != "true" ]]; then
|
||||
echo "[server-provision] ENABLE_OTELCOL=${ENABLE_OTELCOL:-},跳过 otelcol-contrib 配置。"
|
||||
return
|
||||
fi
|
||||
|
||||
if command -v readlink >/dev/null 2>&1; then
|
||||
resolved_source="$(readlink -f "${source_bin}" 2>/dev/null || echo "${source_bin}")"
|
||||
fi
|
||||
|
||||
if [[ ! -x "${resolved_source}" ]]; then
|
||||
echo "[server-provision] otelcol-contrib 不存在或不可执行: ${source_bin}" >&2
|
||||
echo "[server-provision] 请先在构建机准备好 otelcol-contrib ${version},再通过 provision-tools 上传到目标机。" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${DRY_RUN}" == "true" ]]; then
|
||||
echo "+ install -m 0755 ${resolved_source} ${target_bin}"
|
||||
return
|
||||
fi
|
||||
|
||||
install -m 0755 "${resolved_source}" "${target_bin}"
|
||||
if ! "${target_bin}" --version >/dev/null 2>&1; then
|
||||
echo "[server-provision] otelcol-contrib 安装后无法执行: ${target_bin}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! "${target_bin}" --version 2>/dev/null | grep -q "${version}"; then
|
||||
echo "[server-provision] 警告: otelcol-contrib 版本不是期望的 ${version}: $("${target_bin}" --version 2>/dev/null || true)" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
sync_spacetime_install() {
|
||||
local root_dir="$1"
|
||||
local target_bin_dir="${root_dir}/bin/current"
|
||||
@@ -115,14 +169,6 @@ sync_spacetime_install() {
|
||||
local resolved_command="${SPACETIME_BIN_SOURCE}"
|
||||
local install_dir=""
|
||||
local root_bin="${root_dir}/bin"
|
||||
local share_bin_dir=""
|
||||
local version_dir=""
|
||||
local parent_dir=""
|
||||
|
||||
if [[ -x "${target_cli}" && -x "${target_standalone}" ]]; then
|
||||
echo "[server-provision] SpacetimeDB current 目录已存在: ${target_bin_dir}"
|
||||
return
|
||||
fi
|
||||
|
||||
echo "[server-provision] 同步 SpacetimeDB current 目录到 ${target_bin_dir}"
|
||||
if [[ "${DRY_RUN}" == "true" ]]; then
|
||||
@@ -137,26 +183,10 @@ sync_spacetime_install() {
|
||||
install_dir="$(cd -- "$(dirname -- "${resolved_command}")" && pwd)"
|
||||
mkdir -p "${root_bin}"
|
||||
|
||||
for share_bin_dir in \
|
||||
"/usr/.local/share/spacetime/bin" \
|
||||
"/root/.local/share/spacetime/bin" \
|
||||
"${HOME:-}/.local/share/spacetime/bin"; do
|
||||
if [[ -d "${share_bin_dir}" ]]; then
|
||||
version_dir="$(find "${share_bin_dir}" -mindepth 1 -maxdepth 1 -type d | sort -V | tail -n 1)"
|
||||
if [[ -n "${version_dir}" && -x "${version_dir}/spacetimedb-cli" && -x "${version_dir}/spacetimedb-standalone" ]]; then
|
||||
echo "[server-provision] 同步 SpacetimeDB 安装: ${version_dir} -> ${target_bin_dir}"
|
||||
rm -rf "${target_bin_dir}"
|
||||
mkdir -p "${target_bin_dir}"
|
||||
cp -a "${version_dir}/." "${target_bin_dir}/"
|
||||
chmod +x "${target_cli}" "${target_standalone}"
|
||||
chown -R spacetimedb:spacetimedb "${root_bin}"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -d "${install_dir}/bin" ]]; then
|
||||
echo "[server-provision] 同步 SpacetimeDB 安装: ${install_dir}/bin -> ${root_bin}"
|
||||
rm -rf "${root_bin}"
|
||||
mkdir -p "${root_bin}"
|
||||
cp -a "${install_dir}/bin/." "${root_bin}/"
|
||||
elif [[ -x "${install_dir}/spacetimedb-cli" && -x "${install_dir}/spacetimedb-standalone" ]]; then
|
||||
echo "[server-provision] 同步 SpacetimeDB 安装: ${install_dir} -> ${target_bin_dir}"
|
||||
@@ -165,14 +195,8 @@ sync_spacetime_install() {
|
||||
cp -f "${install_dir}/spacetimedb-cli" "${target_cli}"
|
||||
cp -f "${install_dir}/spacetimedb-standalone" "${target_standalone}"
|
||||
chmod +x "${target_cli}" "${target_standalone}"
|
||||
elif [[ -f "${resolved_command}" ]]; then
|
||||
parent_dir="$(cd -- "${install_dir}/.." && pwd)"
|
||||
if [[ -d "${parent_dir}/bin" && -x "${parent_dir}/bin/current/spacetimedb-cli" && -x "${parent_dir}/bin/current/spacetimedb-standalone" ]]; then
|
||||
echo "[server-provision] 同步 SpacetimeDB 安装: ${parent_dir}/bin -> ${root_bin}"
|
||||
cp -a "${parent_dir}/bin/." "${root_bin}/"
|
||||
else
|
||||
echo "[server-provision] 未能从 spacetime 命令路径推断完整 SpacetimeDB 安装目录: ${resolved_command}" >&2
|
||||
fi
|
||||
else
|
||||
echo "[server-provision] 未能从 SpacetimeDB 交付包推断完整安装目录: ${resolved_command}" >&2
|
||||
fi
|
||||
|
||||
if [[ ! -x "${target_cli}" || ! -x "${target_standalone}" ]]; then
|
||||
@@ -396,6 +420,10 @@ render_api_env_example() {
|
||||
deploy/env/api-server.env.example
|
||||
}
|
||||
|
||||
render_otelcol_service() {
|
||||
cat deploy/systemd/otelcol-contrib.service
|
||||
}
|
||||
|
||||
validate_nginx_tls() {
|
||||
local cert_dir="/etc/letsencrypt/live/${SERVER_NAME}"
|
||||
if [[ "${SERVER_NAME}" == "genarrative.example.com" ]]; then
|
||||
@@ -532,6 +560,8 @@ render_api_service() {
|
||||
|
||||
require_path deploy/systemd/spacetimedb.service
|
||||
require_path deploy/systemd/genarrative-api.service
|
||||
require_path deploy/systemd/otelcol-contrib.service
|
||||
require_path deploy/otelcol/genarrative-debug.yaml
|
||||
require_path deploy/nginx/genarrative.conf
|
||||
require_path deploy/nginx/genarrative-dev-http.conf
|
||||
require_path deploy/nginx/snippets/genarrative-maintenance.conf
|
||||
@@ -541,6 +571,7 @@ require_path scripts/deploy/maintenance-off.sh
|
||||
require_path scripts/deploy/maintenance-status.sh
|
||||
|
||||
validate_server_names
|
||||
require_non_root_relative_path "PROVISION_TOOLS_DIR" "${PROVISION_TOOLS_DIR}"
|
||||
|
||||
echo "[server-provision] target=${DEPLOY_TARGET}, dry_run=${DRY_RUN}, nginx_config_mode=${NGINX_CONFIG_MODE}, source_commit=$(cat .jenkins-source-commit)"
|
||||
|
||||
@@ -548,7 +579,7 @@ run_cmd id
|
||||
install_build_dependencies
|
||||
install_nginx_brotli_modules
|
||||
install_sccache
|
||||
run_cmd mkdir -p "${SPACETIME_ROOT}" "${RELEASE_ROOT}" "$(dirname "${CURRENT_LINK}")" "$(dirname "${WEB_LINK}")" /etc/genarrative /var/lib/genarrative/maintenance /var/lib/genarrative/auth
|
||||
run_cmd mkdir -p "${SPACETIME_ROOT}" "${RELEASE_ROOT}" "$(dirname "${CURRENT_LINK}")" "$(dirname "${WEB_LINK}")" /etc/genarrative /var/lib/genarrative/maintenance /var/lib/genarrative/auth /var/lib/genarrative/tracking-outbox
|
||||
|
||||
if ! id spacetimedb >/dev/null 2>&1; then
|
||||
run_cmd useradd --system --home-dir "${SPACETIME_ROOT}" --shell /usr/sbin/nologin spacetimedb
|
||||
@@ -595,6 +626,16 @@ else
|
||||
echo "[server-provision] 已存在环境文件,保留不覆盖: ${API_ENV_FILE}"
|
||||
fi
|
||||
|
||||
if [[ "${ENABLE_OTELCOL:-true}" == "true" ]]; then
|
||||
sync_otelcol_install
|
||||
otelcol_service="$(mktemp)"
|
||||
render_otelcol_service >"${otelcol_service}"
|
||||
install_file "${otelcol_service}" /etc/systemd/system/otelcol-contrib.service 0644
|
||||
rm -f "${otelcol_service}"
|
||||
else
|
||||
echo "[server-provision] ENABLE_OTELCOL=${ENABLE_OTELCOL:-},跳过 otelcol-contrib service 安装。"
|
||||
fi
|
||||
|
||||
if [[ "${NGINX_CONFIG_MODE}" != "none" ]]; then
|
||||
install_nginx_config_with_rollback
|
||||
else
|
||||
@@ -603,7 +644,13 @@ fi
|
||||
|
||||
run_cmd systemctl daemon-reload
|
||||
if [[ "${ENABLE_SERVICES}" == "true" ]]; then
|
||||
if [[ "${ENABLE_OTELCOL:-true}" == "true" ]]; then
|
||||
run_cmd systemctl enable otelcol-contrib.service
|
||||
fi
|
||||
run_cmd systemctl enable spacetimedb.service genarrative-api.service
|
||||
if [[ "${ENABLE_OTELCOL:-true}" == "true" ]]; then
|
||||
run_cmd systemctl restart otelcol-contrib.service
|
||||
fi
|
||||
run_cmd systemctl restart spacetimedb.service
|
||||
wait_for_spacetimedb_service
|
||||
ensure_spacetime_owner_client_token
|
||||
|
||||
@@ -226,7 +226,7 @@ npm run loadtest:k6:works
|
||||
## 排障
|
||||
|
||||
- 如果公开 gallery 返回 `creation_entry_disabled` 或 503,检查本地 creation entry 配置是否禁用了对应入口。
|
||||
- 如果高压下返回 429,优先确认目标环境是否设置了 `GENARRATIVE_API_MAX_CONCURRENT_REQUESTS`。429 表示 api-server 应用层背压已生效,不等同于业务错误;继续看内存、p95、`http_req_failed` 和 OTLP / Nginx timing 判断阈值是否偏低。
|
||||
- 如果高压下返回 429,优先确认目标环境是否设置了 `GENARRATIVE_API_MAX_CONCURRENT_REQUESTS` 以及 `GENARRATIVE_API_GALLERY_MAX_CONCURRENT_REQUESTS`、`GENARRATIVE_API_DETAIL_MAX_CONCURRENT_REQUESTS`、`GENARRATIVE_API_ADMIN_MAX_CONCURRENT_REQUESTS`。429 表示 Nginx 或 api-server 背压已生效,不等同于业务错误;继续看内存、p95、`http_req_failed` 和 OTLP / Nginx timing 判断阈值是否偏低。
|
||||
- 如果直连 `api-server` 压测出现 `connection refused` 或 status 0,说明压力已经打到 TCP 监听 / accept 层;此时同时检查 `GENARRATIVE_API_LISTEN_BACKLOG`、Nginx upstream keepalive 和是否需要在 Nginx 前置限流,不能只靠应用层背压解释。
|
||||
- 如果个人作品列表返回 401,确认 `AUTH_TOKEN` 是当前 api-server 可识别的 access token。
|
||||
- 如果详情全部 404,确认是否已向目标环境导入与 `WORKS_DATA` 一致的数据。
|
||||
@@ -247,7 +247,7 @@ sudo journalctl -u genarrative-api.service -f
|
||||
sudo journalctl -u spacetimedb.service -f
|
||||
```
|
||||
|
||||
api-server 的 OpenTelemetry 默认关闭。需要验证 OTLP traces / metrics / logs 时,先在服务器本机启动只监听 `127.0.0.1` 的 `otelcol-contrib` debug exporter:
|
||||
api-server 的 OpenTelemetry 在生产与容器模板里默认开启。需要临时关闭时,显式把 `GENARRATIVE_OTEL_ENABLED=false`;需要验证 OTLP traces / metrics / logs 时,先在服务器本机启动只监听 `127.0.0.1` 的 `otelcol-contrib` debug exporter:
|
||||
|
||||
```bash
|
||||
npm run otel:debug
|
||||
@@ -317,12 +317,14 @@ Rider 的 Logs 面板展示的是 OTLP log event 自身字段,不会自动把
|
||||
- `process.memory.usage`:进程常驻内存 / RSS。
|
||||
- `process.memory.virtual`:进程虚拟内存;Windows 当前按 `PrivateUsage` 上报,Linux 取 `VmSize`。
|
||||
- `genarrative.process.memory.private`:进程私有内存,Windows 来自 `PrivateUsage`,Linux 近似取 `/proc/self/status` 的 `VmData`。
|
||||
- `process.cpu.time`:进程 user + system 累计 CPU 秒数。
|
||||
- `genarrative.process.cpu.usage_percent`:两次指标采集之间的进程 CPU 使用率;100% 约等于占满 1 个 CPU core。
|
||||
- `process.thread.count`:线程数。
|
||||
- `process.windows.handle.count`:Windows 句柄数。
|
||||
- `process.unix.file_descriptor.count`:Linux 文件描述符数。
|
||||
- `genarrative.http.server.response_bodies.in_flight`:Axum / Hyper 仍持有的响应 body 数;如果内存高但该值很低,说明热点不在业务 handler 生命周期内。
|
||||
- `genarrative.http.server.request_permits.available`:应用层 HTTP 背压剩余 permit 数;如果该值未接近 0,说明没有打满 `GENARRATIVE_API_MAX_CONCURRENT_REQUESTS`。
|
||||
- `genarrative.puzzle_gallery.cache.hits` / `genarrative.puzzle_gallery.cache.misses` / `genarrative.puzzle_gallery.cache.rebuilds`:拼图广场响应缓存命中、未命中和重建次数。
|
||||
- `genarrative.http.server.request_permits.available`:应用层 HTTP 背压剩余 permit 数,带 `pool=default|gallery|detail|admin`;如果目标 pool 未接近 0,说明没有打满对应 `GENARRATIVE_API_*_MAX_CONCURRENT_REQUESTS`。
|
||||
- `genarrative.puzzle_gallery.cache.hits` / `genarrative.puzzle_gallery.cache.stale_hits` / `genarrative.puzzle_gallery.cache.misses` / `genarrative.puzzle_gallery.cache.refreshes_started` / `genarrative.puzzle_gallery.cache.refreshes_failed` / `genarrative.puzzle_gallery.cache.rebuilds`:拼图广场响应缓存 fresh 命中、stale 命中、未命中、后台刷新和重建次数。
|
||||
- `genarrative.puzzle_gallery.cache.rebuild.duration`:拼图广场缓存重建耗时。
|
||||
- `genarrative.puzzle_gallery.cache.data_json_bytes`:拼图广场缓存内预序列化 data JSON 大小。
|
||||
- `genarrative.spacetime.read.calls` / `genarrative.spacetime.read.duration_ms`:SpacetimeDB 订阅本地 cache 读次数和耗时;`read=list_puzzle_gallery` 表示当前路径走 view / local cache,不是 procedure。
|
||||
@@ -336,7 +338,7 @@ Rider 的 Logs 面板展示的是 OTLP log event 自身字段,不会自动把
|
||||
```bash
|
||||
systemctl show genarrative-api.service -p LimitNOFILE -p TasksMax
|
||||
cat /proc/$(pidof api-server)/limits
|
||||
tr '\0' '\n' < /proc/$(pidof api-server)/environ | grep GENARRATIVE_API_MAX_CONCURRENT_REQUESTS
|
||||
tr '\0' '\n' < /proc/$(pidof api-server)/environ | grep 'GENARRATIVE_API_.*MAX_CONCURRENT_REQUESTS'
|
||||
ss -ltnp | grep 8082
|
||||
curl -sS http://127.0.0.1:8082/healthz
|
||||
```
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
{
|
||||
"source": "spacetime-migration-7.local.json",
|
||||
"generatedAt": "2026-05-11T13:09:51.569Z",
|
||||
"source": "spacetime-migration-1.json",
|
||||
"generatedAt": "2026-05-18T11:54:04.280Z",
|
||||
"counts": {
|
||||
"puzzle_work_profile": 3,
|
||||
"custom_world_profile": 1,
|
||||
"match3d_work_profile": 0
|
||||
"match3d_work_profile": 0,
|
||||
"square_hole_work_profile": 0,
|
||||
"visual_novel_work_profile": 0
|
||||
},
|
||||
"tables": {
|
||||
"puzzle_work_profile": [
|
||||
@@ -113,7 +115,9 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"match3d_work_profile": []
|
||||
"match3d_work_profile": [],
|
||||
"square_hole_work_profile": [],
|
||||
"visual_novel_work_profile": []
|
||||
},
|
||||
"profileIds": {
|
||||
"puzzle": [
|
||||
|
||||
@@ -137,12 +137,12 @@ function unwrapPayload(json) {
|
||||
}
|
||||
|
||||
function hasCollection(payload, keys) {
|
||||
return keys.some((key) => Array.isArray(payload?.[key]));
|
||||
return Boolean(payload) && keys.some((key) => Array.isArray(payload[key]));
|
||||
}
|
||||
|
||||
function firstCollection(payload, keys) {
|
||||
for (const key of keys) {
|
||||
if (Array.isArray(payload?.[key])) return payload[key];
|
||||
if (payload && Array.isArray(payload[key])) return payload[key];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
@@ -152,10 +152,11 @@ function hasListItemShape(payload, keys) {
|
||||
if (collection.length === 0) return true;
|
||||
const item = collection[0];
|
||||
const hasId = Boolean(
|
||||
item?.profileId || item?.profile_id || item?.workId || item?.work_id || item?.publicWorkCode,
|
||||
item &&
|
||||
(item.profileId || item.profile_id || item.workId || item.work_id || item.publicWorkCode),
|
||||
);
|
||||
const hasTitle = Boolean(
|
||||
item?.title || item?.workTitle || item?.work_title || item?.levelName || item?.worldName,
|
||||
item && (item.title || item.workTitle || item.work_title || item.levelName || item.worldName),
|
||||
);
|
||||
return hasId && hasTitle;
|
||||
}
|
||||
@@ -213,7 +214,8 @@ function performDetailRequest() {
|
||||
const payload = unwrapPayload(json);
|
||||
const ok = check(response, {
|
||||
[`${endpoint.name} status is 200`]: (res) => res.status === 200,
|
||||
[`${endpoint.name} has detail payload`]: () => endpoint.expectKeys.some((key) => payload?.[key]),
|
||||
[`${endpoint.name} has detail payload`]: () =>
|
||||
Boolean(payload) && endpoint.expectKeys.some((key) => payload[key]),
|
||||
});
|
||||
worksDetailShapeErrorRate.add(!ok, { endpoint: endpoint.name });
|
||||
}
|
||||
|
||||
132
scripts/prepare-server-provision-tools.sh
Normal file
132
scripts/prepare-server-provision-tools.sh
Normal file
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PROVISION_TOOLS_DIR="${PROVISION_TOOLS_DIR:-provision-tools}"
|
||||
OTELCOL_VERSION="${OTELCOL_VERSION:-0.151.0}"
|
||||
OTELCOL_DOWNLOAD_ROOT="${OTELCOL_DOWNLOAD_ROOT:-https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download}"
|
||||
SPACETIME_INSTALLER_URL="${SPACETIME_INSTALLER_URL:-https://install.spacetimedb.com}"
|
||||
SPACETIME_DOWNLOAD_ROOT="${SPACETIME_DOWNLOAD_ROOT:-https://github.com/clockworklabs/SpacetimeDB/releases/latest/download}"
|
||||
PROVISION_TOOLS_TMP_PARENT="${PROVISION_TOOLS_TMP_PARENT:-${WORKSPACE:-$(pwd)}/.tmp/server-provision-tools}"
|
||||
TMP_DIR_TO_CLEAN=""
|
||||
|
||||
cleanup_tmp_dir() {
|
||||
if [[ -n "${TMP_DIR_TO_CLEAN}" ]]; then
|
||||
rm -rf "${TMP_DIR_TO_CLEAN}"
|
||||
fi
|
||||
}
|
||||
|
||||
require_cmd() {
|
||||
local name="$1"
|
||||
if ! command -v "${name}" >/dev/null 2>&1; then
|
||||
echo "[prepare-provision-tools] 缺少命令: ${name}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
download_file() {
|
||||
local url="$1"
|
||||
local output="$2"
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fsSL --retry 3 --retry-delay 2 "${url}" -o "${output}"
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget -O "${output}" "${url}"
|
||||
else
|
||||
echo "[prepare-provision-tools] 需要 curl 或 wget 下载: ${url}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
make_spacetime_wrapper() {
|
||||
local target="$1"
|
||||
|
||||
cat >"${target}" <<'EOF'
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
SELF_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
exec "$SELF_DIR/bin/current/spacetimedb-cli" "$@"
|
||||
EOF
|
||||
chmod 0755 "${target}"
|
||||
}
|
||||
|
||||
prepare_otelcol() {
|
||||
local tmp_dir="$1"
|
||||
local archive="${tmp_dir}/otelcol-contrib.tar.gz"
|
||||
local extract_dir="${tmp_dir}/otelcol-contrib"
|
||||
local url="${OTELCOL_DOWNLOAD_ROOT}/v${OTELCOL_VERSION}/otelcol-contrib_${OTELCOL_VERSION}_linux_amd64.tar.gz"
|
||||
local target="${PROVISION_TOOLS_DIR}/otelcol-contrib"
|
||||
|
||||
require_cmd tar
|
||||
|
||||
echo "[prepare-provision-tools] 下载 otelcol-contrib: ${url}"
|
||||
mkdir -p "${extract_dir}"
|
||||
download_file "${url}" "${archive}"
|
||||
tar -xzf "${archive}" -C "${extract_dir}"
|
||||
|
||||
if [[ ! -x "${extract_dir}/otelcol-contrib" ]]; then
|
||||
echo "[prepare-provision-tools] otelcol-contrib 包中缺少可执行文件。" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
install -m 0755 "${extract_dir}/otelcol-contrib" "${target}"
|
||||
"${target}" --version >/dev/null
|
||||
}
|
||||
|
||||
prepare_spacetime() {
|
||||
local tmp_dir="$1"
|
||||
local install_root="${tmp_dir}/spacetime-root"
|
||||
local target_dir="${PROVISION_TOOLS_DIR}/spacetime"
|
||||
|
||||
echo "[prepare-provision-tools] 使用官方安装器准备 SpacetimeDB: ${SPACETIME_INSTALLER_URL}"
|
||||
mkdir -p "${install_root}"
|
||||
download_file "${SPACETIME_INSTALLER_URL}" "${tmp_dir}/spacetime-install.sh"
|
||||
chmod 0755 "${tmp_dir}/spacetime-install.sh"
|
||||
TMPDIR="${tmp_dir}" SPACETIME_DOWNLOAD_ROOT="${SPACETIME_DOWNLOAD_ROOT}" sh "${tmp_dir}/spacetime-install.sh" --root-dir "${install_root}" -y
|
||||
|
||||
if [[ ! -x "${install_root}/bin/current/spacetimedb-cli" ]]; then
|
||||
echo "[prepare-provision-tools] SpacetimeDB 安装结果缺少 bin/current/spacetimedb-cli。" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -x "${install_root}/bin/current/spacetimedb-standalone" ]]; then
|
||||
echo "[prepare-provision-tools] SpacetimeDB 安装结果缺少 bin/current/spacetimedb-standalone。" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "${target_dir}"
|
||||
cp -a "${install_root}/bin" "${target_dir}/bin"
|
||||
make_spacetime_wrapper "${target_dir}/spacetime"
|
||||
|
||||
"${target_dir}/spacetime" --version >/dev/null
|
||||
}
|
||||
|
||||
main() {
|
||||
local tmp_dir
|
||||
|
||||
require_cmd chmod
|
||||
require_cmd cp
|
||||
require_cmd install
|
||||
require_cmd mktemp
|
||||
require_cmd rm
|
||||
|
||||
mkdir -p "${PROVISION_TOOLS_TMP_PARENT}"
|
||||
tmp_dir="$(mktemp -d "${PROVISION_TOOLS_TMP_PARENT%/}/run.XXXXXX")"
|
||||
TMP_DIR_TO_CLEAN="${tmp_dir}"
|
||||
trap cleanup_tmp_dir EXIT
|
||||
|
||||
rm -rf "${PROVISION_TOOLS_DIR}"
|
||||
mkdir -p "${PROVISION_TOOLS_DIR}"
|
||||
|
||||
prepare_otelcol "${tmp_dir}"
|
||||
prepare_spacetime "${tmp_dir}"
|
||||
|
||||
cat >"${PROVISION_TOOLS_DIR}/MANIFEST.txt" <<EOF
|
||||
otelcol-contrib ${OTELCOL_VERSION}
|
||||
spacetime installer ${SPACETIME_INSTALLER_URL}
|
||||
spacetime download root ${SPACETIME_DOWNLOAD_ROOT}
|
||||
EOF
|
||||
|
||||
echo "[prepare-provision-tools] 工具包已准备: ${PROVISION_TOOLS_DIR}"
|
||||
find "${PROVISION_TOOLS_DIR}" -maxdepth 5 \( -type f -o -type l \) | sort
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user