From b6bbff19e9a23c7bcc5d64f210eb29225a30d4ed Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Tue, 27 Oct 2026 00:32:14 +0800 Subject: [PATCH] =?UTF-8?q?fix(packaging):=20=E5=8C=85=E5=86=85=20unit=20?= =?UTF-8?q?=E6=8C=87=E5=90=91=20/usr/bin=EF=BC=9Bcore-dist=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=97=A0=20nfpm=20=E6=89=93=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 两个都是发布链上的真缺陷,构建 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。 --- deploy.sh | 17 +++++ docs/deployment.md | 9 ++- packaging/core-dist.sh | 128 +++++++++++++++++++++++++++++++--- packaging/modelrouter.service | 5 +- 4 files changed, 146 insertions(+), 13 deletions(-) diff --git a/deploy.sh b/deploy.sh index 0d5e92d..108aaa2 100755 --- a/deploy.sh +++ b/deploy.sh @@ -115,6 +115,23 @@ precheck() { 如需全新部署,先把旧目录移开:mv /etc/llmsproxy /etc/llmsproxy.bak" fi + # unit 与二进制路径必须一致。包安装把二进制放 /usr/bin,源码部署放 + # /usr/local/bin;unit 里写错一行,systemctl restart 只会给 203/EXEC, + # 看起来像"部署成功但服务起不来"。此处提前拦住。 + local unit="/etc/systemd/system/modelrouter.service" + if [[ -f "$unit" ]]; then + local unit_exe + unit_exe=$(awk -F'=' '/^ExecStart=/{print $2; exit}' "$unit" | awk '{print $1}') + if [[ -n "$unit_exe" && "$unit_exe" != "$TARGET_BIN" && ! -x "$unit_exe" ]]; then + fail "unit 的 ExecStart 指向 $unit_exe,但该文件不存在。 + 包安装(deb/rpm)用 /usr/bin/modelrouter;源码部署(本脚本)用 $TARGET_BIN。 + 请把 $unit 的 ExecStart 改成 $TARGET_BIN,或改用包安装。" + fi + if [[ -n "$unit_exe" && "$unit_exe" != "$TARGET_BIN" ]]; then + warn "unit ExecStart=$unit_exe,而本脚本部署到 $TARGET_BIN(两者都存在,确认哪个该生效)" + fi + fi + if [[ -n "$NEW_CONFIG_SRC" ]]; then [[ -f "$NEW_CONFIG_SRC" ]] || fail "待部署配置不存在: $NEW_CONFIG_SRC" # 同一文件比对:--config 指向线上文件本身时没有"投放"动作可做, diff --git a/docs/deployment.md b/docs/deployment.md index 34bbb50..abea189 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -16,9 +16,14 @@ sudo ./deploy.sh --config other.yaml # 投放并部署另一份配置 部署目标(改名后): -| 项 | 路径 | +> 两种布局并存,区别只在于二进制位置:**包安装**(deb/rpm)放 `/usr/bin`, +> unit 自带;**源码部署**(`deploy.sh`)放 `/usr/local/bin`,需要让 unit 的 +> `ExecStart` 指向它。`deploy.sh` 会校验这一点,写错时直接中止而不是留下 +> 一个 203/EXEC 的服务。 + +| 项 | 路径(源码部署 / 包安装) | |---|---| -| 二进制 | `/usr/local/bin/modelrouter` | +| 二进制 | `/usr/local/bin/modelrouter` / `/usr/bin/modelrouter` | | 配置 | `/etc/modelrouter/config.yaml` | | 适配器 | `/etc/modelrouter/adapters/` | | 插件(可选) | `/etc/modelrouter/plugins/`(需在配置里设 `plugin_dir`) | diff --git a/packaging/core-dist.sh b/packaging/core-dist.sh index 005197c..d5c17c9 100755 --- a/packaging/core-dist.sh +++ b/packaging/core-dist.sh @@ -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" < +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 < "$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..." diff --git a/packaging/modelrouter.service b/packaging/modelrouter.service index d781fe0..e7951ec 100644 --- a/packaging/modelrouter.service +++ b/packaging/modelrouter.service @@ -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