mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-11-07 09:47:23 +00:00
产品名早已是 ModelRouter,但仓库标识符仍沿用旧名。这次统一,读写两侧都做兼容, 已部署的机器不会因为顺序问题立刻失效。 - go module `llmsproxy` → `modelrouter`;全仓 import 与 `cmd/modelrouter` 同步。 - `Makefile BINARY=modelrouter`;发行包名、systemd unit、/usr/share 资源、 `/usr/local/bin` 目标全部改为 modelrouter。 - 默认配置目录 `/etc/llmsproxy` → `/etc/modelrouter`。**读取侧回退**:插件与 `storeDir` 仍会检查旧路径,所以"先换二进制、后改 unit"不会立即失效。 - 环境变量 `LLMSPROXY_*`(PLUGIN_DIR / XDISPLAY_BASE / ARCH / LIVE_ZEN / BUILD_DIR)改为 `MODELROUTER_*`;`LLMS_PROXY_MASTER_KEY` → `MODELROUTER_MASTER_KEY`。旧名在读取侧继续接受。 - 桌面端内嵌核心 `bin/llmsproxy` → `bin/modelrouter`,并保留旧文件名回退, 避免升级到一半的 profile 被判成"core binary missing"。 - 新增 `packaging/migrate-to-modelrouter.sh`:**默认 dry-run**,复制而非移动、 旧目录永不删除、支持 `--rollback`;随 release tar 分发并由 core-dist 校验。 - `deploy.sh` 增加改名保护:发现旧 `/etc/llmsproxy` 而新配置不存在时**中止**, 否则会部署出一个"能启动但没有 key、没有源"的空实例。 - 临时目录前缀 `/tmp/llmsproxy-*` → `/tmp/modelrouter-*`;清理逻辑同时回收两代。 兼容别名(`__llmsproxy_*` 全局、`llmsproxy` SDK 别名、旧路径/旧 env)刻意保留, 它们是升级路径而非遗留;docs/plugins.md 已标注为废弃。 本提交未包含:`adapters/opencodezen.lua`、`adapter_opencodezen_test.go`、 `adapter_opencodezen_live_test.go`(OpenCode UA/免费池的既有改动,另批提交)。
130 lines
4.8 KiB
Go
130 lines
4.8 KiB
Go
// Command modelrouter is a standalone gateway that exposes multiple upstream LLM
|
|
// sources behind a unified OpenAI-compatible HTTP API. Protocol differences are
|
|
// handled by Lua adapters (per source), optionally signing outgoing requests.
|
|
// It supports explicit model selection or AUTO routing by priority, image
|
|
// generation, multimodal payloads, a web UI for adapter/source management, and
|
|
// concurrent/queued scheduling with backoff and failover.
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"modelrouter/internal/config"
|
|
"modelrouter/internal/core"
|
|
"modelrouter/internal/gateway"
|
|
)
|
|
|
|
func main() {
|
|
cfgPath := flag.String("config", "config.yaml", "path to gateway config file")
|
|
checkOnly := flag.Bool("check", false, "validate the config file and exit (0 = valid, 1 = invalid); nothing is started and no file is written")
|
|
showSecrets := flag.Bool("show-secrets", false, "print the config's credentials in the clear and exit; nothing is started and no file is written")
|
|
flag.Parse()
|
|
|
|
// -check is the deploy-time preflight: parse and validate the config
|
|
// without starting the Lua VM, touching runtime.json, or binding a port.
|
|
// It deliberately does NOT call EnsureDefault, so a missing file is an
|
|
// error here instead of being silently created.
|
|
if *checkOnly {
|
|
if _, err := os.Stat(*cfgPath); err != nil {
|
|
log.Fatalf("[modelrouter] check: %v", err)
|
|
}
|
|
if _, err := config.Load(*cfgPath); err != nil {
|
|
log.Fatalf("[modelrouter] check: %v", err)
|
|
}
|
|
log.Printf("[modelrouter] check: %s is valid", *cfgPath)
|
|
return
|
|
}
|
|
|
|
// -show-secrets is the operator escape hatch for a config whose
|
|
// credentials are sealed at rest: it prints them to stdout and exits
|
|
// without starting anything or writing anything. Nothing else in the
|
|
// program prints a credential, and this path never logs to a file.
|
|
if *showSecrets {
|
|
if _, err := os.Stat(*cfgPath); err != nil {
|
|
log.Fatalf("[modelrouter] show-secrets: %v", err)
|
|
}
|
|
if err := config.PrintSecrets(*cfgPath); err != nil {
|
|
log.Fatalf("[modelrouter] show-secrets: %v", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
created, err := config.EnsureDefault(*cfgPath)
|
|
if err != nil {
|
|
log.Fatalf("[modelrouter] config: %v", err)
|
|
}
|
|
|
|
c, err := core.New(*cfgPath)
|
|
if err != nil {
|
|
log.Fatalf("[modelrouter] core: %v", err)
|
|
}
|
|
defer c.Close()
|
|
|
|
if created {
|
|
for _, k := range c.GatewayKeys() {
|
|
log.Printf("[modelrouter] generated default config at %s — admin key: %s (rotate it in the WebUI after first login)", *cfgPath, k)
|
|
}
|
|
}
|
|
|
|
gw, err := gateway.New(c)
|
|
if err != nil {
|
|
log.Fatalf("[modelrouter] gateway: %v", err)
|
|
}
|
|
|
|
// Ops hygiene: surface the two most common footguns instead of silently
|
|
// running with them.
|
|
//
|
|
// Read the AUTHORITATIVE source. Auth uses core.ListKeys() (cfg.Keys), not
|
|
// the legacy cfg.GatewayKeys list: seedKeys copies gateway_keys into keys[]
|
|
// on first start and the YAML list is ignored for auth afterwards. Checking
|
|
// GatewayKeys() here made the warning lie — once the legacy list was empty
|
|
// (e.g. after rotating the starter key away) it reported "every request is
|
|
// rejected" while seven working keys, one of them admin, were in service.
|
|
if keys := c.ListKeys(); len(keys) == 0 {
|
|
log.Printf("[modelrouter] WARNING: no gateway key configured — every request will be rejected. Add one in the WebUI or set gateway_keys")
|
|
} else {
|
|
for _, k := range keys {
|
|
if k.Key == "sk-gw-local-0001" || k.Key == "sk-local-0001" {
|
|
log.Printf("[modelrouter] WARNING: gateway key %q looks like the starter/example key — rotate it before exposing the gateway", k.Key)
|
|
}
|
|
}
|
|
}
|
|
if l := c.Listen(); strings.HasPrefix(l, "0.0.0.0:") || strings.HasPrefix(l, "::") {
|
|
log.Printf("[modelrouter] WARNING: listen=%s binds ALL interfaces — bind an internal address in production", l)
|
|
}
|
|
|
|
srv := &http.Server{
|
|
Addr: c.Listen(),
|
|
Handler: gw.Handler(),
|
|
ReadHeaderTimeout: 10 * time.Second,
|
|
}
|
|
go func() {
|
|
cert, key := c.TLS()
|
|
if cert != "" && key != "" {
|
|
log.Printf("[modelrouter] listening HTTPS on %s (cert=%q) (default_model=%s, models=%v, adapters=%d)",
|
|
c.Listen(), cert, c.DefaultModel(), c.Registry().ModelList(), len(c.ListAdapters()))
|
|
if err := srv.ListenAndServeTLS(cert, key); err != nil && err != http.ErrServerClosed {
|
|
log.Fatalf("[modelrouter] server: %v", err)
|
|
}
|
|
return
|
|
}
|
|
log.Printf("[modelrouter] listening HTTP on %s (default_model=%s, models=%v, adapters=%d)",
|
|
c.Listen(), c.DefaultModel(), c.Registry().ModelList(), len(c.ListAdapters()))
|
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
log.Fatalf("[modelrouter] server: %v", err)
|
|
}
|
|
}()
|
|
|
|
stop := make(chan os.Signal, 1)
|
|
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
|
|
<-stop
|
|
log.Printf("[modelrouter] shutting down")
|
|
}
|