# 部署与迁移 > 本文档面向运维:单机 systemd 部署、发行包安装、目录与权限、从 `llmsproxy` > 改名后的迁移与回滚。 > 返回 [README](../README.md)。 ## 1. 单机部署(推荐) 仓库根目录的 `deploy.sh` 负责:构建 → 预检配置 → 备份 → 原子替换二进制 → 重启服务 → 后置校验。它**不**修改源码,也**不**假设全新机器。 ```bash sudo ./deploy.sh # 默认读取仓库内 config.yaml 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/bin/modelrouter` | | 配置 | `/etc/modelrouter/config.yaml` | | 适配器 | `/etc/modelrouter/adapters/` | | 插件(可选) | `/etc/modelrouter/plugins/`(需在配置里设 `plugin_dir`) | | 主密钥 | `/etc/modelrouter/master.key`(0600) | | systemd unit | `/etc/systemd/system/modelrouter.service` | `deploy.sh` 会在以下情况**中止**而不是"看起来成功": - 部署后二进制起不来、或新配置 `-check` 不通过; - 凭据无法解封(`master.key` 丢失 / 与配置不匹配); - 检测到改名前的 `/etc/llmsproxy`,但新配置尚未建立(否则会部署出一个 没有 key、没有源的空实例,所有请求都被拒)。 ## 2. 发行包安装 三个产物由 `packaging/core-dist.sh` 生成到 `cmd/build/dist/`: ```text modelrouter__amd64.deb modelrouter-.x86_64.rpm modelrouter--linux-amd64.tar.gz ``` deb / rpm 安装后: ```bash sudo systemctl enable --now modelrouter sudo journalctl -u modelrouter -f ``` tar 包内只有:`modelrouter`、`config.example.yaml`、`modelrouter.service`、 `migrate-to-modelrouter.sh`、`adapters/`。 **发行包不含业务插件**(`channels` / `billing` / `quota` / `usage` 等)。 它们保留在仓库中作为可选扩展,需要时手工放入 `plugin_dir`,网关不会自动安装、 覆盖或删除。这是有意的:插件属于运维资产,生命周期不与内核绑定。 首次启动会在 `/etc/modelrouter/config.yaml` 生成一份**随机 admin key** 的配置, key 打印在启动日志里。发行包不预置任何可用 key(历史上曾因此泄露过一个真实的 `sk-gw-local-0001`)。 ## 3. 目录与权限 - `/etc/modelrouter` 是唯一的读写根(systemd unit 用 `ProtectSystem=strict` + `ReadWritePaths=/etc/modelrouter` 固化这一点)。 - `master.key` 必须 0600。当前 unit 以 root 运行:降权到普通用户需要先把 `master.key` 交给该用户并统一目录属主,否则服务会"带着明文落盘继续跑", 失败只留一行日志——这是独立变更,不要和加固混在一起。 - `plugin_dir` 由运维维护;插件自带的状态文件(如 `.channels-store.json`)也在其中。 ## 4. 从 llmsproxy 迁移 二进制、配置目录、服务名、环境变量已统一为 `modelrouter`: | 旧 | 新 | |---|---| | `/usr/local/bin/llmsproxy` | `/usr/local/bin/modelrouter` | | `/etc/llmsproxy/` | `/etc/modelrouter/` | | `llmsproxy.service` | `modelrouter.service` | | `LLMSPROXY_PLUGIN_DIR` | `MODELROUTER_PLUGIN_DIR` | | `LLMSPROXY_XDISPLAY_BASE` | `MODELROUTER_XDISPLAY_BASE` | | `LLMS_PROXY_MASTER_KEY` | `MODELROUTER_MASTER_KEY` | 迁移脚本**默认 dry-run**,复制而非移动,旧目录永不删除: ```bash sudo ./packaging/migrate-to-modelrouter.sh # 预演(逐条打印将做什么) sudo ./packaging/migrate-to-modelrouter.sh --apply # 执行 ``` 它做五件事:复制状态目录并改写配置里的旧绝对路径、复核 `master.key` 0600、 安装新二进制(旧文件保留)、由旧 unit 生成新 unit(改写 ExecStart / WorkingDirectory / ReadWritePaths)、复制 `/usr/share` 资源;最后跑 `modelrouter -check` 并 `systemctl daemon-reload`。 确认新服务可用后,再手工清理旧布局(脚本不会删): ```bash sudo systemctl disable --now llmsproxy.service sudo rm -f /etc/systemd/system/llmsproxy.service sudo rm -rf /usr/local/bin/llmsproxy /etc/llmsproxy /usr/share/llmsproxy ``` 回滚(不需要旧文件被删): ```bash sudo ./packaging/migrate-to-modelrouter.sh --rollback --apply ``` **读取侧兼容**:旧配置路径与旧环境变量仍被接受,所以"先换二进制、后改 unit" 这种顺序不会立刻失效。但写入永远落在新路径,迁移完成后不要两台布局混用。 ## 5. 部署后验证清单 ```bash # 1) 进程与监听 systemctl status modelrouter --no-pager ss -ltnp | grep modelrouter # 2) 鉴权生效(无 key 必须 401) curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/v1/models # 3) 带 key 能列出模型 curl -s -H "Authorization: Bearer $KEY" http://127.0.0.1:8080/v1/models | head -c 400 # 4) 真实请求走通(证明 LuaJIT 适配器路径未被打断) curl -s -H "Authorization: Bearer $KEY" \ -d '{"model":"AUTO","messages":[{"role":"user","content":"hi"}]}' \ http://127.0.0.1:8080/v1/chat/completions | head -c 400 # 5) 仓库文件与生产文件一致(部署脚本的收尾检查) sha256sum /usr/local/bin/modelrouter build/modelrouter ``` 第 4 步失败但第 3 步成功,通常是适配器或上游凭据问题,不是部署问题: 查看 `journalctl -u modelrouter -n 100`。