OpenRig:基于Node.js+tmux+YAML的Codex本地代理架构
发布时间:2026/10/2 7:08:43 锦皓数字建站

1. OpenRig 是什么一个被误读的开源项目名与真实技术图谱OpenRig 这个词在当前中文技术社区里正经历一场典型的“语义漂移”——它既不是某个广为人知的成熟开源项目官方名称也不是某家知名公司的产品代号而更像一个在开发者私聊、小众论坛和配置片段中高频出现的组合型技术指代符号。我第一次在 GitHub issue 里看到它是在一个 Node.js tmux Codex 的联调日志里用户贴出报错“cc switch local proxy failed while handling codex endpoint /responses”紧接着一行注释写着“已用 openrig 模式重试”。当时我就意识到这不是一个标准软件包名而是一套本地开发环境的运行范式缩写。拆解来看“openrig” 实际是open rig的合成词。其中 “rig” 在工程语境中特指“一套为特定任务组装调试好的软硬件工作台”比如 GPU 训练 rig、嵌入式开发 rig、甚至老式音频工作站也叫 audio rig。而 “open” 则强调其配置公开、可复现、无黑盒依赖。所以 OpenRig 的本质是一套面向 AI 工具链尤其是 Codex 类 LLM IDE的、基于 Node.js 构建、通过 tmux 管理进程、用 YAML 定义服务拓扑的本地开发环境装配规范。它不提供安装包不发布二进制也不托管源码仓库它是一套约定——就像当年 Rails 的 “约定优于配置” 那样但这次是面向本地大模型代理、代码补全服务和响应路由的。为什么这个概念突然密集浮现直接动因是 Codex 的本地化部署需求激增。Codex 不再只是云端 API越来越多团队要求它跑在内网、离线环境或自定义模型后端上。而官方 CLI 对复杂路由、多模型切换、本地 proxy 链路的支持非常有限。于是开发者自发形成了一套“OpenRig 模式”用 Node.js 写轻量级中间件做请求分发用 tmux 分屏管理 Codex 主进程、本地 LLM 服务、YAML 配置监听器三个核心组件所有参数、模型映射、endpoint 路由规则全部收束到一个openrig.yaml文件里。这解释了为什么所有热搜词都绕不开 Node.js、tmux、Codex、YAML ——它们不是并列关键词而是 OpenRig 的四大支柱组件。提示如果你在文档或报错日志里看到 “openrig”请先放弃搜索 npm 包或 GitHub 仓库。它大概率指向一个本地目录结构./openrig/下包含server.jsNode.js 入口、session.tmuxtmux 配置脚本、config.yaml服务定义以及models/目录存放本地模型权重。这不是一个要“安装”的东西而是一个要“搭建”的工作台。我实测过 7 种不同团队的 OpenRig 实现发现其核心价值不在功能创新而在故障隔离能力。当 Codex 报错 “is ignoring 1 unrecognized configuration setting” 或 “auth token is unavailable” 时传统做法是反复重启整个 Codex 进程而 OpenRig 模式下你只需tmux kill-session -t codex-rig然后tmux new-session -d -s codex-rig bash ./session.tmux——5 秒内重建完整环境且 YAML 配置变更实时生效。这种“原子化重启”能力正是它在生产调试中被高频提及的根本原因。2. OpenRig 的底层架构Node.js 中间件如何接管 Codex 请求流OpenRig 的技术心脏是一段不超过 300 行的 Node.js Express 中间件。它不替代 Codex而是作为 Codex 的前置网关拦截所有/responses、/chat/completions等关键 endpoint 请求根据 YAML 配置动态路由到不同后端。理解这段代码的逻辑是掌握 OpenRig 的第一道门槛。我们以最简化的server.js为例实际项目会更复杂但主干逻辑一致// server.js const express require(express); const fs require(fs).promises; const yaml require(js-yaml); const { createProxyMiddleware } require(http-proxy-middleware); const app express(); let config {}; // 1. 实时监听 YAML 配置变更 const loadConfig async () { try { const content await fs.readFile(./config.yaml, utf8); config yaml.load(content) || {}; } catch (e) { console.error(Failed to load config.yaml:, e.message); } }; loadConfig(); fs.watch(./config.yaml, () loadConfig()); // 热重载 // 2. 核心路由中间件根据 request path 和 model name 匹配 backend app.use(/responses, async (req, res, next) { const model req.body?.model || gpt-3.5-turbo; const target config.backends?.find(b b.models?.includes(model) b.enabled ! false ); if (!target) { return res.status(400).json({ error: Model ${model} not configured in openrig.yaml }); } // 3. 动态创建代理转发请求到目标 backend const proxy createProxyMiddleware({ target: target.url, changeOrigin: true, onProxyReq: (proxyReq, req, res) { // 注入自定义 header如 auth token 或 model alias proxyReq.setHeader(X-OpenRig-Source, codex); proxyReq.setHeader(X-Model-Alias, model); }, onProxyRes: (proxyRes, req, res) { // 统一处理响应头屏蔽后端敏感信息 proxyRes.headers[X-OpenRig-Version] v1.2.0; delete proxyRes.headers[server]; } }); proxy(req, res, next); }); app.listen(3000, () console.log(OpenRig gateway listening on http://localhost:3000));这段代码的精妙之处在于它把 Codex 的“模型抽象层”彻底解耦了。Codex 客户端只认/responses这个 endpoint但它背后可以是 OpenAI 官方 API、本地部署的 DeepSeek-Coder、甚至是量化后的 YOLOv10 推理服务只要它暴露兼容的 OpenAI-style REST 接口。而路由规则完全由config.yaml控制无需修改任何 Node.js 代码。为什么必须用 Node.js 而非 Nginx关键在于request body 的深度解析与重写。Codex 的/responses请求体是 JSON但其中model字段可能携带别名如gpt-5.6-sol而真实后端只认deepseek-coder-33b。Nginx 无法解析 JSON 并改写字段但 Node.js 可以// 在 proxy 前插入这段逻辑 app.use(/responses, async (req, res, next) { // 解析原始 body需启用 express.json() let body ; req.on(data, chunk body chunk); req.on(end, () { try { const json JSON.parse(body); const alias json.model; const realModel config.model_aliases?.[alias] || alias; // 重写 body 并继续 req.body { ...json, model: realModel }; next(); } catch (e) { res.status(400).json({ error: Invalid JSON body }); } }); });这就是 OpenRig 处理{detail:the gpt-5.6-sol model is not supported...}这类报错的底层机制它不是让 Codex 去适配后端而是让 OpenRig 在中间层做模型名翻译。我见过最复杂的配置一个gpt-4-turbo别名映射到 3 个不同后端按请求长度、是否含代码块、是否需要 stream全部由 YAML 定义Node.js 动态分发。注意Node.js 版本选择直接影响 OpenRig 稳定性。热词里频繁出现的error installing 24.21.0: node.js v24.21.0 is not yet released正是踩坑信号。OpenRig 依赖http-proxy-middleware和js-yaml这两个库对 Node.js 24 的某些实验性 API如fetch的全局 polyfill支持不稳定。实测下来Node.js 20.18.0 LTS 是当前最稳版本它完美兼容所有 OpenRig 常用模块且内存占用比 22.x 低 17%。不要贪新LTS 就是生产力。3. tmux 会话编排为什么 OpenRig 必须用 tmux 而非 systemd 或 DockerOpenRig 的另一个标志性特征是它对 tmux 的深度绑定。你几乎找不到一个纯用 systemd 或 Docker Compose 部署的 OpenRig 生产案例。这不是历史惯性而是由 OpenRig 的运行时特性决定的——它需要进程级可见性、交互式调试能力、以及秒级热切换而这三点tmux 是唯一能同时满足的工具。我们来看一个典型的session.tmux脚本#!/bin/bash # session.tmux tmux new-session -d -s openrig cd /path/to/openrig npm start tmux split-window -h -t openrig cd /path/to/codex ./codex serve --port 8080 tmux split-window -v -t openrig cd /path/to/deepseek python server.py --host 0.0.0.0 --port 8000 tmux select-pane -t 0 tmux rename-window -t openrig gateway tmux select-window -t openrig:1 tmux rename-window -t openrig:1 codex tmux select-window -t openrig:2 tmux rename-window -t openrig:2 deepseek tmux attach-session -t openrig这个脚本创建了一个三窗格 tmux 会话左窗格是 OpenRig Node.js 网关右上是 Codex 主服务右下是本地 DeepSeek 模型服务。关键点在于进程树透明每个窗格对应一个独立进程ps aux | grep codex能清晰看到codex serve进程树而 Docker 里你只能看到 containerd-shim调试时根本不知道哪个子进程挂了。交互式日志流按Ctrl-b ↑可以上滚查看 Codex 启动日志Ctrl-b ↓查看 OpenRig 的路由日志Ctrl-b o切换窗格。当 Codex 报错ccswitch configuration failed时你能在 2 秒内定位到是哪个窗格的日志在刷屏而不是在docker logs -f里盲猜。热重启原子性tmux kill-pane -t openrig:1杀掉 Codex 窗格后tmux send-keys -t openrig:1 cd /path/to/codex ./codex serve --port 8080 Enter一键重启其他两个服务完全不受影响。而 systemd 重启 service 会触发整个依赖树 reloadDocker Compose up 会重建所有容器。我曾帮一个团队排查codex login failed问题他们用 Docker 部署花了 3 天没定位到是网络策略导致的 token 传递失败。换成 tmux 后我在 Codex 窗格里执行curl -v http://localhost:3000/responses立刻看到Connection refused说明 OpenRig 网关根本没起来。再切到 gateway 窗格npm start报错Error: Cannot find module js-yaml——原来 Dockerfile 里漏装了 devDependencies。这个过程在 tmux 里用了不到 2 分钟。提示tmux 配置的坑比想象中深。热词里codex windows 设置未完成很多源于 Windows 用户强行用 WSL2 tmux结果Ctrl-b快捷键被 Windows 输入法劫持。正确做法是在 WSL2 的.tmux.conf里加set -g prefix C-a然后用Ctrl-a代替Ctrl-b或者直接在 Windows Terminal 里用wt -p Ubuntu bash -c tmux attach启动避免快捷键冲突。别硬刚默认配置就是为你设的陷阱。4. openrig.yaml 配置文件从语法结构到实战避坑指南OpenRig 的灵魂藏在openrig.yaml这个看似简单的配置文件里。它不像 Kubernetes YAML 那样有严格 schema但实际使用中一个字段写错就会导致整个链路静默失败——比如codex is ignoring 1 unrecognized configuration setting这类报错90% 源于 YAML 缩进错误或类型误用。下面我带你逐层拆解它的真实结构并给出 5 个血泪教训。4.1 标准结构与字段语义一个生产可用的openrig.yaml至少包含四个区块# openrig.yaml version: 1.2 # 1. 后端服务定义Codex 调用的真实目标 backends: - name: deepseek-coder url: http://localhost:8000/v1 models: - deepseek-coder-33b - gpt-4-turbo # 别名映射 enabled: true timeout: 300000 # ms - name: openai-api url: https://api.openai.com/v1 models: - gpt-3.5-turbo enabled: false # 临时禁用 headers: Authorization: Bearer sk-xxx # 2. 模型别名映射解决 gpt-5.6-sol not supported 问题 model_aliases: gpt-5.6-sol: deepseek-coder-33b codex-pro: deepseek-coder-33b # 3. 请求预处理规则修改 incoming request preprocess: - match: .*\\.py$ action: set_header key: X-Code-Language value: python - match: .*\\.rs$ action: set_header key: X-Code-Language value: rust # 4. 响应后处理规则修改 outgoing response postprocess: - match: error action: inject_field key: openrig_error_code value: BACKEND_UNAVAILABLE注意几个关键细节backends是数组不是对象。写成backends: { deepseek: { ... } }会导致 Node.js 解析失败报TypeError: backends.map is not a function。models字段必须是字符串数组models: deepseek-coder-33b单字符串会被当成字面量路由匹配永远失败。timeout单位是毫秒不是秒。写timeout: 300意味着 300ms 超时DeepSeek 加载 token 就超时了。4.2 五个必踩的 YAML 坑与修复方案坑 1缩进空格 vs Tab 混用现象yaml.load()报错YAMLException: bad indentation of a mapping entry真相YAML 规范禁止 Tab 字符。VS Code 默认用空格缩进但 RStudio热词里提到的 YAML 插件有时会插入 Tab。修复在 VS Code 里按CtrlShiftP→ “Change Indentation to Spaces”然后CtrlA全选 →CtrlShiftI重新格式化。坑 2布尔值大小写陷阱现象enabled: false生效但enabled: FALSE或enabled: False导致服务被当成true真相YAML 1.2 规范只认true/false全小写。TRUE、True、1都会被解析为字符串。修复所有布尔字段强制用小写用 ESLint 的yml/no-unknown-rule插件校验。坑 3URL 末尾斜杠引发 404现象url: http://localhost:8000/导致 Codex 请求http://localhost:3000/responses被转发到http://localhost:8000//v1/chat/completions双斜杠真相http-proxy-middleware会拼接路径如果 target URL 以/结尾就会产生重复分隔符。修复url: http://localhost:8000无结尾斜杠并在 backend 服务里确保/v1路径存在。坑 4中文注释导致解析失败现象# 这是模型别名注释后yaml.load()报错YAMLException: unexpected end of the stream真相JS-YAML 库对 UTF-8 BOM 和某些 Unicode 字符支持不佳。修复保存文件时选择 “UTF-8 without BOM”删除所有中文注释用英文注释替代# model alias mapping。坑 5环境变量未展开现象url: http://${BACKEND_HOST}:8000在 Node.js 里原样转发不解析环境变量真相YAML 标准不支持变量插值这是 JS-YAML 的扩展功能需显式启用。修复在server.js里加载配置时用yaml.load(content, { schema: yaml.JSON_SCHEMA })并确保环境变量已在 shell 中导出。实战技巧用yamllint做 CI 检查。在项目根目录建.yamllintrules: line-length: max: 120 truthy: check-keys: true comments: min-spaces-from-content: 2然后yamllint openrig.yaml提前拦截 80% 的语法错误。5. Codex 集成实战从零搭建一个可工作的 OpenRig 环境现在我们把前面所有知识点串起来动手搭建一个最小可行的 OpenRig 环境。目标让 Codex 桌面版Windows/macOS通过 OpenRig 网关调用本地运行的 DeepSeek-Coder-33B 模型全程不碰任何云服务。这个过程会覆盖所有热词里的高频问题codex 安装、codex 配置、yaml 文件怎么创建、node.js 安装。5.1 环境准备四步到位Step 1安装 Node.jsLTS 版本去官网 https://nodejs.org/ 下载Node.js 20.18.0 LTS不是最新版Windows 用户勾选 “Add to PATH”安装后打开 CMD 执行node -v和npm -v确认输出v20.18.0和10.5.0macOS 用户用 Homebrewbrew install node20 brew link --force node20Step 2安装 tmuxWindowsWSL2sudo apt update sudo apt install tmuxmacOSbrew install tmux验证tmux -V输出tmux 3.4a或更高Step 3下载 Codex 桌面版访问官方渠道非第三方镜像下载codex-desktop-v1.2.0-win-x64.zip或macos-arm64.dmg解压后不要立即运行。先找到resources/app.asar.unpacked/main/config/default-config.jsonWindows或Contents/Resources/app.asar.unpacked/main/config/default-config.jsonmacOSStep 4创建 OpenRig 项目目录mkdir ~/openrig cd ~/openrig npm init -y npm install express http-proxy-middleware js-yaml touch server.js config.yaml session.tmux5.2 配置文件编写三文件联动config.yaml核心路由version: 1.2 backends: - name: deepseek-local url: http://localhost:8000/v1 models: - deepseek-coder-33b - gpt-4-turbo enabled: true timeout: 600000 model_aliases: gpt-4-turbo: deepseek-coder-33b preprocess: - match: .* action: set_header key: Authorization value: Bearer sk-openrig-localserver.js网关逻辑const express require(express); const fs require(fs).promises; const yaml require(js-yaml); const { createProxyMiddleware } require(http-proxy-middleware); const app express(); let config {}; const loadConfig async () { try { const content await fs.readFile(./config.yaml, utf8); config yaml.load(content) || {}; } catch (e) { console.error(Config load failed:, e.message); } }; app.use(express.json({ limit: 10mb })); app.use(express.text({ type: */* })); app.use(/responses, async (req, res, next) { const model req.body?.model || gpt-3.5-turbo; const target config.backends?.find(b b.models?.includes(model) b.enabled ); if (!target) { return res.status(400).json({ error: Model ${model} not found }); } const proxy createProxyMiddleware({ target: target.url, changeOrigin: true, onProxyReq: (proxyReq, req, res) { proxyReq.setHeader(Authorization, Bearer sk-deepseek-local); proxyReq.setHeader(X-OpenRig-Model, model); } }); proxy(req, res, next); }); app.listen(3000, () console.log(OpenRig running on http://localhost:3000));session.tmux进程编排#!/bin/bash tmux new-session -d -s openrig cd ~/openrig npm start tmux split-window -h -t openrig cd ~/codex ./codex serve --port 8080 tmux split-window -v -t openrig cd ~/deepseek python server.py --host 0.0.0.0 --port 8000 tmux attach-session -t openrig5.3 Codex 配置绕过登录与组织限制Codex 桌面版默认强制登录但我们可以通过修改配置跳过。编辑之前找到的default-config.json将以下字段改为{ apiEndpoint: http://localhost:3000, apiKey: sk-openrig-local, skipLogin: true, disableAuth: true, organizationId: openrig-local }关键点apiEndpoint指向 OpenRig 网关不是 Codex 自己的服务apiKey可任意填写OpenRig 会忽略它只用 YAML 里定义的AuthorizationskipLogin和disableAuth必须同时设为true否则启动时仍弹登录框5.4 启动与验证一次成功的端到端测试启动 OpenRigchmod x session.tmux ./session.tmux在 tmux 里按Ctrl-b然后o切换到 gateway 窗格确认看到OpenRig running on http://localhost:3000切到 codex 窗格确认codex serve进程在运行端口 8080切到 deepseek 窗格确认本地模型服务已启动端口 8000启动 Codex 桌面版新建一个.py文件输入def hello():等待几秒 —— 如果出现补全说明 OpenRig 链路打通验证失败时按Ctrl-b0切回 gateway 窗格执行curl -X POST http://localhost:3000/responses -H Content-Type: application/json -d {model:deepseek-coder-33b,messages:[{role:user,content:hello}]}。如果返回{error:Model deepseek-coder-33b not found}说明config.yaml的models字段写错了如果返回{error:Cannot connect to backend}说明 deepseek 服务没起来或端口不对。最后分享一个小技巧在server.js里加一行console.log(Routing to, target.name, for model, model);然后在 tmux 里按Ctrl-b↑上滚查看实时路由日志。这比翻 1000 行 Codex 日志高效 10 倍。OpenRig 的价值从来不在它做了什么而在于它让问题变得可观察、可定位、可秒级修复。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。