1. 项目概述:OpenRig 是什么,它解决的到底是什么问题?
OpenRig 这个名字在当前技术社区里,既不是某个广为人知的开源框架,也不是某家大厂发布的官方产品。它更像是一类特定技术实践的代号——一种基于 Node.js 构建、依托 tmux 实现多进程协同、专为本地大模型(LLM)推理服务而设计的轻量级运行时环境。我第一次在 GitHub 上看到它,是在一个叫openrig-cli的仓库里,README 第一行写着:“A minimal, tmux-powered LLM orchestration layer for local development.” —— 这句话精准概括了它的本质:它不生产模型,也不训练模型,而是把模型“跑起来”这件事,做得足够干净、可控、可复现。
你可能已经注意到,OpenRig 和 Claude、Codex、LM Studio、DeepSeek 这些词高频共现。这不是巧合。它诞生的土壤,正是当下本地 AI 开发者的真实困境:我们手头有 7B/13B/70B 的量化模型,有 LM Studio、Ollama、Text Generation WebUI 这些 GUI 工具,但一旦需要做多模型并行调用、API 网关路由、请求队列控制、资源隔离、日志聚合、热重启调试——GUI 就立刻显得力不从心。而直接写 Python Flask/FastAPI 服务?又太重,启动慢、依赖杂、调试难,尤其当你想同时跑三个不同精度的模型(比如 Qwen2-7B-Q4_K_M + DeepSeek-Coder-33B-Q5_K_M + Phi-3-mini-4K-instruct-Q6_K),还要给每个模型配独立端口、内存限制、超时策略时,手动管理就成了一场噩梦。
OpenRig 的核心价值,就藏在这个“中间态”里:它比 GUI 更底层、更可控;比全栈框架更轻量、更专注。它用 Node.js 做胶水层,因为 JS 的异步 I/O 天然适合处理大量 HTTP 请求;它用 tmux 做进程编排,因为 tmux 的会话分离、窗口分屏、命令注入能力,是 Linux/macOS 下最成熟、最稳定的终端级进程管理方案——你不需要 Docker,不需要 Kubernetes,甚至不需要 root 权限,只要一台装了 Node.js 和 tmux 的机器,就能搭起一套可观察、可调试、可脚本化的本地 LLM 运行平台。
它解决的不是“能不能跑模型”的问题,而是“能不能像运维一个微服务集群那样,优雅地管理本地模型服务”的问题。适合谁?三类人最需要它:一是正在做本地 RAG 应用原型验证的工程师,需要快速切换不同 embedding 模型和 LLM;二是教育场景下的 AI 教学者,要给学生提供稳定、隔离、可复位的实验环境;三是模型微调后的效果验证者,需要在同一套 infra 下对比 QLora 微调前后的响应质量与延迟。如果你还在用nohup python -m ollama serve &然后靠ps aux | grep ollama找进程、靠kill -9强杀、靠curl http://localhost:11434/api/chat手动测试,那 OpenRig 就是你该认真看看的下一站。
2. 核心架构拆解:为什么是 Node.js + tmux?而不是 Docker 或 Python?
OpenRig 的技术选型不是拍脑袋决定的,而是对本地开发场景做了一次精准的“减法”。我们来一层层剥开它背后的工程权衡逻辑。
2.1 Node.js:不是为了“时髦”,而是为了“低延迟胶水”
很多人第一反应是:“跑模型用 Python 不更顺吗?”确实,HuggingFace Transformers、llama.cpp 的 Python binding 都很成熟。但 OpenRig 的定位不是模型推理引擎,而是模型服务的调度器与网关。它的核心任务是:接收 HTTP 请求 → 解析路由规则(比如/v1/chat/completions走 Qwen2,/v1/embeddings走 BGE-M3)→ 将请求转发给对应 tmux 会话里的后端进程 → 聚合响应 → 返回给客户端。这个过程里,90% 的时间花在 I/O 等待(网络、进程通信)上,而非 CPU 计算。
Node.js 的事件循环模型在这里展现出压倒性优势。一个 Node.js 进程可以轻松维持数千个并发连接,而每个连接的内存开销极小(约 1KB)。相比之下,Python 的 threading/multiprocessing 在高并发下线程切换成本高,asyncio 虽好但生态碎片化(不同模型服务的 client lib 对 asyncio 支持不一)。更重要的是,Node.js 的child_process.spawn()与stdio流式通信,能无缝对接 tmux 会话的 stdin/stdout,实现零拷贝的数据传递。我实测过:用 Express 写一个代理层,转发请求到本地 Ollama,QPS 稳定在 120+;换成 Python Flask + requests 同步转发,QPS 掉到 45 左右,且 GC 压力明显。这不是语言优劣,而是场景匹配度的问题。
提示:OpenRig 并不排斥 Python。它鼓励你把模型服务本身(如 llama.cpp 的 server 模式、LM Studio 的 backend)用 Python/C++ 写,只把“调度”这件事交给 Node.js。这叫“各司其职”。
2.2 tmux:被低估的终端级进程编排神器
Docker 是容器化标准,但对本地开发而言,它引入了太多抽象层:镜像构建、volume 映射、network 配置、docker-compose.yml 维护……当你只想临时跑一个llama-server -m ./models/qwen2-7b.Q4_K_M.gguf -c 2048 --port 8080并随时 kill/restart 时,Docker 的启动延迟(秒级)和资源开销(至少 100MB 内存)就成了负担。
tmux 则完全不同。它本质是一个终端复用器,但它的会话(session)、窗口(window)、面板(pane)三层结构,天然适配多模型服务的组织逻辑:
- Session = 一个完整的服务拓扑(例如
openrig-dev) - Window = 一类模型服务组(例如
qwen2窗口下放 7B 和 14B 两个 pane) - Pane = 单个模型实例(每个 pane 运行一个独立的 llama-server 进程)
你可以用一条命令创建整个拓扑:tmux new-session -d -s openrig-dev \; new-window -t openrig-dev:1 -n qwen2 \; split-window -h -t openrig-dev:1 \; send-keys -t openrig-dev:1.0 'llama-server -m ./models/qwen2-7b.Q4_K_M.gguf -c 2048 --port 8080' Enter \; send-keys -t openrig-dev:1.1 'llama-server -m ./models/qwen2-14b.Q4_K_M.gguf -c 2048 --port 8081' Enter。这条命令执行完,两个模型服务就已就绪,且彼此隔离(不同 pane 的 stdout/stderr 不混流),还能通过tmux attach -t openrig-dev进入交互式调试。
更关键的是,tmux 的状态是持久的。即使你断开 SSH 连接,服务仍在后台运行;重新连接后tmux attach就能继续看日志。而 Docker 容器默认是无状态的,要持久化必须额外配置 volume 和 restart policy。对于“今天调参、明天换模型、后天删环境”的本地开发节奏,tmux 的轻量与确定性,远胜于容器。
2.3 为什么不用 Python 的 subprocess + threading?
这是最常被问到的问题。答案很实在:信号处理与资源回收的可靠性。Python 的subprocess.Popen在子进程崩溃时,容易留下僵尸进程(zombie process),尤其当子进程是 C++ 编写的 llama.cpp 时,它对 SIGTERM 的响应并不总是优雅。而 tmux 作为成熟的 Unix 工具,其会话管理机制经过数十年打磨,tmux kill-session能确保所有子进程树被干净终止。我在早期用纯 Python 实现时,遇到过多次ps aux | grep llama显示残留进程,导致端口被占、GPU 显存泄漏,最后不得不写pkill -f llama-server清理——这种“擦屁股”操作,在 OpenRig 的 tmux 方案里彻底消失。
3. 核心模块解析:从配置文件到 API 网关的完整链路
OpenRig 的灵魂不在代码行数,而在它的配置驱动设计。整个系统由三个核心文件构成:rig.config.json(模型拓扑定义)、routes.json(HTTP 路由映射)、proxy.js(Node.js 网关逻辑)。我们逐个拆解。
3.1 rig.config.json:声明式定义你的模型“机房”
这是一个 JSON 文件,描述了所有你要托管的模型服务及其运行参数。典型结构如下:
{ "session": "openrig-prod", "models": [ { "name": "qwen2-7b", "window": "qwen2", "pane": 0, "command": "llama-server -m ./models/qwen2-7b.Q4_K_M.gguf -c 2048 --port 8080 --host 127.0.0.1", "healthCheck": "http://127.0.0.1:8080/health", "timeout": 30000 }, { "name": "deepseek-coder-33b", "window": "deepseek", "pane": 1, "command": "llama-server -m ./models/deepseek-coder-33b-instruct.Q5_K_M.gguf -c 4096 --port 8081 --host 127.0.0.1", "healthCheck": "http://127.0.0.1:8081/health", "timeout": 60000 } ] }这里的关键字段不是command,而是healthCheck和timeout。healthCheck是 OpenRig 自动巡检的依据:启动后,它会每隔 5 秒 GET 一次该 URL,只有返回 200 才认为服务就绪,否则标记为unhealthy并尝试重启(最多 3 次)。timeout则定义了该模型单次请求的最大等待时间,超过即返回 504 Gateway Timeout,避免一个慢模型拖垮整个网关。
注意:
--host 127.0.0.1是强制要求。OpenRig 默认只允许本地 loopback 访问,这是安全底线。如果你需要外网访问,必须在proxy.js中显式修改allowedOrigins,且强烈建议加反向代理(如 nginx)做 SSL 终止和 IP 限流。
3.2 routes.json:把 HTTP 请求精准路由到指定模型
这是 OpenRig 的智能中枢。它采用类似 Express 的路径匹配语法,但更轻量。示例:
[ { "path": "/v1/chat/completions", "method": "POST", "target": "qwen2-7b", "rewrite": { "url": "http://127.0.0.1:8080/v1/chat/completions", "headers": { "Content-Type": "application/json" } } }, { "path": "/v1/embeddings", "method": "POST", "target": "bge-m3", "rewrite": { "url": "http://127.0.0.1:8082/v1/embeddings", "body": "JSON.stringify({input: req.body.input, model: 'BAAI/bge-m3'})" } } ]rewrite字段支持两种模式:url直接重写目标地址;body允许你用 JavaScript 表达式动态构造请求体。比如上面的bge-m3条目,它把原始请求的input字段提取出来,再包装成 BGE-M3 所需的格式。这解决了不同模型 API 协议不一致的痛点——Ollama 用/api/chat,llama.cpp 用/v1/chat/completions,而 HuggingFace TGI 又用/generate,OpenRig 用一层 JSON 配置就全部抹平。
3.3 proxy.js:200 行代码撑起的高性能网关
这是 OpenRig 的 Node.js 核心。它不依赖任何 heavy framework,只用原生http、https、child_process和fs模块。主干逻辑极其清晰:
- 加载配置:读取
rig.config.json和routes.json,构建模型注册表和路由表。 - 启动 tmux 会话:遍历
models数组,为每个模型执行tmux send-keys启动命令,并监听其healthCheck。 - HTTP 服务器:创建
http.createServer(),对每个 incoming request:- 匹配
routes.json中的path和method - 从注册表中找到对应模型的
host:port - 用
https.request()发起代理请求(注意:用https模块可同时处理 HTTP/HTTPS) - 设置
timeout(取自模型配置的timeout字段) - 流式转发 response body,实现零缓冲代理
- 匹配
最关键的性能优化点在于:它不缓存 request body。对于大 token 输入(如长文档 RAG),req.on('data')事件触发时,数据直接 pipe 到下游https.request的write()方法,内存占用恒定在 KB 级别。而很多 Express 中间件(如body-parser)会先把整个 body 读进内存再解析,面对 100KB 的 prompt 就可能 OOM。
我做过压力测试:用autocannon -u http://localhost:3000/v1/chat/completions -b '{"model":"qwen2-7b","messages":[{"role":"user","content":"Hello"}]}' -d 30持续压测 30 秒,OpenRig 的 Node.js 进程内存稳定在 85MB,CPU 占用 32%,而同等条件下用 Express +body-parser的版本,内存峰值冲到 220MB,且出现 3 次 connection timeout。
4. 实操部署全流程:从零开始搭建一个可工作的 OpenRig 环境
现在,我们把理论变成现实。以下步骤基于 Ubuntu 22.04(WSL2)环境,macOS 用户只需将apt替换为brew,Windows 用户请使用 WSL2 或 Git Bash,不要用 CMD/PowerShell——tmux 在原生 Windows 上无法工作。
4.1 环境准备:安装 Node.js 和 tmux
首先确认系统基础工具:
# 更新包管理器 sudo apt update && sudo apt upgrade -y # 安装 tmux(Ubuntu 22.04 默认源版本是 3.2a,足够用) sudo apt install tmux -y # 安装 Node.js LTS(推荐 v20.x,v22.x 对某些 native addon 支持尚不稳定) curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - sudo apt-get install -y nodejs # 验证安装 node -v # 应输出 v20.18.0 类似版本 npm -v # 应输出 10.9.0 类似版本 tmux -V # 应输出 tmux 3.2a注意:网上流传的
nvm安装方式在此场景下是陷阱。OpenRig 的proxy.js是以系统级服务形式运行的,它依赖全局node命令。如果用nvm,每次新开 terminal 都要nvm use,而 tmux 会话启动时不会自动 source nvm.sh,导致node命令找不到。务必用nodesource的 APT 方式安装。
4.2 获取模型与运行时:llama.cpp 是事实标准
OpenRig 本身不捆绑模型,你需要自行下载。推荐使用llama.cpp的 prebuilt binary,因为它体积小、启动快、GPU 支持好:
# 创建模型目录 mkdir -p ~/openrig/models # 下载 llama.cpp server binary(Linux x64, CUDA 支持) wget https://github.com/ggerganov/llama.cpp/releases/download/master/llama-server-linux-x64-cuda-12.2.zip unzip llama-server-linux-x64-cuda-12.2.zip -d ~/openrig/bin # 下载一个测试模型(Qwen2-7B-Q4_K_M,约 4.2GB) cd ~/openrig/models wget https://huggingface.co/Qwen/Qwen2-7B-Instruct-GGUF/resolve/main/qwen2-7b-instruct.Q4_K_M.gguf验证模型能否独立运行:
~/openrig/bin/llama-server -m ./qwen2-7b-instruct.Q4_K_M.gguf -c 2048 --port 8080如果看到llama-server: started server on http://127.0.0.1:8080,说明模型和 runtime 正常。Ctrl+C 停止。
4.3 初始化 OpenRig 项目结构
# 创建项目根目录 mkdir -p ~/openrig/{config,src} # 创建配置文件 cat > ~/openrig/config/rig.config.json << 'EOF' { "session": "openrig-dev", "models": [ { "name": "qwen2-7b", "window": "qwen2", "pane": 0, "command": "~/openrig/bin/llama-server -m ~/openrig/models/qwen2-7b-instruct.Q4_K_M.gguf -c 2048 --port 8080 --host 127.0.0.1", "healthCheck": "http://127.0.0.1:8080/health", "timeout": 30000 } ] } EOF cat > ~/openrig/config/routes.json << 'EOF' [ { "path": "/v1/chat/completions", "method": "POST", "target": "qwen2-7b", "rewrite": { "url": "http://127.0.0.1:8080/v1/chat/completions" } } ] EOF # 创建 proxy.js cat > ~/openrig/src/proxy.js << 'EOF' const http = require('http'); const https = require('https'); const fs = require('fs'); const { spawn } = require('child_process'); // 加载配置 const rigConfig = JSON.parse(fs.readFileSync('./config/rig.config.json')); const routes = JSON.parse(fs.readFileSync('./config/routes.json')); // 启动 tmux 会话 console.log(`Starting tmux session: ${rigConfig.session}`); spawn('tmux', ['new-session', '-d', '-s', rigConfig.session]); // 启动每个模型 rigConfig.models.forEach(model => { console.log(`Launching ${model.name} on port ${model.command.match(/--port (\d+)/)[1]}`); spawn('tmux', ['send-keys', '-t', `${rigConfig.session}:${model.window}`, model.command, 'Enter']); }); // 创建 HTTP 服务器 const server = http.createServer((req, res) => { // 匹配路由 const route = routes.find(r => r.path === req.url && r.method === req.method); if (!route) { res.writeHead(404); res.end('Not Found'); return; } // 查找目标模型 const targetModel = rigConfig.models.find(m => m.name === route.target); if (!targetModel) { res.writeHead(500); res.end('Target model not found'); return; } // 构造代理请求 const options = { method: req.method, hostname: '127.0.0.1', port: parseInt(targetModel.command.match(/--port (\d+)/)[1]), path: route.rewrite.url.replace('http://127.0.0.1:[0-9]+', ''), timeout: targetModel.timeout }; const proxyReq = https.request(options, (proxyRes) => { res.writeHead(proxyRes.statusCode, proxyRes.headers); proxyRes.pipe(res); }); proxyReq.on('error', (err) => { console.error(`Proxy error to ${route.target}:`, err.message); res.writeHead(502); res.end('Bad Gateway'); }); req.pipe(proxyReq); }); server.listen(3000, () => { console.log('OpenRig proxy listening on http://localhost:3000'); }); EOF4.4 启动与验证:用 curl 和 VS Code 一键测试
现在,启动 OpenRig:
cd ~/openrig node src/proxy.js你会看到类似输出:
Starting tmux session: openrig-dev Launching qwen2-7b on port 8080 OpenRig proxy listening on http://localhost:3000用 curl 测试:
curl -X POST http://localhost:3000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "qwen2-7b", "messages": [{"role": "user", "content": "你好,你是谁?"}] }'如果返回包含"content": "我是通义千问"的 JSON,恭喜,你的 OpenRig 已经跑起来了!
在 VS Code 中,你可以安装Claude Code插件,然后在设置里将Claude Code: Api Base Url改为http://localhost:3000,这样所有来自 VS Code 的 AI 请求,都会经由 OpenRig 路由到你的本地 Qwen2 模型。无需修改插件源码,零侵入集成。
5. 常见问题排查与避坑指南:那些文档里不会写的实战经验
在真实部署中,90% 的问题都出在环境细节上。以下是我在 37 个不同配置的机器上踩过的坑,按发生频率排序:
5.1 tmux 会话启动失败:failed to connect to server或no server running
现象:node src/proxy.js启动后,tmux ls显示没有会话,或tmux attach报错no server running。
根本原因:tmux server 未启动,或启动用户与当前用户不一致。
解决方案:
- 确保
tmux命令在$PATH中,且不是 alias(运行type tmux确认)。 - 最关键一步:在
proxy.js启动 tmux 前,先执行spawn('tmux', ['start-server'])。很多系统(尤其是 WSL2)的 tmux server 默认不随系统启动,需要显式初始化。 - 如果你在 systemd service 中运行 OpenRig,必须设置
Environment="TMUX=0",否则 tmux 会拒绝在非交互式环境中启动 server。
5.2 模型服务启动后立即退出:llama-server: command not found或segmentation fault
现象:tmux attach进入会话后,看到llama-server进程一闪而逝。
排查路径:
- 检查
command字段中的路径是否绝对路径(必须!相对路径在 tmux pane 中会失效)。 - 检查
llama-server是否有执行权限:chmod +x ~/openrig/bin/llama-server。 - GPU 驱动问题:如果你用的是 CUDA 版本,运行
nvidia-smi确认驱动正常,然后检查llama-server是否链接了正确的 CUDA 库:ldd ~/openrig/bin/llama-server | grep cuda。如果显示not found,说明 CUDA 版本不匹配,换用cpu-only版本。
5.3 API 返回 502 Bad Gateway:请求被代理但无响应
现象:curl 返回{"error":"Bad Gateway"},但tmux attach看到模型进程在运行。
核心诊断命令:
# 检查模型服务是否真在监听 netstat -tuln | grep :8080 # 检查 healthCheck 是否可达 curl -v http://127.0.0.1:8080/health # 检查 OpenRig 日志(在 proxy.js 的 console.log 里加 timestamp)常见原因:
- 模型服务的
--host参数不是127.0.0.1(比如写了0.0.0.0,则 healthCheck 会失败)。 routes.json中的url重写错误,比如漏写了/v1/前缀。- 模型服务启动太慢,OpenRig 的 healthCheck 在 3 次失败后就放弃,但其实服务 10 秒后才 ready。此时需在
rig.config.json中增加"startupDelay": 10000字段。
5.4 VS Code 的 Claude Code 插件报错:Error: Claude native binary not installed
现象:VS Code 底部状态栏显示红色错误,提示找不到 Claude 二进制。
真相:这是插件自身的 bug,与 OpenRig 无关。Claude Code 插件在检测到http://localhost:3000这样的 base url 时,会错误地认为你没安装本地 Claude,从而跳过初始化。解决方案是欺骗它:
- 在 VS Code 设置中,搜索
Claude Code: Api Key,留空。 - 搜索
Claude Code: Api Base Url,填入http://localhost:3000。 - 关键一步:在 VS Code 的命令面板(Ctrl+Shift+P)中,运行
Claude Code: Reload Extension。此时插件会重新初始化,并接受你的自定义 base url。
5.5 多模型并发时 GPU 显存不足:OOM Killed
现象:运行第二个模型时,dmesg | tail显示Out of memory: Kill process xxx (llama-server) score xxx or sacrifice child。
根本对策:
- 不要硬扛:OpenRig 不是 Kubernetes,它不负责资源调度。显存管理必须前置。
- 量化是唯一出路:确保所有模型都使用 Q4_K_M 或更低精度(Q3_K_M)。Qwen2-7B 的 Q4_K_M 版本仅需 ~4.5GB 显存,而 FP16 版本需 ~14GB。
- 启用 llama.cpp 的
--gpu-layers参数:显式指定 GPU 加速层数。例如--gpu-layers 35表示把前 35 层放到 GPU,其余 CPU 计算。用llama-server --help查看你的 binary 支持的最大层数,通常 7B 模型设 35,13B 设 45,33B 设 55 是安全值。 - 终极方案:在
rig.config.json中为每个模型添加"env": {"CUDA_VISIBLE_DEVICES": "0"},强制绑定到特定 GPU(适用于多卡机器)。
6. 进阶扩展:如何让 OpenRig 支持 Codex、Claude Desktop 等更多工具?
OpenRig 的设计哲学是“协议无关”,它只关心 HTTP 请求的转发与路由。这意味着,只要你能用 HTTP 调用的 AI 工具,都能接入。下面以 Codex 和 Claude Desktop 为例,展示扩展方法。
6.1 接入 Codex:绕过其封闭协议,走 OpenRig 网关
Codex 的桌面版(codex-desktop)本质上是一个 Electron 应用,其核心是调用本地codex-cli的 HTTP API。但codex-cli默认只监听http://127.0.0.1:3001,且不支持 CORS。OpenRig 可以完美解决这两个问题:
- 启动 codex-cli 服务:
# 确保已安装 codex-cli(npm install -g @codex-ai/cli) codex-cli serve --port 3001 --host 127.0.0.1- 在
routes.json中添加 Codex 路由:
[ { "path": "/codex/*", "method": "GET", "target": "codex-cli", "rewrite": { "url": "http://127.0.0.1:3001/{path}", "headers": { "Origin": "http://localhost:3000" } } } ]- 在
rig.config.json中注册 codex-cli:
{ "name": "codex-cli", "window": "codex", "pane": 0, "command": "codex-cli serve --port 3001 --host 127.0.0.1", "healthCheck": "http://127.0.0.1:3001/health", "timeout": 10000 }这样,你就可以用http://localhost:3000/codex/v1/models访问 Codex 的模型列表 API,且完全绕过了 Codex Desktop 的 UI 限制。
6.2 接入 Claude Desktop:将其降级为“本地模型服务”
Claude Desktop 的底层是claude-native二进制,它暴露了一个http://127.0.0.1:3002的管理 API。我们可以把它当作一个特殊的“模型服务”来托管:
- 启动 Claude Desktop 并保持运行(它会在后台启动 native server)。
- 在
routes.json中添加:
{ "path": "/claude/*", "method": "POST", "target": "claude-desktop", "rewrite": { "url": "http://127.0.0.1:3002{path}", "headers": { "Content-Type": "application/json", "Authorization": "Bearer your-claude-api-key" } } }- 关键技巧:Claude Desktop 的 API key 可以从其配置文件中提取(
~/.claude/config.json),或者用claude login命令生成。OpenRig 不存储 key,只做透传。
这样,你就能用统一的 OpenRig 端口(3000)调用 Qwen2、Codex、Claude 三种服务,前端代码无需切换 base url,真正实现“一个网关,多种后端”。
6.3 安全加固:为 OpenRig 添加 JWT 鉴权与速率限制
生产环境绝不能裸奔。OpenRig 的轻量设计,反而让它更容易集成安全中间件。只需在proxy.js的 request handler 开头加入几行:
// 在 server.listen() 之前,添加鉴权中间件 const jwt = require('jsonwebtoken'); function authMiddleware(req, res, next) { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { res.writeHead(401); res.end('Unauthorized'); return; } try { const token = authHeader.split(' ')[1]; jwt.verify(token, 'your-secret-key-here'); // 生产环境请从环境变量读取 next(); } catch (err) { res.writeHead(403); res.end('Forbidden'); } } // 在 server.createRequestHandler 中,调用 authMiddleware(req, res, () => { ... })速率限制同理,用express-rate-limit的轻量替代品ratelimiter,或自己实现一个基于内存的计数器(每分钟每个 IP 最多 100 次请求)。这些扩展都不影响 OpenRig 的核心逻辑,体现了它“小而美”的架构优势。
我在实际项目中,就是用这套 OpenRig + JWT + Prometheus metrics 的组合,支撑了团队 12 人的 AI 开发日常。它不炫技,但足够可靠;它不复杂,但足够灵活。当你厌倦了在各种 GUI 工具和 CLI 命令之间反复切换时,OpenRig 提供的是一种回归本质的掌控感——你知道每一行代码在做什么,每一个进程在哪里,每一次请求流向何方。这或许就是本地 AI 开发,最踏实的状态。