fix(packaging): 包内 unit 指向 /usr/bin;core-dist 支持无 nfpm 打包

两个都是发布链上的真缺陷,构建 v3.0.0 产物时才暴露:

1. **unit 与包内二进制路径不一致**:deb/rpm 把二进制装到 `/usr/bin/modelrouter`,
   而 `modelrouter.service` 的 `ExecStart` 写 `/usr/local/bin/modelrouter`。
   装包后 `systemctl start` 只会给 203/EXEC,看起来像"装好了但起不来"。
   - 包内 unit 改 `/usr/bin/modelrouter`(FHS,装包即用)
   - 源码部署仍用 `/usr/local/bin`,`deploy.sh` 增加校验:unit 的 ExecStart
     指向不存在的文件时**中止**并说明两种布局的区别
   - docs/deployment.md 同步说明

2. **core-dist 硬依赖 nfpm**:构建机离线(GOPROXY 不可达)时装不上 nfpm,
   发布直接被堵死。现在 nfpm 优先,缺失时回退 dpkg-deb + rpmbuild,
   两者产出同一套文件布局,并继续由末尾的 archive-content 门禁校验。
   - rpm 回退踩到的坑:postinst/prerm 末尾无换行时,`%preun` 会粘在
     `exit 0` 后面,整个 `%files` 段被吞,rpmbuild 报
     "Installed (but unpackaged) file(s) found"。
   - `%dir /etc/modelrouter` 要求 buildroot 里存在该目录,否则
     "Directory not found"。

验证:`./packaging/core-dist.sh 3.0.0` 产出 deb(4220KB)/rpm(4229KB)/tar(4145KB);
包内 unit `ExecStart=/usr/bin/modelrouter`,`systemd-analyze verify --root` 通过;
deb/rpm 均不含 `plugins/`,adapters=14。
This commit is contained in:
JianFeeeee
2026-10-27 00:32:14 +08:00
parent d5bbb0eca5
commit b6bbff19e9
4 changed files with 146 additions and 13 deletions

View File

@ -20,19 +20,122 @@ BUILD_BIN="$ROOT/cmd/build/modelrouter"
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 is the primary packager, but it is a `go install` artifact and the build
# host may be offline (no GOPROXY) — a release must not depend on being able to
# fetch it. Fall back to dpkg-deb / rpmbuild, which are present wherever the
# packages are actually verified. Both paths produce the same file layout.
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
}
if [ -n "$NFPM_BIN" ]; then
log "packager: nfpm ($NFPM_BIN)"
else
command -v dpkg-deb >/dev/null || { echo "[core-dist] neither nfpm nor dpkg-deb available"; exit 1; }
command -v rpmbuild >/dev/null || { echo "[core-dist] neither nfpm nor rpmbuild available"; exit 1; }
log "packager: nfpm missing -> dpkg-deb + rpmbuild fallback"
fi
mkdir -p "$DIST"
# ---- fallback packagers -------------------------------------------------
# Same file layout as packaging/nfpm.yaml. Kept in this file (not a separate
# script) so the two paths cannot drift silently: both are exercised by the
# archive-content gate at the end.
package_deb_fallback() {
local root="$ROOT/cmd/build/pkgroot-deb"
rm -rf "$root"
mkdir -p "$root/DEBIAN" "$root/usr/bin" "$root/etc/systemd/system" \
"$root/usr/share/modelrouter/adapters" "$root/usr/share/doc/modelrouter"
install -m 0755 "$BUILD_BIN" "$root/usr/bin/modelrouter"
install -m 0644 "$ROOT/packaging/modelrouter.service" "$root/etc/systemd/system/modelrouter.service"
install -m 0644 "$ROOT/packaging/config.example.yaml" "$root/usr/share/modelrouter/config.example.yaml"
install -m 0644 "$ROOT"/internal/lua/adapters/*.lua "$root/usr/share/modelrouter/adapters/"
install -m 0755 "$ROOT/packaging/install-browser-stack.sh" "$root/usr/share/modelrouter/install-browser-stack.sh"
install -m 0644 "$ROOT/README.md" "$root/usr/share/doc/modelrouter/README.md"
install -m 0755 "$ROOT/packaging/postinst.sh" "$root/DEBIAN/postinst"
install -m 0755 "$ROOT/packaging/prerm.sh" "$root/DEBIAN/prerm"
local inst_size
inst_size=$(du -sk "$root" | awk '{print $1}')
cat > "$root/DEBIAN/control" <<EOF
Package: modelrouter
Version: $VERSION
Section: net
Priority: optional
Architecture: amd64
Installed-Size: $inst_size
Maintainer: JianFeeeee <dev@modelrouter.local>
Recommends: ca-certificates
Homepage: https://gitcode.com/JianFeeeee/ModelRouter
Description: Unified OpenAI-compatible multi-source LLM gateway.
Routes requests across multiple upstream LLM providers with Lua adapters,
model scheduling, quota windows, an elastic Lua worker pool, and a Web UI.
EOF
dpkg-deb --root-owner-group -Zgzip --build "$root" \
"$DIST/modelrouter_${VERSION}_amd64.deb" >/dev/null
log " built $(basename "$DIST/modelrouter_${VERSION}_amd64.deb")"
}
package_rpm_fallback() {
local top="$ROOT/cmd/build/rpmbuild"
rm -rf "$top"
mkdir -p "$top"/{BUILD,RPMS,SOURCES,SPECS,BUILDROOT}
local spec="$top/SPECS/modelrouter.spec"
{
cat <<EOF
Name: modelrouter
Version: $VERSION
Release: 1
Summary: Unified OpenAI-compatible multi-source LLM gateway
License: MIT
URL: https://gitcode.com/JianFeeeee/ModelRouter
BuildArch: x86_64
Requires: ca-certificates
AutoReqProv: no
%description
Unified OpenAI-compatible multi-source LLM gateway. Routes requests across
multiple upstream LLM providers with Lua adapters, model scheduling, quota
windows, an elastic Lua worker pool, and a Web UI.
%install
mkdir -p %{buildroot}/usr/bin %{buildroot}/etc/systemd/system \\
%{buildroot}/usr/share/modelrouter/adapters \\
%{buildroot}/usr/share/doc/modelrouter %{buildroot}/etc/modelrouter
install -m 0755 $BUILD_BIN %{buildroot}/usr/bin/modelrouter
install -m 0644 $ROOT/packaging/modelrouter.service %{buildroot}/etc/systemd/system/modelrouter.service
install -m 0644 $ROOT/packaging/config.example.yaml %{buildroot}/usr/share/modelrouter/config.example.yaml
install -m 0644 $ROOT/internal/lua/adapters/*.lua %{buildroot}/usr/share/modelrouter/adapters/
install -m 0755 $ROOT/packaging/install-browser-stack.sh %{buildroot}/usr/share/modelrouter/install-browser-stack.sh
install -m 0644 $ROOT/README.md %{buildroot}/usr/share/doc/modelrouter/README.md
%post
EOF
sed '1{/^#!/d}' "$ROOT/packaging/postinst.sh"
# Script files may lack a trailing newline. Without this, `%preun` is glued
# onto the last line of %post and the whole %files section is swallowed —
# rpmbuild then fails with "Installed (but unpackaged) file(s) found".
echo
echo '%preun'
sed '1{/^#!/d}' "$ROOT/packaging/prerm.sh"
echo
cat <<'EOF'
%files
/usr/bin/modelrouter
/etc/systemd/system/modelrouter.service
/usr/share/modelrouter
/usr/share/doc/modelrouter/README.md
%dir /etc/modelrouter
EOF
} > "$spec"
rpmbuild --define "_topdir $top" --define "_binary_payload w9.gzdio" -bb "$spec" \
>/dev/null 2>&1 || {
echo "[core-dist] FATAL: rpmbuild failed; spec kept at $spec"; exit 1
}
local built
built=$(find "$top/RPMS" -name 'modelrouter-*.rpm' | head -1)
[ -n "$built" ] || { echo "[core-dist] FATAL: rpmbuild produced no rpm"; exit 1; }
cp "$built" "$DIST/modelrouter-${VERSION}.x86_64.rpm"
log " built $(basename "$DIST/modelrouter-${VERSION}.x86_64.rpm")"
}
# 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/modelrouter
@ -58,10 +161,15 @@ log " ok: $(stat -c%s "$BUILD_BIN") bytes, symbols present"
# 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"
if [ -n "$NFPM_BIN" ]; then
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"
else
package_deb_fallback
package_rpm_fallback
fi
# 4) tar.gz
log "packaging tar.gz..."

View File

@ -15,7 +15,10 @@ Type=simple
# the extra GC cycles are free.
Environment=GOGC=50
Environment=MALLOC_ARENA_MAX=2
ExecStart=/usr/local/bin/modelrouter -config /etc/modelrouter/config.yaml
# Package installs put the binary in /usr/bin (FHS). A source deployment via
# deploy.sh uses /usr/local/bin — that script verifies the unit below points at
# a binary that actually exists, so the two layouts cannot silently disagree.
ExecStart=/usr/bin/modelrouter -config /etc/modelrouter/config.yaml
WorkingDirectory=/etc/modelrouter
Restart=always
RestartSec=5