mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-10-03 23:54:06 +00:00
发 v1.8.0 时发现包里没有 billing.lua —— 而 v1.8.0 的主打特性就是计费插件, 发布说明明写「随核心发布的示例插件(billing)」。 功能本身没坏:internal/lua/vm.go 用 //go:embed plugins/*.lua 把插件编进二进制, writeBundledPlugins 在 plugin_dir 不存在时把它写出来。实测确认(用正确格式的 配置):全新 plugin_dir 启动后自动生成 billing.lua 70031 字节,插件正常加载。 所以这是产物内容与发布说明不符,不是功能缺失 —— 运维解包检查时看不到它, 只能等首次启动后才发现。 三处改动: 1) tar.gz 现在带 plugins/。让运维能在安装前读它、改它,而不是启动后才知道。 2) 修复 dpkg-deb 那行。`dpkg-deb -I "$DIST"/llmsproxy_*.deb` 的 glob 展开成三个 .deb(dist 里堆着 1.5.9 / 1.6.0 / 本次产物),dpkg-deb 只认第一个归档,其余 参数被当成 control component 名,退出码 2。脚本是 set -euo pipefail,于是 在这里直接终止 —— 「done」从未打印,我加在后面的产物内容校验从来没有执行过, 真正跑过的只有上面那道 100KB 大小下限。改为取最新的那个 deb。 这个缺陷早于本次改动,是被它暴露出来的。 3) 新增产物内容校验:tar.gz 必须含 llmsproxy / config.example.yaml / llmsproxy.service / plugins/billing.lua,adapters 至少 10 个 .lua。 大小下限抓不到这个问题 —— 少 70KB 仍然远超 100KB,而首次启动自动 seed 的 行为会在运行时把它藏起来,运维看到插件正常工作就以为包是完整的。 目录按内容而非名字匹配:tar 列出的是 …/adapters/openai.lua,不存在裸 …/adapters 条目,第一版按名字 grep 把完整包判成了坏包。 两个变异验证:去掉 plugins 拷贝 → 「archive is missing plugins/billing.lua」 退出 1;只留 1 个 adapter → 「archive has only 1 adapters」退出 1。
149 lines
6.9 KiB
Bash
Executable File
149 lines
6.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Build the llmsproxy CORE distribution packages (deb + rpm + tar.gz).
|
|
#
|
|
# Unlike the GUI packages (Electron), the core is a single static-ish binary
|
|
# that server admins install with dpkg/rpm. The systemd unit bakes in the
|
|
# memory tuning (GOGC=50, MALLOC_ARENA_MAX=2) measured on the production box.
|
|
#
|
|
# Requires: go, nfpm (PATH), dpkg-deb/rpmbuild for verification
|
|
# Outputs: cmd/build/dist/llmsproxy_<ver>_amd64.deb
|
|
# cmd/build/dist/llmsproxy-<ver>.x86_64.rpm
|
|
# cmd/build/dist/llmsproxy-<ver>-linux-amd64.tar.gz
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$ROOT"
|
|
|
|
VERSION="${1:-$(git describe --tags --dirty 2>/dev/null | sed 's/^v//' || echo 0.1.0)}"
|
|
DIST="$ROOT/cmd/build/dist"
|
|
BUILD_BIN="$ROOT/cmd/build/llmsproxy"
|
|
|
|
log() { printf '\033[1;34m[core-dist]\033[0m %s\n' "$*"; }
|
|
|
|
# nfpm is installed via `go install` -> lives in GOPATH/bin, which may not be
|
|
# on the caller's PATH (non-interactive shells, cron). Resolve it explicitly.
|
|
NFPM_BIN="$(command -v nfpm || true)"
|
|
[ -z "$NFPM_BIN" ] && [ -n "$(go env GOPATH)" ] && {
|
|
[ -x "$(go env GOPATH)/bin/nfpm" ] && NFPM_BIN="$(go env GOPATH)/bin/nfpm"
|
|
}
|
|
[ -n "$NFPM_BIN" ] || {
|
|
echo "[core-dist] nfpm missing. Install: go install github.com/goreleaser/nfpm/v2/cmd/nfpm@v2.41.2"
|
|
exit 1
|
|
}
|
|
|
|
mkdir -p "$DIST"
|
|
|
|
# 1) build the core binary (luajit tag, -s -w like deploy.sh)
|
|
log "building core binary (luajit, stripped)..."
|
|
CGO_ENABLED=1 go build -tags luajit -trimpath -ldflags="-s -w" -o "$BUILD_BIN" ./cmd/llmsproxy
|
|
|
|
# 2) sanity gates: binary must contain the expected symbols and not be degenerate.
|
|
# NOTE under `set -o pipefail`, `strings | grep -q` is BROKEN: grep -q exits
|
|
# on first hit and closes the pipe, strings gets SIGPIPE -> pipeline returns
|
|
# 141 -> the check spuriously fails. Stage strings output in a temp file.
|
|
SYMS_TMP="$(mktemp)"
|
|
strings "$BUILD_BIN" > "$SYMS_TMP"
|
|
for sym in ProbeSlots reqRing shrinkStepLocked mergeUsage; do
|
|
grep -qF "$sym" "$SYMS_TMP" || {
|
|
echo "[core-dist] FATAL: binary missing expected symbol '$sym'"; rm -f "$SYMS_TMP"; exit 1
|
|
}
|
|
done
|
|
rm -f "$SYMS_TMP"
|
|
[ "$(stat -c%s "$BUILD_BIN")" -gt 5000000 ] || {
|
|
echo "[core-dist] FATAL: core binary implausibly small"; exit 1
|
|
}
|
|
log " ok: $(stat -c%s "$BUILD_BIN") bytes, symbols present"
|
|
|
|
# 3) deb + rpm via nfpm. nfpm v2 here does not render {{ .Env.VERSION }}, so
|
|
# stamp the version into a throwaway copy of the config; -t places the
|
|
# generated package in the dist dir.
|
|
log "packaging deb + rpm (v$VERSION)..."
|
|
NFPM_CFG="$(mktemp)" && sed "s/__VERSION__/$VERSION/g" "$ROOT/packaging/nfpm.yaml" > "$NFPM_CFG"
|
|
"$NFPM_BIN" package --config "$NFPM_CFG" --packager deb -t "$DIST"
|
|
"$NFPM_BIN" package --config "$NFPM_CFG" --packager rpm -t "$DIST"
|
|
rm -f "$NFPM_CFG"
|
|
|
|
# 4) tar.gz
|
|
log "packaging tar.gz..."
|
|
TAR_DIR="$DIST/llmsproxy-$VERSION-linux-amd64"
|
|
rm -rf "$TAR_DIR"; mkdir -p "$TAR_DIR"
|
|
cp "$BUILD_BIN" "$TAR_DIR/llmsproxy"
|
|
# Use the tracked sanitised template, NOT the local config.yaml: that file is
|
|
# gitignored and carries the operator's real gateway_keys / upstream keys. It
|
|
# was previously copied verbatim as config.example.yaml, which shipped a real,
|
|
# working admin key (sk-gw-local-0001) to every install — and postinst.sh copies
|
|
# the example to /etc when none exists, so it ended up live in production.
|
|
cp "$ROOT/packaging/config.example.yaml" "$TAR_DIR/config.example.yaml"
|
|
cp "$ROOT/packaging/llmsproxy.service" "$TAR_DIR/llmsproxy.service"
|
|
mkdir -p "$TAR_DIR/adapters"
|
|
cp "$ROOT"/internal/lua/adapters/*.lua "$TAR_DIR/adapters/"
|
|
|
|
# The shipped plugins go into the archive too, not just into the binary's
|
|
# embedded FS.
|
|
#
|
|
# Seeding works without them (internal/lua/vm.go embeds plugins/*.lua and
|
|
# writeBundledPlugins materialises them on first start — verified: a fresh
|
|
# plugin_dir gains billing.lua, 70031 bytes, and the plugin loads), so this is
|
|
# not a functional fix. It is a contents/claim mismatch: the v1.8.0 release
|
|
# notes tell the operator the billing plugin ships with the core, and this
|
|
# archive — the artefact they unpack and inspect — did not contain it. Shipping
|
|
# the file also means an operator can read and adapt it before installing,
|
|
# instead of only discovering it after the first boot writes it out.
|
|
mkdir -p "$TAR_DIR/plugins"
|
|
cp "$ROOT"/internal/lua/plugins/*.lua "$TAR_DIR/plugins/"
|
|
|
|
tar -C "$DIST" -czf "$DIST/llmsproxy-$VERSION-linux-amd64.tar.gz" \
|
|
"$(basename "$TAR_DIR")"
|
|
rm -rf "$TAR_DIR"
|
|
|
|
# 5) verify artifacts are healthy (the same gate that would have caught the
|
|
# 264KB exe fiasco)
|
|
log "verifying packages..."
|
|
for f in "$DIST"/llmsproxy_*.deb "$DIST"/llmsproxy-*.rpm "$DIST"/*.tar.gz; do
|
|
[ -f "$f" ] || continue
|
|
sz=$(stat -c%s "$f")
|
|
[ "$sz" -gt 100000 ] || { echo "[core-dist] FATAL: $f suspiciously small ($sz B)"; exit 1; }
|
|
log " ✓ $f ($((sz/1024)) KB)"
|
|
done
|
|
# dpkg-deb takes ONE archive; every extra argument is read as an additional
|
|
# control-component name. With the glob unquoted this used to expand to three
|
|
# .deb files (1.5.9 / 1.6.0 / the one just built) and dpkg-deb exited 2 — under
|
|
# `set -e` that aborted the script right here, so the "done" line never printed
|
|
# and NO content verification below ever ran. The size gate above was the only
|
|
# check that ever executed. Pick the newest deb explicitly.
|
|
newest_deb=$(ls -t "$DIST"/llmsproxy_*.deb 2>/dev/null | head -1 || true)
|
|
if [ -n "$newest_deb" ]; then
|
|
dpkg-deb -I "$newest_deb" 2>/dev/null | grep -E 'Package|Version' | head -2 || true
|
|
fi
|
|
|
|
# 6) the archive must actually carry what the release notes promise.
|
|
#
|
|
# A size floor alone cannot catch this: a tar.gz missing all 70KB of plugins
|
|
# still clears the 100KB threshold easily, and the seeded-on-first-boot
|
|
# behaviour hides it at runtime — the operator sees a working billing plugin and
|
|
# concludes the package is complete. Assert the file is in the archive.
|
|
log "verifying archive contents..."
|
|
TARBALL="$DIST/llmsproxy-$VERSION-linux-amd64.tar.gz"
|
|
[ -f "$TARBALL" ] || { echo "[core-dist] FATAL: $TARBALL missing"; exit 1; }
|
|
listing=$(tar tzf "$TARBALL" 2>/dev/null || true)
|
|
[ -n "$listing" ] || { echo "[core-dist] FATAL: cannot list $TARBALL"; exit 1; }
|
|
for want in llmsproxy config.example.yaml llmsproxy.service plugins/billing.lua; do
|
|
if ! printf '%s\n' "$listing" | grep -q "/$want\$"; then
|
|
echo "[core-dist] FATAL: archive is missing $want"
|
|
echo " (release notes claim the billing plugin ships with the core)"
|
|
exit 1
|
|
fi
|
|
log " ✓ archive contains $want"
|
|
done
|
|
# Directories are matched by their CONTENTS, not by name: tar written with
|
|
# `tar -czf` from a populated tree lists "…/adapters/openai.lua" and never a bare
|
|
# "…/adapters" entry, so an earlier version of this check that grepped for
|
|
# "/adapters$" reported a complete archive as broken.
|
|
n_adapters=$(printf '%s\n' "$listing" | grep -c "/adapters/.*\.lua\$" || true)
|
|
if [ "$n_adapters" -lt 10 ]; then
|
|
echo "[core-dist] FATAL: archive has only $n_adapters adapters (expected >= 10)"
|
|
exit 1
|
|
fi
|
|
log " ✓ archive contains $n_adapters adapters"
|
|
|
|
log "done: $DIST" |