From ba01da937b2a5df5a0c9a7948c3202c30fe08a43 Mon Sep 17 00:00:00 2001 From: pi-agent Date: Fri, 2 Oct 2026 23:31:29 +0800 Subject: [PATCH] =?UTF-8?q?fix(packaging):=20=E6=89=93=E5=8C=85=E5=B8=A6?= =?UTF-8?q?=E4=B8=8A=E8=AE=A1=E8=B4=B9=E6=8F=92=E4=BB=B6=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=80=E4=B8=AA=E8=AE=A9=E6=89=80=E6=9C=89?= =?UTF-8?q?=E4=BA=A7=E7=89=A9=E6=A0=A1=E9=AA=8C=E5=BD=A2=E5=90=8C=E8=99=9A?= =?UTF-8?q?=E8=AE=BE=E7=9A=84=E7=BC=BA=E9=99=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 发 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。 --- packaging/core-dist.sh | 56 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/packaging/core-dist.sh b/packaging/core-dist.sh index 5445fc4..3013480 100755 --- a/packaging/core-dist.sh +++ b/packaging/core-dist.sh @@ -77,6 +77,21 @@ 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" @@ -90,6 +105,45 @@ for f in "$DIST"/llmsproxy_*.deb "$DIST"/llmsproxy-*.rpm "$DIST"/*.tar.gz; do [ "$sz" -gt 100000 ] || { echo "[core-dist] FATAL: $f suspiciously small ($sz B)"; exit 1; } log " ✓ $f ($((sz/1024)) KB)" done -dpkg-deb -I "$DIST"/llmsproxy_*.deb 2>/dev/null | grep -E 'Package|Version' | head -2 +# 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" \ No newline at end of file