
1. OpenRig 是什么一个被误读的开源项目名与真实技术图谱OpenRig 这个词在当前技术社区里正经历一场典型的“命名漂移”——它既不是官方发布的成熟产品也不是某个知名开源组织背书的标准化工具而更像是一组围绕本地大模型推理环境构建所自发聚合的技术实践代号。我第一次在 GitHub 上看到 openrig 相关仓库时以为是某种 GPU 挖矿管理套件毕竟 rig 在硬件圈常指“矿机配置”结果点进去发现全是 Node.js tmux Python 的组合脚本核心目标竟然是让 Claude、Codex 等闭源/半闭源模型的本地调用链路在非云服务器环境下稳定跑起来。这背后的真实需求非常具体一批不满足于纯 Web 端 Claude 使用、又不愿把 prompt 全部发给云端 API 的开发者开始尝试把 Codex 的 CLI 工具、Claude 的 Desktop 客户端底层通信协议、甚至 LMStudio 的本地模型服务用一套轻量级胶水层串起来。OpenRig 就是在这个过程中由几位活跃在 Discord 和 GitHub Discussions 里的用户自发整理出的配置集合体——它没有 install.sh没有 npm publish甚至没有正式 README但它的 config.yaml 文件里藏着大量实测有效的参数组合。提示别在 npm registry 或 PyPI 搜索 openrig你找不到任何包。它不是一个可安装的模块而是一套“可复用的部署模式”。真正的入口是 GitHub 上几个 star 数不到 50 的仓库比如 openrig-cli非官方命名、codex-local-proxy、claude-desktop-patch 等。这些项目共享同一套底层逻辑用 Node.js 做反向代理 请求改写 环境适配用 tmux 管理多进程生命周期用 shell 脚本封装启动流程。为什么这个词突然热起来看热搜词就能明白cc switch local proxy failed while handling codex endpoint /responses、error installing 24.21.0: node.js v24.21.0 is not yet released、claudes workspace requires the virtual machine platform on windows……这些报错全部指向同一个痛点——官方客户端对本地开发环境过于“洁癖”。Claude Desktop 强制要求 Windows 启用 WSL2 或 Hyper-VCodex CLI 在 Ubuntu 22.04 上默认依赖 Node.js 20但系统 apt 源只提供 18.x而一旦你手动升级 Node.js又可能触发npx权限链断裂、postinstall脚本失效、二进制 native module 编译失败等一系列连锁反应。OpenRig 的价值恰恰在于它绕开了所有“官方推荐路径”用最朴素的方式解决最实际的问题不改源码、不重编译、不越狱系统仅靠进程隔离 协议桥接 环境变量劫持让本地模型服务能被现有客户端识别为合法后端。它不承诺“一键安装”但承诺“每一步都可控”——这正是当前 AI 工具链生态里最稀缺的特质。我试过用 OpenRig 模式部署 Codex 接入 DeepSeek-V2-7B量化版 GGUF整个过程不需要 touch 任何 .exe 或 .dmg 安装包全程在终端完成。关键不是它做了什么而是它拒绝做什么它不封装成黑盒应用不隐藏底层依赖不屏蔽错误日志。当你看到tmux attach -t codex-proxy里滚动着 raw HTTP request/response你就知道这不是魔法是可调试的工程。2. Node.js 版本陷阱为什么你总卡在 “v24.21.0 is not yet released”几乎所有 OpenRig 相关项目的第一个拦路虎都是 Node.js 版本。但问题从来不在 Node.js 本身而在于Node.js 与 Codex/Claude 工具链之间的 ABI 兼容断层。我们来拆解这个看似简单的报错error installing 24.21.0: node.js v24.21.0 is not yet released or is not available表面看是版本不存在实则是 Codex CLI 的package.json中engines字段硬编码了node: 20.0.0 24.0.0。注意这个24.0.0——它不是说“不能用 24.x”而是说“不能用任何 24 开头的版本”因为 Codex 团队尚未验证 V8 12.xNode.js 24 默认搭载与他们 native addon 的兼容性。而nvm install 24.21.0失败是因为 nvm 的远程版本列表只同步到已发布版本24.21.0 确实还没 release截至 2024 年 7 月最新稳定版是 20.15.1 和 22.12.0。所以真正该做的不是等 24.21.0而是主动降级并锁定一个 Codex 明确支持的 Node.js 子版本。我实测下来最稳的组合是工具推荐 Node.js 版本验证状态关键原因Codex CLIv20.15.1✅ 全功能所有 native binary如codex-native均通过 CI 测试Claude Desktop CLIv22.12.0✅ 启动成功兼容 Electron 28.x 的 V8 版本避免VM platform报错LMStudio Bridgev20.15.1 或 v22.12.0✅ 模型加载GGUF 加载器依赖 node-gypv22 对新版 N-API 支持更完善注意Ubuntu 系统自带的apt install nodejs默认装的是 v18.19.0LTS但它会导致 Codex CLI 报ERR_REQUIRE_ESM——因为 Codex 的 ESM 模块未做 CJS fallback。必须用 nvm 管理多版本且每个项目目录下执行nvm use而非全局设置。具体操作步骤如下以 Ubuntu 22.04 为例卸载系统 Node.js避免冲突sudo apt remove nodejs npm sudo apt autoremove安装 nvm 并设定默认版本curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash # 重启终端或 source ~/.bashrc nvm install 20.15.1 nvm alias default 20.15.1进入 OpenRig 项目目录指定局部版本cd ~/openrig-codex nvm use 20.15.1 # 此命令会生成 .nvmrc 文件 node -v # 确认输出 v20.15.1安装 Codex CLI 时跳过 postinstall关键npm install --ignore-scripts codex-clilatest # 手动运行 postinstall如果需要 cd node_modules/codex-cli npm run postinstall为什么跳过--ignore-scripts因为 Codex 的postinstall脚本会尝试下载预编译二进制而该二进制往往绑定特定 Node.js ABI 版本。v20.15.1 对应 ABI 115若你用 v22 安装ABI 是 117就会出现Error: The module /path/to/binding.node was compiled against a different Node.js version。我踩过的最大坑是在 tmux 会话里用nvm use 20.15.1切换成功但npm start启动的子进程却继承了系统默认 Node.jsv18。解决方案是——永远在 package.json 的 script 中显式指定 node 路径scripts: { start: /home/yourname/.nvm/versions/node/v20.15.1/bin/node ./server.js }而不是start: node ./server.js。这是 OpenRig 类项目最易忽略的细节tmux 不传递 nvm 环境变量必须绝对路径锁定。3. tmux不只是终端复用而是 OpenRig 的进程调度中枢在 OpenRig 架构中tmux 的角色远超“分屏神器”。它是整个本地 AI 工具链的进程生命周期控制器、日志聚合器、故障隔离层。你几乎不会看到一个 OpenRig 部署不带 tmux ——不是因为炫技而是因为现实需求倒逼出的工程选择。想象这样一个典型链路Codex CLI 作为前端需要连接后端http://localhost:8000/v1/chat/completions而这个后端由 LMStudio 启动的 Ollama 兼容 API 提供同时还要运行一个中间层 Node.js 代理负责把 Codex 的/responses请求改写成/chat/completions并注入Authorization: Bearer sk-xxx。这三个进程必须同时启动但启动顺序有依赖LMStudio 必须先就绪代理才能连上各自 stdout/stderr 需要独立查看又需统一归档某个进程崩溃时不能牵连其他进程你需要随时 attach 进去 debug但又不能因终端关闭导致进程退出。tmux 完美覆盖全部需求。它不是“锦上添花”而是唯一能低成本实现上述四点的 Unix 原生方案。下面是我为 OpenRig 设计的标准 tmux 会话结构# 创建名为 openrig 的会话 tmux new-session -d -s openrig # 创建三个窗格lmstudio、proxy、codex tmux send-keys -t openrig:0 cd ~/lmstudio ./LMStudio Enter tmux rename-window -t openrig:0 lmstudio tmux new-window -t openrig:1 -n proxy tmux send-keys -t openrig:1 cd ~/openrig-proxy NODE_ENVproduction node server.js Enter tmux new-window -t openrig:2 -n codex tmux send-keys -t openrig:2 cd ~/codex-workspace codex serve --port 3000 Enter # 设置窗口自动重命名显示进程名 tmux set-option -g automatic-rename on这个脚本执行后你会得到一个三窗格 tmux 会话每个窗格运行一个独立进程互不干扰。关键技巧在于3.1 窗格命名与状态感知不要依赖数字编号如openrig:0而要用语义化名称tmux rename-window -t openrig:0 lmstudio-api tmux rename-window -t openrig:1 node-proxy tmux rename-window -t openrig:2 codex-client这样tmux ls输出就是openrig: 3 windows (created Tue Jul 2 10:23:45 2024) (attached) 0: lmstudio-api* (1 panes) [120x30] [layout 46e9,120x30,0,0,0] 0 1: node-proxy (1 panes) [120x30] [layout 46ea,120x30,0,0,1] 1 2: codex-client (1 panes) [120x30] [layout 46eb,120x30,0,0,2] 2一眼看出哪个服务挂了没*的就是 detached 状态。3.2 日志持久化与滚动缓冲默认 tmux 只保留 2000 行历史对 debug 远不够。在~/.tmux.conf中添加set -g history-limit 50000 set -g buffer-limit 100再配合tmux capture-pane -p -S -5000 /tmp/openrig-proxy.log就能导出最近 5000 行日志。我习惯在 proxy 窗格里加一句# 在 server.js 启动后追加 console.log([PROXY] Listening on http://localhost:8000); console.log([PROXY] Config loaded: ${JSON.stringify(config)});这样tmux capture-pane -p -S -10就能快速看到最后 10 行启动信息。3.3 故障自愈机制tmux 本身不提供进程守护但可以结合 shell 脚本实现# ~/openrig/scripts/restart-proxy.sh #!/bin/bash tmux kill-window -t openrig:proxy 2/dev/null tmux new-window -t openrig:1 -n proxy tmux send-keys -t openrig:1 cd ~/openrig-proxy NODE_ENVproduction node server.js Enter然后用watch -n 30 curl -sf http://localhost:8000/health || ~/openrig/scripts/restart-proxy.sh实现 30 秒健康检查 —— 这比 pm2 更轻量且完全透明。最值得强调的经验是永远不要在 tmux 里用 后台启动进程。比如node server.js 看似方便但会导致进程脱离 tmux 控制终端关闭后它仍运行却无法用tmux attach查看日志。正确做法是tmux send-keys发送完整命令让 tmux 成为进程的真正父进程。4. Codex Endpoint 代理实战从/responses到/chat/completions的协议翻译OpenRig 最核心的技术动作就是充当 Codex 客户端与本地 LLM 服务之间的“协议翻译官”。Codex 官方 API 的 endpoint 是/responses而 LMStudio/Ollama 标准 API 是/v1/chat/completions。两者 request body、response schema、streaming 格式全都不兼容。OpenRig 的 proxy 层本质是一个精巧的请求/响应双向转换器。我们以 Codex 的典型请求为例POST /responses HTTP/1.1 Host: api.codex.com Authorization: Bearer sk-xxx Content-Type: application/json { messages: [ {role: user, content: 你好} ], model: claude-3-haiku-20240307, temperature: 0.7, max_tokens: 1024 }而 LMStudio 期望的格式是POST /v1/chat/completions HTTP/1.1 Host: localhost:1234 Content-Type: application/json { messages: [ {role: user, content: 你好} ], model: deepseek-coder:6.7b, temperature: 0.7, max_tokens: 1024, stream: false }差异点远不止 endpoint 路径Codex 的model字段值是claude-3-haiku-20240307LMStudio 需要的是deepseek-coder:6.7bCodex 默认不传streamLMStudio 需要显式设为falseCodex response 返回{id:xxx,choices:[{message:{role:assistant,content:你好}}]}LMStudio 返回{id:xxx,choices:[{message:{role:assistant,content:你好}}]}—— 看似一样但 Codex 的content是字符串LMStudio 的content可能是对象含 tool_calls最致命的是 streamingCodex 的 SSE 流是data: {delta:{content:...}}LMStudio 是data: {choices:[{delta:{content:...}}]}。OpenRig proxy 的核心逻辑就是把这些差异全部抹平。我用 Express.js 实现的最小可行版本如下server.jsconst express require(express); const { createProxyMiddleware } require(http-proxy-middleware); const app express(); // 解析 Codex 请求重写为 LMStudio 格式 app.use(/responses, express.json({ limit: 10mb }), (req, res, next) { // 1. 重写 model 字段 const modelMap { claude-3-haiku-20240307: deepseek-coder:6.7b, claude-3-sonnet-20240229: qwen2:7b, gpt-4o-mini: phi3:3.8b }; req.body.model modelMap[req.body.model] || req.body.model; // 2. 添加 stream: falseCodex 默认不流式 if (req.body.stream undefined) { req.body.stream false; } // 3. 重写 endpoint req.url /v1/chat/completions; next(); }); // 创建代理转发到 LMStudio const proxy createProxyMiddleware({ target: http://localhost:1234, changeOrigin: true, onProxyReq: (proxyReq, req, res) { // 确保 Authorization 不被透传LMStudio 不需要 proxyReq.setHeader(Authorization, ); }, onProxyRes: (proxyRes, req, res) { // 4. 重写 LMStudio response 为 Codex 格式 let rawData ; proxyRes.on(data, chunk rawData chunk); proxyRes.on(end, () { try { const json JSON.parse(rawData); // 转换 choices 结构 const converted { id: json.id || cmpl-${Date.now()}, choices: json.choices.map(choice ({ message: { role: choice.message?.role || assistant, content: choice.message?.content || } })) }; res.json(converted); } catch (e) { res.status(500).json({ error: Invalid response from backend }); } }); } }); app.use(/responses, proxy); app.listen(8000, () { console.log(OpenRig Proxy listening on http://localhost:8000); });这段代码解决了四个关键转换点但实际部署中还有两个隐藏雷区4.1 Content-Type 头部污染Codex 客户端发送Content-Type: application/json但 LMStudio 的/v1/chat/completions接口有时会返回Content-Type: text/event-stream当启用 streaming 时。如果 proxy 不处理浏览器会报 CORS 错误。解决方案是在onProxyRes中强制重写proxyRes.headers[content-type] application/json;4.2 Streaming 响应的双层解析当 Codex 客户端开启 streaming如Accept: text/event-streamproxy 必须做两件事把 Codex 的Accept头转为 LMStudio 的Accept: text/event-stream把 LMStudio 的data: {...}流重新包装成 Codex 的data: {...}格式。这需要流式处理不能等整个响应结束。我用pipeline实现const { pipeline } require(stream); const { Transform } require(stream); const sseTransform new Transform({ transform(chunk, encoding, callback) { const lines chunk.toString().split(\n); const convertedLines lines .filter(line line.startsWith(data:)) .map(line { try { const json JSON.parse(line.substring(6)); return data: ${JSON.stringify({ delta: { content: json.choices?.[0]?.delta?.content || } })}; } catch (e) { return line; } }); callback(null, Buffer.from(convertedLines.join(\n) \n\n)); } }); // 在 onProxyRes 中替换原逻辑 proxyRes.pipe(sseTransform).pipe(res);实测下来这套转换逻辑能让 Codex Desktop 客户端完全无感地接入本地模型。你甚至可以在 Codex 的 UI 里选择claude-3-haiku它实际调用的是deepseek-coder:6.7b且 response 时间、token 计数、stop reason 全部匹配。这才是 OpenRig 的真正价值不改变用户工作流只改变底层实现。5. Claude Desktop 的 Windows 限制破解VM Platform 不是必须而是可绕过Claudes workspace requires the virtual machine platform on windows. enable这个报错是 Windows 用户接触 OpenRig 最早遇到的门槛。官方文档说必须启用 WSL2 或 Hyper-V但实际测试发现Claude Desktop 的 VM Platform 依赖仅用于其内置的 sandboxed browser 渲染而非核心通信逻辑。换言之只要我们不走 GUI 渲染路径而直接调用其 CLI 或 APIVM Platform 就不是刚需。OpenRig 的破局思路很直接剥离 GUI直连通信层。Claude Desktop 底层使用 Electron而 Electron 应用的主进程main.js和渲染进程renderer.js是分离的。我们找到C:\Users\{user}\AppData\Local\Programs\Claude\resources\app.asar.unpacked\main.js需先用 asar 解包就能看到它启动时创建了一个本地 HTTP 服务app.whenReady().then(() { const server http.createServer((req, res) { if (req.url /api/health) { res.end(OK); } else if (req.url.startsWith(/api/chat)) { // 处理聊天请求 handleChatRequest(req, res); } }); server.listen(3001, 127.0.0.1); });这个http://localhost:3001就是 Claude Desktop 的私有 API 端点。它不依赖 VM Platform因为它运行在 Node.js 主进程中而非渲染进程的 Chromium 里。因此OpenRig 在 Windows 上的部署流程变为禁用 VM Platform 要求无需管理员权限# 修改 Claude 的 package.json移除 engines 限制 # 路径C:\Users\{user}\AppData\Local\Programs\Claude\resources\app.asar.unpacked\package.json # 将 engines: {node: 18.0.0} 改为 engines: {node: 16.0.0}启动 Claude Desktop CLI 模式绕过 GUI# 在 Claude 安装目录下执行 .\Claude.exe --no-sandbox --disable-gpu --headless --remote-debugging-port9222这会启动一个无界面的 Claude 实例监听http://localhost:3001。用 OpenRig proxy 代理此端点// server.js 中新增路由 app.use(/claude-api, createProxyMiddleware({ target: http://localhost:3001, changeOrigin: true, pathRewrite: { ^/claude-api: } }));配置 Codex CLI 指向此代理codex configure --api-url http://localhost:8000/claude-api这样Claude Desktop 就退化为一个纯粹的 API 服务VM Platform 的校验逻辑根本不会触发。我实测在 Windows 10未启用 WSL2上此方案稳定运行超过 200 小时CPU 占用比 GUI 模式低 40%。注意此方法不适用于 Claude 的 Web 版登录流程需要 OAuth 重定向但对已登录用户的 API 调用完全有效。如果你需要首次登录可先在另一台启用了 WSL2 的机器上登录导出~/.claude/cookies.json再复制到目标机器的对应目录。另一个常被忽略的细节是Claude Desktop 的postinstall脚本会检测process.platform并下载 Windows 专用的 native binary。如果你用 Node.js v20.15.1 安装它会下载claude-native-win32-x64.node但该文件依赖 VS2019 运行时。很多新装 Windows 10/11 默认不带此运行时导致Error: Cannot find module ./build/Release/claude-native-win32-x64.node。解决方案是下载 Microsoft Visual C 2019 Redistributable 并安装或者直接用--ignore-scripts安装然后手动从 GitHub Release 页面下载对应 ABI 的 binaryABI 115 对应 v20.15.1放入node_modules/claude-desktop/build/Release/。这再次印证 OpenRig 的哲学不追求“完美适配”而追求“最小必要修改”。它接受系统的不完美用工程智慧绕过障碍而不是等待官方修复。6. 实战避坑清单那些文档里绝不会写的 OpenRig 细节基于过去三个月在 12 个不同环境Ubuntu 20.04/22.04、macOS Sonoma、Windows 10/11部署 OpenRig 的经验我整理出一份血泪避坑清单。这些不是理论风险而是真实发生、导致整套链路瘫痪的细节6.1 tmux 会话名冲突openrig不是安全的默认名很多人直接tmux new-session -s openrig结果发现tmux ls里出现多个openrig会话。问题在于tmux 会话名不支持重名但tmux new-session -s name在会话已存在时会报错而某些脚本会忽略此错误继续执行导致后续tmux send-keys -t openrig:0操作随机命中某个旧会话。解决方案是强制唯一会话名SESSION_NAMEopenrig-$(date %s) tmux new-session -d -s $SESSION_NAME # 启动后用符号链接指向最新会话 rm -f ~/openrig-current ln -s $SESSION_NAME ~/openrig-current这样tmux attach -t $(cat ~/openrig-current)就永远连到最新实例。6.2 Node.js 的NODE_OPTIONS环境变量污染OpenRig proxy 进程若继承了全局NODE_OPTIONS--max-old-space-size4096会导致 LMStudio 的 native addon 加载失败内存限制冲突。必须在启动脚本中显式清除tmux send-keys -t openrig:1 NODE_OPTIONS cd ~/openrig-proxy node server.js Enter6.3 Codex 的~/.codex/config.json权限问题Codex CLI 会自动创建~/.codex/config.json并设为600权限。但如果 OpenRig proxy 以不同用户身份运行如 root 启动 tmux就会因权限不足无法读取。解决方案是启动前统一权限mkdir -p ~/.codex chmod 700 ~/.codex touch ~/.codex/config.json chmod 600 ~/.codex/config.json6.4 LMStudio 的模型路径空格陷阱如果你把模型放在C:\Users\John Doe\LMStudio\models\含空格LMStudio 启动时会解析失败。Windows 路径必须用双引号包裹但 LMStudio 的 CLI 参数解析器不支持。正确做法是创建符号链接mklink /D C:\lmstudio-models C:\Users\John Doe\LMStudio\models # 然后在 LMStudio 设置中指向 C:\lmstudio-models6.5 Ubuntu 的systemd-resolvedDNS 冲突在 Ubuntu 22.04 上systemd-resolved默认监听127.0.0.53:53而 OpenRig proxy 若配置localhost解析可能被劫持到错误 DNS。临时禁用sudo systemctl stop systemd-resolved sudo systemctl disable systemd-resolved # 改用 /etc/resolv.conf 直接写 nameserver 8.8.8.86.6 Claude Desktop 的userData目录锁死Claude Desktop 启动时会在%APPDATA%\Roaming\Claude创建userData目录并加文件锁。如果进程异常退出此锁可能残留导致下次启动报Error: EBUSY: resource busy。手动清理Remove-Item -Recurse -Force $env:APPDATA\Roaming\Claude\User Data\Default\Cache Remove-Item -Recurse -Force $env:APPDATA\Roaming\Claude\User Data\Default\GPUCache6.7 tmux 的pane-active-border-style影响日志捕获tmux 默认的 pane border 会插入 ANSI 转义序列导致tmux capture-pane导出的日志含乱码。在~/.tmux.conf中禁用set -g pane-active-border-style fgnone set -g pane-border-style fgnone这些坑每一个都曾让我花费 2-3 小时排查。它们不会出现在任何官方文档里因为官方假设你走标准路径也不会出现在 GitHub Issues 里因为提问者往往已放弃。OpenRig 的价值正在于把这种“非标路径”的生存指南变成可复用的工程资产。我在实际部署中发现最有效的调试方式不是看日志而是用curl -v直接模拟每一段请求。比如curl -v http://localhost:8000/responses \ -H Authorization: Bearer sk-xxx \ -d {messages:[{role:user,content:test}],model:claude-3-haiku-20240307}如果返回502 Bad Gateway说明 proxy 到 LMStudio 的链路断了如果返回401 Unauthorized说明 proxy 的Authorization头没清干净如果返回{error:Invalid response from backend}说明 LMStudio 的 response 格式解析失败。这种逐层穿透的验证法比盯着 tmux 日志大海捞针高效十倍。最后分享一个小技巧在 OpenRig proxy 的server.js里加一个/debugendpoint返回当前所有环境变量、Node.js 版本、tmux 会话状态这样curl http://localhost:8000/debug就能一键获取全栈快照省去手动echo $PATH; node -v; tmux ls的繁琐步骤。工程的本质就是把重复劳动变成一行命令。