From c21707b11193c3afd28198f9976bce5b7b30a8b7 Mon Sep 17 00:00:00 2001 From: dekun Date: Tue, 19 May 2026 02:08:06 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=B7=BB=E5=8A=A0=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E4=B8=8E=E7=94=9F=E4=BA=A7=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DEPLOY.md | 398 ++++++++++++++++++++++++++++++++++++++---------------- README.md | 213 +++++++++++++++++++++++++++++ 2 files changed, 493 insertions(+), 118 deletions(-) create mode 100644 README.md diff --git a/DEPLOY.md b/DEPLOY.md index 751eed7..96857f4 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -1,181 +1,343 @@ -# LLM 中转网关 · PM2 部署说明 +# openai_node · 生产部署文档 -本文说明如何在服务器上使用 **PM2** 守护 **FastAPI(uvicorn)** 进程,实现崩溃自动拉起、开机自启与日志管理。 +本文说明如何在 **VPS** 上部署本网关,并通过 **宝塔面板** 反向代理、**frp** 汇聚多台本地 Ollama 节点。适用于当前推荐拓扑: + +- 网关监听:**8150** +- 宝塔反代:域名 → `127.0.0.1:8150` +- 各 PC 模型端口:**3313–3318**(frp 映射到 VPS 本机 `127.0.0.1`) + +代码仓库:[https://git.bz121.com/dekun/openai_node.git](https://git.bz121.com/dekun/openai_node.git) --- -## 1. 前置条件 +## 1. 部署拓扑 -| 组件 | 说明 | -|------|------| -| **Python** | 建议 3.10+,已加入 `PATH` | -| **Node.js** | 用于安装 PM2(LTS 即可) | -| **PM2** | `npm i -g pm2` | -| **本项目** | 含 `main.py`、`requirements.txt`,可选 `ecosystem.config.cjs` | +``` + Internet + │ + ▼ + ┌─────────────────┐ + │ VPS │ + │ 宝塔 Nginx:443 │ + │ │ │ + │ ▼ │ + │ openai_node │ + │ :8150 │ + │ │ │ + │ 127.0.0.1:3313 ├─── frp ─── PC1 Ollama :3313 + │ 127.0.0.1:3314 ├─── frp ─── PC2 Ollama :3314 + │ 127.0.0.1:3315 ├─── frp ─── PC3 Ollama :3315 + └─────────────────┘ +``` -上游本地大模型(默认 `http://127.0.0.1:10434`)需在同一台机器或内网可达;若在大模型同一主机部署网关,确保端口未被占用(下文示例为 **8000**)。 +要点: + +1. **不要**把 3313 等端口直接暴露到公网,仅 VPS 本机访问。 +2. **不要**让宝塔直接把域名反代到各 PC;应只反代到 **8150**,由网关按 `model` 调度。 +3. `nodes.json` 里 `host` 填 **`127.0.0.1`**,`port` 填 frp 映射后的端口。 --- -## 2. 准备运行环境 +## 2. 前置条件 -在 **项目根目录**(与 `main.py` 同级)执行: +| 组件 | 版本建议 | 用途 | +|------|----------|------| +| Python | 3.10+ | 运行网关 | +| Node.js | LTS | 安装 PM2 | +| PM2 | 最新 | 进程守护 | +| 宝塔 | 7.x+ | Nginx 反代、SSL | +| frp | 客户端 + 服务端 | 内网穿透(可用其它方案,原理相同) | -### Linux / macOS +各 PC 已安装 **Ollama** 并监听本地端口(如 3313),且与 VPS 网络可达(通过 frp)。 + +--- + +## 3. 在 VPS 上部署网关 + +### 3.1 克隆代码 + +```bash +cd /www/wwwroot # 按宝塔网站目录调整 +git clone https://git.bz121.com/dekun/openai_node.git +cd openai_node +``` + +### 3.2 虚拟环境与依赖 ```bash -cd /path/to/中转网关 python3 -m venv venv source venv/bin/activate pip install -r requirements.txt -U ``` -### Windows(PowerShell) - -```powershell -cd C:\path\to\中转网关 -python -m venv venv -.\venv\Scripts\Activate.ps1 -pip install -r requirements.txt -U -``` - -在项目目录创建 **`gateway.json`**(可复制 `gateway.json.example`),填写 **`username`**、**`password`**;**`api_key`** 可留空,首次启动会自动写入 `sk-...`。也可用环境变量 **`GATEWAY_CONFIG`** 指定配置文件绝对路径。 - -确认可手动启动: +### 3.3 配置文件 ```bash -python -m uvicorn main:app --host 0.0.0.0 --port 8000 +cp gateway.json.example gateway.json +cp nodes.json.example nodes.json +chmod 600 gateway.json nodes.json ``` -浏览器访问 `http://服务器IP:8000` 正常后再交给 PM2。 +编辑 **`gateway.json`**:设置 `username`、`password`;`api_key` 可留空自动生成。 + +编辑 **`nodes.json`**(示例三台): + +```json +{ + "nodes": [ + { + "id": "node-1", + "name": "书房电脑", + "host": "127.0.0.1", + "port": 3313, + "enabled": true, + "max_concurrent": 1, + "models": [ + { "id": "qwen2.5:14b", "label": "千问 14B" } + ] + } + ] +} +``` + +`models[].id` 必须与 Ollama 模型名、API 请求中的 `model` **完全一致**。 + +也可部署后浏览器打开 **系统设置** 页面维护(需先登录)。 + +### 3.4 环境变量 + +| 变量 | 生产建议 | +|------|----------| +| `JWT_SECRET` | **必改**,长随机字符串 | +| `GATEWAY_PORT` | `8150`(与 PM2、宝塔一致) | + +可选: + +| 变量 | 说明 | +|------|------| +| `STATS_DB` | 统计库路径,默认 `gateway_stats.db` | +| `NODES_CONFIG` | 节点配置路径,默认 `nodes.json` | +| `UPSTREAM_URL` | 仅在没有配置 `nodes.json` 节点时作为单机上流 | + +### 3.5 手动试跑 + +```bash +export JWT_SECRET="你的随机密钥" +python -m uvicorn main:app --host 0.0.0.0 --port 8150 +``` + +确认: + +```bash +curl -s http://127.0.0.1:8150/api/models/cards | head +curl -s http://127.0.0.1:3313/v1/models # frp 通了才有返回 +``` + +浏览器访问 `http://VPS_IP:8150`,首页应出现模型卡片。 --- -## 3. 环境变量(生产必改) +## 4. PM2 守护 -| 变量 | 含义 | 建议 | -|------|------|------| -| `JWT_SECRET` | 签发网页登录 JWT 的密钥 | 长随机字符串,勿泄露 | -| `UPSTREAM_URL` | 上游 OpenAI 兼容服务根地址(无末尾 `/`) | 默认 `http://127.0.0.1:10434`,按实际修改 | +项目已包含 **`ecosystem.config.cjs`**(端口 **8150**)。 -可在 shell 中导出,或在 PM2 的 `env` / `ecosystem.config.cjs` 中配置(见下文)。 +### 4.1 修改 PM2 配置 ---- +编辑 `ecosystem.config.cjs` 中 `env.JWT_SECRET` 为生产密钥。 -## 4. 使用 PM2 启动(推荐 ecosystem) - -项目根目录已提供 **`ecosystem.config.cjs`**:自动根据 **Windows / 非 Windows** 选择虚拟环境中的 Python 解释器,并启动: - -`python -m uvicorn main:app --host 0.0.0.0 --port 8000` - -### 4.1 修改配置 - -1. 用编辑器打开 `ecosystem.config.cjs`。 -2. 在 `env` 里填写 **`JWT_SECRET`**(必填),按需修改 **`UPSTREAM_URL`**。 -3. 若端口冲突,可在 `args` 中把 `8000` 改成其它端口。 - -### 4.2 启动与常用命令 - -在项目根目录执行: +### 4.2 启动 ```bash +cd /www/wwwroot/openai_node +source venv/bin/activate pm2 start ecosystem.config.cjs pm2 save +pm2 status ``` -常用运维: +常用命令: | 命令 | 作用 | |------|------| -| `pm2 status` | 查看进程状态 | -| `pm2 logs llm-gateway` | 实时日志 | -| `pm2 restart llm-gateway` | 重启 | +| `pm2 logs llm-gateway` | 查看日志 | +| `pm2 restart llm-gateway` | 重启(改代码/配置后) | | `pm2 stop llm-gateway` | 停止 | -| `pm2 delete llm-gateway` | 从 PM2 列表移除 | -修改代码或依赖后:`pip install …` 完成再执行 `pm2 restart llm-gateway`。 - ---- - -## 5. 不用配置文件时的等价命令 - -若暂时不用 `ecosystem.config.cjs`,需自行写出虚拟环境里 **Python 可执行文件绝对路径**。 - -**Linux 示例:** +更新代码后: ```bash -pm2 start /path/to/中转网关/venv/bin/python \ - --name llm-gateway \ - --cwd /path/to/中转网关 \ - --interpreter none \ - -- -m uvicorn main:app --host 0.0.0.0 --port 8000 +git pull +source venv/bin/activate +pip install -r requirements.txt -U +pm2 restart llm-gateway ``` -**Windows 示例(PowerShell,路径按实际修改):** - -```powershell -pm2 start "C:\path\to\中转网关\venv\Scripts\python.exe" ` - --name llm-gateway ` - --cwd "C:\path\to\中转网关" ` - --interpreter none ` - -- -m uvicorn main:app --host 0.0.0.0 --port 8000 -``` - -然后通过 PM2 设置环境变量(任选其一): - -```bash -pm2 restart llm-gateway --update-env -``` - -或在启动前于终端导出 `JWT_SECRET`、`UPSTREAM_URL` 后再 `pm2 start …`(注意:`--update-env` 与持久化策略以当前 PM2 版本文档为准)。 - ---- - -## 6. 开机自启 - -PM2 保存当前进程列表后,生成并启用开机脚本: +### 4.3 开机自启 ```bash pm2 save pm2 startup +# 按提示执行输出的 sudo 命令 ``` -终端会打印一行以 `sudo` 开头的命令,**复制执行**即可完成 systemd(Linux)或对应平台的自启配置。Windows 可使用 **pm2-windows-service** 或任务计划程序配合 `pm2 resurrect`,按环境选用。 +--- + +## 5. 宝塔反向代理 + +### 5.1 添加站点 + +宝塔 → **网站** → 添加站点 → 绑定域名 → 申请 **SSL**(Let’s Encrypt)。 + +### 5.2 反向代理到 8150 + +网站 → **设置** → **反向代理** → 添加: + +| 项 | 值 | +|----|-----| +| 目标 URL | `http://127.0.0.1:8150` | +| 发送域名 | `$host` | + +在 **配置文件** 或「高级」中建议补充(流式 LLM 必备): + +```nginx +location / { + proxy_pass http://127.0.0.1:8150; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + proxy_read_timeout 600s; + proxy_send_timeout 600s; +} +``` + +说明: + +- `X-Forwarded-For` / `X-Real-IP` 用于网关 **流量统计** 记录真实客户端 IP。 +- `proxy_buffering off` 与长超时避免流式输出卡顿或中断。 + +### 5.3 验证 + +```bash +curl -I https://你的域名/ +``` + +应返回 200。登录 Web → 首页查看模型卡片状态。 --- -## 7. 日志与配置文件 +## 6. frp 多机穿透(示例) -| 项 | 说明 | +以下为常见做法,按你实际 frp 版本调整。 + +### 6.1 VPS 运行 frps + +`/etc/frp/frps.toml` 示例: + +```toml +bindPort = 7000 +``` + +### 6.2 各 PC 运行 frpc + +`/etc/frp/frpc.toml` 示例(书房电脑,本地 Ollama 3313): + +```toml +serverAddr = "VPS公网IP" +serverPort = 7000 + +[[proxies]] +name = "ollama-pc1" +type = "tcp" +localIP = "127.0.0.1" +localPort = 3313 +remotePort = 3313 +``` + +其它电脑分别映射 **3314、3315** … 到 VPS 的 **3314、3315**。 + +### 6.3 在 VPS 上验证 + +```bash +curl http://127.0.0.1:3313/v1/models +curl http://127.0.0.1:3314/v1/models +``` + +均正常后,在 Web **系统设置** 中为对应节点点击 **测试**,或等待首页健康检查(约 30 秒)变为 **空闲**。 + +--- + +## 7. Ollama 侧注意 + +1. Ollama 需监听 `0.0.0.0` 或 frp 能访问的地址(仅内网/frp,勿公网裸露)。 +2. API 请求中的 `model` 使用 `ollama list` 中的名称,与 `nodes.json` 一致。 +3. 流式统计 Token 依赖响应中的 `usage`;可在请求中加 OpenAI 兼容参数(若上游支持): + + ```json + "stream_options": { "include_usage": true } + ``` + +--- + +## 8. 客户端接入 + +| 项 | 值 | +|----|-----| +| Base URL | `https://你的域名/v1` | +| API Key | `gateway.json` 的 `api_key` 或用户中心复制 | + +示例: + +```bash +curl https://你的域名/v1/chat/completions \ + -H "Authorization: Bearer sk-你的密钥" \ + -H "Content-Type: application/json" \ + -d '{"model":"qwen2.5:14b","messages":[{"role":"user","content":"hi"}]}' +``` + +--- + +## 9. 运维与数据文件 + +| 路径 | 说明 | 备份 | +|------|------|------| +| `gateway.json` | 账号、API Key | 建议 | +| `nodes.json` | 节点与模型 | 建议 | +| `gateway_stats.db` | 访问与 Token 统计 | 可选 | +| `~/.pm2/logs/` | PM2 日志 | 按需 | + +修改 `nodes.json` 或 Web 设置后,**PM2 会自动读入**(`save_nodes_config` 热加载);若仅改环境变量需 `pm2 restart`。 + +--- + +## 10. 故障排查 + +| 现象 | 排查 | |------|------| -| PM2 日志 | `pm2 logs llm-gateway`,默认在 `~/.pm2/logs/` | -| 账号与 API Key | **`gateway.json`**(或 `GATEWAY_CONFIG` 指向的文件),含明文密码,请限制权限(如 `chmod 600`),勿提交到版本库 | +| 首页卡片 **离线** | VPS 上 `curl 127.0.0.1:端口/v1/models`;检查 frp、Ollama 是否启动 | +| **404 未找到模型** | 请求 `model` 与 `nodes.json` 中 `models[].id` 不一致 | +| **502 上游连接失败** | 端口错、frp 断、防火墙;`pm2 logs llm-gateway` | +| 统计 IP 全是 VPS | 宝塔未传 `X-Forwarded-For`,见 §5.2 | +| Token 始终为 0 | 上游未返回 `usage`;流式需 `include_usage` | +| 改 `JWT_SECRET` 后无法进设置 | 重新登录 Web | --- -## 8. 反向代理(可选) +## 11. 部署检查清单 -对外只暴露 443/80 时,可用 Nginx/Caddy 把域名反向代理到 `http://127.0.0.1:8000`,并配置 TLS。此时上游 **`UPSTREAM_URL`** 仍指向本机大模型地址,与网关对外端口无关。 +- [ ] `gateway.json`、`nodes.json` 已配置且权限 `600` +- [ ] `JWT_SECRET` 已修改 +- [ ] PM2 进程 `llm-gateway` 在线,端口 **8150** +- [ ] 宝塔 HTTPS 反代到 `127.0.0.1:8150`,流式相关 Nginx 参数已加 +- [ ] frp 映射 3313+ 在 VPS 本机 `curl` 通过 +- [ ] 首页模型卡片状态为 **空闲** 或 **忙碌**(非长期离线) +- [ ] API 试调用成功,`/stats` 有记录 --- -## 9. 故障排查 +## 12. 相关文档 -1. **`pm2 logs` 中报 ModuleNotFoundError** - 确认 PM2 使用的解释器是 **`venv` 内的 python**,且已在同一 venv 中执行 `pip install -r requirements.txt`。 - -2. **502 / 上游连接失败** - 检查大模型是否监听 `UPSTREAM_URL`,防火墙与内网穿透是否正常。 - -3. **修改 `JWT_SECRET` 后旧登录失效** - 属预期行为,用户需重新登录网页端。 - ---- - -## 10. 小结 - -1. 创建 venv 并安装 `requirements.txt`。 -2. 配置 **`JWT_SECRET`**(及可选 **`UPSTREAM_URL`**)。 -3. `pm2 start ecosystem.config.cjs` → `pm2 save` → `pm2 startup`(按需)。 - -进程名 **`llm-gateway`** 与 `ecosystem.config.cjs` 中 `name` 字段一致,便于 `pm2 restart llm-gateway` 等操作。 +- 功能与配置总览:[README.md](./README.md) +- 仓库地址:[https://git.bz121.com/dekun/openai_node.git](https://git.bz121.com/dekun/openai_node.git) diff --git a/README.md b/README.md new file mode 100644 index 0000000..3de6a94 --- /dev/null +++ b/README.md @@ -0,0 +1,213 @@ +# openai_node + +本地 **Ollama / OpenAI 兼容** 大模型中转网关:统一鉴权、多节点按模型调度、Web 管理面板与流量统计。 + +代码仓库:[https://git.bz121.com/dekun/openai_node.git](https://git.bz121.com/dekun/openai_node.git) + +--- + +## 功能概览 + +| 模块 | 说明 | +|------|------| +| **OpenAI 兼容 API** | `POST /v1/chat/completions`,Bearer `sk-...` 鉴权,支持流式响应 | +| **多节点调度** | 按请求体中的 `model` 转发到不同机器/端口(`nodes.json`) | +| **Web 控制台** | 首页模型分布卡片、流量统计、系统设置、用户中心 | +| **访问统计** | 记录客户端 IP、Token 用量、按节点汇总(SQLite) | +| **健康检查** | 定时探测各节点 `/v1/models`,首页显示中文状态 | + +--- + +## 架构示意 + +``` +客户端 / Cursor / 脚本 + │ + ▼ + 宝塔 Nginx(443/80)──反代──► VPS :8150(本网关) + │ │ + │ ├─ 鉴权 / 统计 / Web + │ │ + │ ┌────────────┼────────────┐ + │ ▼ ▼ ▼ + │ 127.0.0.1:3313 :3314 :3315 …(frp 映射) + │ │ │ │ + │ PC-A Ollama PC-B Ollama PC-C Ollama +``` + +- **对外只暴露一个域名**,反代到网关 **8150**。 +- 各家用/办公室电脑通过 **frp** 等内网穿透,把模型端口映射到 VPS 本机 `127.0.0.1:3313` 等。 +- 网关在 **系统设置** 中维护「端口 ↔ 模型」关系,无需为每台机器单独配公网域名。 + +--- + +## 环境要求 + +- Python **3.10+** +- 上游服务:**Ollama**(或其它 OpenAI 兼容 HTTP API) +- 生产建议:**PM2** 守护进程;**宝塔** 或 Nginx 做 HTTPS 反代 + +--- + +## 快速开始 + +### 1. 克隆仓库 + +```bash +git clone https://git.bz121.com/dekun/openai_node.git +cd openai_node +``` + +### 2. 安装依赖 + +```bash +python3 -m venv venv +source venv/bin/activate # Windows: .\venv\Scripts\Activate.ps1 +pip install -r requirements.txt -U +``` + +### 3. 配置文件 + +```bash +cp gateway.json.example gateway.json +cp nodes.json.example nodes.json +``` + +编辑 **`gateway.json`**: + +```json +{ + "username": "admin", + "password": "你的强密码", + "api_key": "" +} +``` + +`api_key` 留空时,首次启动会自动生成 `sk-...` 并写回文件。 + +编辑 **`nodes.json`**(示例为 3 台节点,主机默认 `127.0.0.1`,端口 `3313–3315`): + +- `models[].id` 必须与调用 API 时 JSON 里的 **`model` 字段完全一致**(Ollama 模型名)。 +- `models[].label` 为 Web 卡片上的显示名。 + +### 4. 启动 + +```bash +export JWT_SECRET="请改为长随机字符串" +python -m uvicorn main:app --host 0.0.0.0 --port 8150 +``` + +浏览器打开 `http://127.0.0.1:8150`: + +| 路径 | 说明 | +|------|------| +| `/` | 首页:模型分布卡片 + 使用说明 | +| `/login` | 管理员登录 | +| `/settings` | 系统设置(节点/端口/模型,需登录) | +| `/stats` | 流量统计、IP 列表、Token 账单 | +| `/user` | 查看 / 复制 API Key | + +--- + +## 调用示例 + +将 `https://你的域名` 换为实际地址(或 `http://IP:8150` 调试): + +```bash +curl https://你的域名/v1/chat/completions \ + -H "Authorization: Bearer sk-xxxx" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "your-model-id-1", + "messages": [{"role": "user", "content": "你好"}], + "stream": true + }' +``` + +在 Cursor、OpenAI SDK 等客户端中: + +- **Base URL**:`https://你的域名/v1` +- **API Key**:`gateway.json` 中的 `api_key` 或用户中心页面复制 + +--- + +## 配置说明 + +### gateway.json + +| 字段 | 说明 | +|------|------| +| `username` | Web 登录用户名 | +| `password` | Web 登录密码(明文存储,请限制文件权限) | +| `api_key` | OpenAI 兼容 API Key,可自动生成 | + +### nodes.json + +| 字段 | 说明 | +|------|------| +| `id` | 节点唯一 ID(系统自动生成亦可) | +| `name` | 显示名称 | +| `host` | 上游主机,frp 场景填 **`127.0.0.1`** | +| `port` | 上游端口,如 **3313–3318** | +| `enabled` | 是否启用 | +| `max_concurrent` | 单节点最大并发请求数(建议 1) | +| `models` | `{ "id": "模型ID", "label": "显示名" }` 列表 | + +也可在 Web **系统设置** 中增删改,保存后写入 `nodes.json`。 + +### 环境变量 + +| 变量 | 默认值 | 说明 | +|------|--------|------| +| `JWT_SECRET` | (弱默认值) | **生产必改**,Web 登录 JWT 签名 | +| `GATEWAY_PORT` | `8150` | 监听端口 | +| `GATEWAY_CONFIG` | `gateway.json` | 账号配置路径 | +| `NODES_CONFIG` | `nodes.json` | 节点配置路径 | +| `STATS_DB` | `gateway_stats.db` | 统计数据库路径 | +| `UPSTREAM_URL` | `http://127.0.0.1:10434` | **未配置 nodes 时** 的单机上流回退地址 | +| `APP_ROOT` | 空 | 反代子路径前缀,如 `/wg` | + +--- + +## 节点状态(中文) + +| 状态 | 含义 | +|------|------| +| 空闲 | 在线且可接受新请求 | +| 忙碌 | 已达 `max_concurrent` | +| 离线 | 健康检查未通过 | +| 未启用 | 节点在设置中关闭 | +| 异常 | 连接失败并带有错误信息 | + +--- + +## 项目文件 + +| 文件 | 说明 | +|------|------| +| `main.py` | 网关主程序(API + Web 单文件) | +| `gateway.json.example` | 账号配置示例 | +| `nodes.json.example` | 多节点配置示例 | +| `requirements.txt` | Python 依赖 | +| `ecosystem.config.cjs` | PM2 配置 | +| `DEPLOY.md` | **生产部署文档**(宝塔 + frp + PM2) | + +--- + +## 安全提示 + +- 勿将 `gateway.json`、`nodes.json`、`gateway_stats.db` 提交到公开仓库。 +- 生产环境务必修改 **`JWT_SECRET`**,并为 `gateway.json` 设置严格文件权限(如 `chmod 600`)。 +- 统计与设置接口需 Web 登录;API 调用使用 `sk-` Key,请勿混用。 + +--- + +## 生产部署 + +详见 **[DEPLOY.md](./DEPLOY.md)**(VPS、宝塔反代 8150、frp 多机、PM2)。 + +--- + +## 许可证 + +私有仓库,按团队内部约定使用。