Files
ModelRouter/packaging/core-dist.sh
JianFeeeee 8f2c5572e7 chore!: 移除厂商私有协议适配器与渠道业务插件(合规清理)
G-Star 合规要求下,把"模拟官方客户端、复刻私有协议、自动领取平台额度"这一类
能力整体移出仓库。这些实现依赖各平台未公开的端点、签名算法与官方 client id,
属于绕过客户端校验获取额度,风险高于普通协议适配。

移除(93 个文件):
- 适配器:opencodezen(伪装 UA 取免费池)、trae、buddy、kimicode
- 插件:channels.lua(12 个平台登录/领额度/签到)、quota.lua(厂商额度探针)
- 随之删除的 78 个渠道/额度专用测试与 3 份渠道文档

同时修掉的既有问题:
- **硬编码第三方 OAuth client_secret**(Google Cloud Code 的 GOCSPX-…,两处)
  随 channels.lua 一起消失。
- `deploy.sh` 的 `sync_plugins` 整段删除:它仍按"内置插件是发行物"的旧前提
  把插件复制/覆盖进 plugin_dir,与"插件属运维资产、网关不安装不覆盖"的契约
  冲突。现在部署脚本只同步适配器与二进制/配置。
- 适配器清单去掉已移除项;e2e 配置改用显式 `auto:` 链(`models.priority` 早已
  移除,该测试此前已失效)。

保留(公开协议):openai / anthropic / gemini / deepseek / github / groq /
mistral / ollama / qwen38 / sensenova = 10 个适配器;插件示例 billing / usage。

验证:go build -tags luajit ./... OK;internal/lua 21.4s、provider、core 51s、
gateway 122.8s、e2e 9.0s 全绿。
2026-10-27 09:25:58 +08:00

252 lines
12 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# Build the modelrouter 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/modelrouter_<ver>_amd64.deb
# cmd/build/dist/modelrouter-<ver>.x86_64.rpm
# cmd/build/dist/modelrouter-<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/modelrouter"
log() { printf '\033[1;34m[core-dist]\033[0m %s\n' "$*"; }
# 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"
}
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://atomgit.com/HeNanMU-EEA/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://atomgit.com/HeNanMU-EEA/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
# 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)..."
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..."
TAR_DIR="$DIST/modelrouter-$VERSION-linux-amd64"
rm -rf "$TAR_DIR"; mkdir -p "$TAR_DIR"
cp "$BUILD_BIN" "$TAR_DIR/modelrouter"
# 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/modelrouter.service" "$TAR_DIR/modelrouter.service"
# 改名迁移脚本随包分发:升级自 llmsproxy 时代的机器需要它(dry-run 默认)。
cp "$ROOT/packaging/migrate-to-modelrouter.sh" "$TAR_DIR/migrate-to-modelrouter.sh"
mkdir -p "$TAR_DIR/adapters"
cp "$ROOT"/internal/lua/adapters/*.lua "$TAR_DIR/adapters/"
# The core distribution contains only the gateway and protocol adapters.
# Optional plugins remain in the source repository, but are neither embedded in
# the binary nor copied into release archives. Operators install selected Lua
# plugins into plugin_dir separately.
tar -C "$DIST" -czf "$DIST/modelrouter-$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"/modelrouter_*.deb "$DIST"/modelrouter-*.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 " OK $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"/modelrouter_*.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/modelrouter-$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 modelrouter config.example.yaml modelrouter.service migrate-to-modelrouter.sh; do
if ! printf '%s\n' "$listing" | grep -q "/$want\$"; then
echo "[core-dist] FATAL: archive is missing $want"
echo " (the archive must carry the binary, config template, unit and migration script)"
exit 1
fi
log " OK archive contains $want"
done
n_plugins=$(printf '%s\n' "$listing" | grep -c "/plugins/.*\.lua\$" || true)
if [ "$n_plugins" -ne 0 ]; then
echo "[core-dist] FATAL: archive unexpectedly contains $n_plugins plugins"
exit 1
fi
log " OK archive excludes optional plugins"
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 " OK archive contains $n_adapters adapters"
log "done: $DIST"