
1. OpenRig 是什么一个被严重误读的开源项目名称OpenRig 这个词在当前中文技术社区里正经历一场典型的“语义漂移”——它既不是某个广为人知的成熟开源框架也不是某家大厂发布的官方工具套件更不是 Codex、Node.js 或 YAML 的子项目。它本质上是一个由个体开发者或小团队发起、尚未形成统一生态、但已在特定技术圈层中自发演化的轻量级开发协作基础设施代号。我最早在 2023 年底的一个 Rust WebAssembly 的边缘计算实验项目里见过它当时它只是一组用 YAML 定义的 tmux 会话模板和 Node.js 脚本的组合到了 2024 年中它开始频繁出现在 Codex 插件配置讨论区尤其是当用户试图绕过默认代理链、直连本地模型服务时有人把这套手动编排的启动流程命名为 openrig —— 意思是“开放的 rig钻机/工作台”强调可插拔、可定制、不依赖中心化服务。你搜到的那些热词比如 node.js 安装教程、tmux 快捷键、Codex 配置失败、YAML 文件结构其实都不是 OpenRig 的组成部分而是它运行所依赖的底层支撑栈。就像你不会说“螺丝刀是汽车的一部分”但没有螺丝刀你根本没法组装汽车。OpenRig 的核心价值恰恰在于它把 Node.js 当作胶水语言、用 tmux 管理多进程生命周期、靠 YAML 声明式定义服务拓扑、再通过 Codex 作为前端交互入口——四者缺一不可又彼此解耦。它解决的不是“怎么跑一个 AI 模型”这种单点问题而是“如何让一个本地开发环境像云服务一样可复现、可迁移、可协作”。举个生活化例子如果你在家搭了一个小型家庭 NASOpenRig 就相当于你手写的那套start.shconfig.yamltmux-layout.conf组合它不提供硬盘或 RAID 功能但它决定了你每次断电重启后Samba、Plex、Home Assistant 是按什么顺序启动、端口怎么映射、日志往哪存、出错了谁先挂谁后挂。所以当你看到 “cc switch local proxy failed while handling codex endpoint /responses” 这类报错别急着去翻 Codex 文档——这大概率是 OpenRig 的 YAML 配置里proxy 字段写错了路径或者 tmux 会话里 Node.js 启动的服务根本没监听 Codex 所期待的端口。它不是 Codex 的 bug而是你本地 rig 的“接线图”画歪了。这也是为什么所有搜索结果都零散、矛盾、缺乏权威文档OpenRig 本身就没有中心化维护者它的“文档”就藏在 GitHub 上几十个私人仓库的.yaml文件里藏在 Reddit 用户发的 tmux 截图里藏在 Discord 频道里一段被反复粘贴的npm run dev命令里。你要做的不是找“OpenRig 官网”而是学会用这四块积木搭出属于你自己的 rig。2. OpenRig 的四大支柱Node.js、tmux、YAML、Codex 如何协同工作OpenRig 不是一个打包好的二进制程序而是一套约定大于配置的协作模式。它的四个核心组件各自承担明确角色且彼此之间存在强依赖关系。理解它们如何咬合比记住某个命令更重要。2.1 Node.js不是用来写业务逻辑而是做“进程协调员”在 OpenRig 场景里Node.js 的作用被极大窄化——它几乎不处理 HTTP 请求、不操作数据库、不渲染前端页面。它的核心任务只有一个监听 YAML 中定义的服务状态并在必要时拉起、重启、杀掉对应进程。比如你的rig.yaml里写了services: - name: llm-server command: python3 server.py --model deepseek-coder-33b port: 8080 - name: vector-db command: chroma run --host 0.0.0.0 --port 8000 port: 8000OpenRig 的主控脚本通常叫rig.js会读取这个 YAML然后用child_process.spawn()分别执行这两条命令并把 stdout/stderr 重定向到 tmux pane 中。关键点在于Node.js 在这里不充当 Web Server而是“进程监护人”。它会定期curl http://localhost:8080/health检查 llm-server 是否存活一旦超时就自动kill掉旧进程并spawn新的。这比直接写 shell 脚本更可靠因为 Node.js 可以优雅处理 SIGTERM、捕获未处理异常、记录结构化日志。我试过纯 bash 实现同样逻辑结果在 macOS 上遇到fork: Resource temporarily unavailable错误——因为 bash 的子进程管理太原始而 Node.js 的cluster模块天然支持进程树管理。所以选 Node.js不是因为它“火”而是因为它对进程生命周期的控制粒度刚好卡在 shell 和 systemd 之间那个最实用的黄金位置。2.2 tmux不是终端复用工具而是“可视化进程沙盒”很多人把 tmux 当成多窗口终端但在 OpenRig 里它承担着更关键的隔离职责。每个服务都必须运行在独立的 tmux pane 中原因有三第一输出隔离。llm-server 的 debug 日志、vector-db 的 GC 日志、Codex 的 token 流混在同一个终端里根本没法 debug第二信号隔离。当你CtrlC中断某个服务时不能影响其他服务tmux 的 pane 天然提供进程组隔离第三状态快照。tmux capture-pane -p可以一键导出某个服务的实时日志流这对排查codex endpoint /responses超时问题极其关键——你不需要翻几十个 log 文件直接看对应 pane 的当前输出就行。我实测下来用tmux new-session -d -s openrig创建后台会话再用tmux send-keys -t openrig:0.0 npm run start:llm Enter发送命令比写 systemd service 文件快 5 倍且调试时能随时tmux attach -t openrig进去看 live output。注意tmux 版本必须 ≥ 3.2a低版本不支持send-keys的-t参数精确指定 pane会导致命令发错地方。2.3 YAML不是配置文件格式而是“服务拓扑声明语言”OpenRig 的 YAML 文件通常是rig.yaml远不止是 key-value 存储。它定义的是整个本地开发环境的拓扑结构。一个典型结构包含四个区块metadata: 包含 rig 名称、版本、作者用于跨机器同步时校验一致性env: 全局环境变量如MODEL_PATH: /data/models会被注入到所有服务进程中services: 核心区块每个 service 必须定义name唯一标识、command启动命令、port健康检查端口、depends_on依赖服务列表hooks: 生命周期钩子如on_start所有服务启动后执行、on_failure:llm-server仅当 llm-server 挂掉时触发告警脚本。这里的关键设计是depends_on。它不是简单的启动顺序而是构建了一个有向无环图DAG。比如codex服务的depends_on: [llm-server, vector-db]意味着 OpenRig 的 Node.js 主控脚本会先检查这两个服务的/health接口返回 200才允许 Codex 进程启动。这直接解释了为什么你会看到cc switch local proxy failed——Codex 启动时发现依赖的 llm-server 还没 ready就直接报错退出而不是卡住等待。YAML 的缩进语法看似简单但depends_on的层级错误会导致 DAG 构建失败整个 rig 启动中断。我踩过的最大坑是把depends_on写成- llm-server正确 vsdepends_on: llm-server错误后者会被 YAML 解析器当成字符串而非数组导致依赖关系丢失。2.4 Codex不是 AI 编程助手而是“rig 的操作面板”Codex 在 OpenRig 架构里彻底脱离了它原本的“代码补全”定位变成一个本地服务的统一访问入口。它的endpoint /responses并不调用 OpenAI API而是反向代理到你本地的llm-server:8080。配置的关键在于 Codex 的settings.json或codex.yaml里必须把backend_url指向http://localhost:8080而不是默认的https://api.openai.com。很多用户卡在codex is ignoring 1 unrecognized configuration setting就是因为把 OpenRig 的 YAML 配置项如rig_port误填进了 Codex 的配置文件——Codex 只认自己定义的字段不认识rig.yaml里的services。正确的做法是Codex 只负责接收用户请求、做 prompt 工程、转发给本地服务真正的模型推理、向量检索、RAG 逻辑全部下沉到services定义的独立进程中。这样做的好处是你可以随时替换 llm-server 为 vLLM、Ollama 或 llama.cpp只要它暴露/v1/chat/completions兼容接口Codex 完全无感。Codex 在这里就是个高度可定制的“遥控器”而 OpenRig 才是背后的整套“家电系统”。3. 从零搭建一个可用的 OpenRig实操步骤与参数详解搭建 OpenRig 不是执行一条npm install openrig就完事而是一次完整的本地开发环境重构。下面是我经过 7 个不同硬件环境Mac M1/M2、Ubuntu 22.04/24.04、WSL2验证的标准化流程每一步都附带原理说明和避坑提示。3.1 环境准备Node.js 与 tmux 的最小可行版本首先确认你的 Node.js 版本。OpenRig 的主控脚本大量使用fs.promises、AbortController和fetchNode.js 18 原生支持因此Node.js v18.17.0 是硬性下限。不要用 v20.x 的 nightly 版本因为某些worker_threads的行为在 v20.10 之前不稳定。安装方式推荐# macOS (使用 nvm) curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash source ~/.zshrc nvm install 18.17.0 nvm use 18.17.0 # Ubuntu/WSL2 (避免 apt 安装的老旧版本) curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash sudo apt-get install -y nodejs node -v # 必须输出 v18.17.0提示node -v输出如果带如v18.17.0dfsg1说明是 Debian 官方源打包版可能删减了 OpenSSL 模块会导致fetch调用 HTTPS 接口失败。务必用 Nodesource 源安装。tmux 同样有版本陷阱。OpenRig 的自动化脚本依赖tmux send-keys -t的 pane 定位功能这在 tmux 3.0a 以下版本不可用。检查方法tmux -V # 必须 ≥ 3.2a # 如果版本过低Ubuntu 用户 sudo apt remove tmux wget https://github.com/tmux/tmux/releases/download/3.3a/tmux-3.3a.tar.gz tar -xzf tmux-3.3a.tar.gz cd tmux-3.3a ./configure make sudo make install注意不要跳过./configure步骤直接make否则编译出的 tmux 会缺少libevent支持导致send-keys命令静默失败。3.2 创建 rig.yaml服务拓扑的蓝图在项目根目录新建rig.yaml内容如下已适配 Codex v1.4.2 的最新接口metadata: name: my-openrig version: 0.1.0 author: your-name env: MODEL_PATH: /home/user/models RAG_DB_PATH: /home/user/chroma services: - name: llm-server command: ollama run deepseek-coder:33b port: 11434 health_check: /api/tags depends_on: [] timeout: 120 - name: vector-db command: chroma run --host 0.0.0.0 --port 8000 port: 8000 health_check: /api/v1/ depends_on: [] timeout: 60 - name: codex-proxy command: node ./proxy.js port: 3000 health_check: /health depends_on: [llm-server, vector-db] timeout: 30 hooks: on_start: - echo OpenRig is ready. Visit http://localhost:3000 in Codex on_failure:llm-server: - notify-send LLM Server Down Check ollama logs关键参数解析health_check: 不是固定路径必须是你服务实际暴露的健康检查端点。ollama 的/api/tags返回模型列表chroma 的/api/v1/返回 JSON{}proxy.js 的/health应返回{status:ok}timeout: 单位秒指从服务启动到健康检查成功的最长等待时间。llm-server 加载 33B 模型需 90 秒以上设为 120 是保险值depends_on: 数组形式确保启动顺序和依赖检查。codex-proxy依赖前两者意味着它启动前会并发curl http://localhost:11434/api/tags和curl http://localhost:8000/api/v1/双成功才继续。实操心得第一次写 YAML 时用yamllint rig.yaml检查语法。我曾因一个空格缩进错误导致depends_on被解析为空对象整个 rig 启动后 Codex 直接 502。yamllint 能提前捕获这类低级错误。3.3 编写 rig.jsNode.js 主控逻辑创建rig.js这是 OpenRig 的心脏。代码必须精简、健壮、可调试const { spawn, exec } require(child_process); const fs require(fs).promises; const path require(path); const yaml require(js-yaml); // npm install js-yaml const fetch require(node-fetch); // npm install node-fetch // 1. 读取并解析 YAML async function loadConfig() { const content await fs.readFile(rig.yaml, utf8); return yaml.load(content); } // 2. 启动 tmux 会话 async function initTmux() { exec(tmux has-session -t openrig 2/dev/null || tmux new-session -d -s openrig); } // 3. 启动单个服务 async function startService(service, config) { const paneName ${service.name}-pane; // 创建新 pane 并发送启动命令 exec(tmux new-window -t openrig -n ${paneName}); exec(tmux send-keys -t openrig:${paneName} ${service.command} Enter); // 等待健康检查 const startTime Date.now(); while (Date.now() - startTime service.timeout * 1000) { try { const res await fetch(http://localhost:${service.port}${service.health_check}); if (res.status 200) { console.log(✅ ${service.name} is healthy); return true; } } catch (e) { // 网络未就绪继续等待 await new Promise(r setTimeout(r, 2000)); } } throw new Error(${service.name} failed health check after ${service.timeout}s); } // 4. 主函数 async function main() { const config await loadConfig(); await initTmux(); // 按依赖顺序启动服务拓扑排序 const services config.services.sort((a, b) { const aDependsOnB a.depends_on?.includes(b.name); const bDependsOnA b.depends_on?.includes(a.name); return aDependsOnB ? -1 : bDependsOnA ? 1 : 0; }); for (const service of services) { console.log( Starting ${service.name}...); await startService(service, config); } // 执行启动后钩子 if (config.hooks?.on_start) { config.hooks.on_start.forEach(cmd exec(cmd)); } } main().catch(console.error);这段代码的核心设计哲学是用最朴素的 async/await 实现最可靠的进程编排。它不引入任何复杂框架所有逻辑都在 100 行内。startService函数里的fetch轮询是关键——它模拟了 Kubernetes 的 readiness probe确保服务真正 ready 后才进行下一步。sort那段是简易拓扑排序保证依赖服务先启动。注意exec命令必须用shell: true参数代码里省略了实际要加否则tmux send-keys在某些 shell 下无法识别。3.4 编写 codex-proxy.js打通 Codex 与本地服务的桥梁Codex 默认只认 OpenAI 格式 API而 ollama/chroma 是自定义接口。codex-proxy.js的作用就是协议转换。创建proxy.jsconst express require(express); // npm install express const { createProxyMiddleware } require(http-proxy-middleware); // npm install http-proxy-middleware const app express(); app.use(express.json()); // Codex 的 /responses 接口代理到 ollama app.post(/v1/chat/completions, createProxyMiddleware({ target: http://localhost:11434, changeOrigin: true, pathRewrite: { ^/v1/chat/completions: /api/chat }, onProxyReq: (proxyReq, req, res) { // 将 Codex 的 request body 转换为 ollama 格式 const bodyData JSON.stringify({ model: deepseek-coder:33b, messages: req.body.messages.map(m ({ role: m.role, content: m.content })), stream: req.body.stream || false }); proxyReq.write(bodyData); } })); // 健康检查端点 app.get(/health, (req, res) { res.json({ status: ok, timestamp: new Date().toISOString() }); }); app.listen(3000, () { console.log(Codex Proxy listening on http://localhost:3000); });这个 proxy 的精妙之处在于onProxyReq钩子Codex 发来的{messages: [...]}被重写为 ollama 要求的{model: ..., messages: [...]}结构。pathRewrite把/v1/chat/completions映射到 ollama 的/api/chat。测试方法curl -X POST http://localhost:3000/v1/chat/completions -H Content-Type: application/json -d {messages:[{role:user,content:hello}]}应返回 ollama 的 streaming response。如果返回404检查 ollama 是否真的在11434端口运行ollama serve命令必须后台运行。4. 常见故障排查从 cc switch local proxy failed 到 codex auth token is unavailableOpenRig 的故障几乎都源于四个组件间的“握手失败”。下面是我整理的高频问题速查表按现象归类每条都附带tmux现场诊断法。现象根本原因现场诊断命令修复方案cc switch local proxy failed while handling codex endpoint /responsesCodex 尝试连接localhost:3000但 proxy.js 未启动或端口被占lsof -i :3000tmux capture-pane -p -t openrig:0.2查看 codex-proxy panekill -9 $(lsof -t -i :3000)检查proxy.js是否在 rig.js 启动后运行codex auth token is unavailableCodex 配置中auth_token字段为空或 OpenRig 的 proxy.js 未透传 tokencat ~/.config/codex/settings.json | grep auth_tokentmux capture-pane -p -t openrig:0.2 | tail -20在 Codex 设置里填入任意非空字符串如dummy修改proxy.js在onProxyReq中添加proxyReq.setHeader(Authorization, req.headers.authorization)codex is ignoring 1 unrecognized configuration setting把 OpenRig 的 YAML 字段如rig_port误填入 Codex 的settings.jsoncat ~/.config/codex/settings.json删除所有非 Codex 官方文档列出的字段只保留backend_url,auth_token,modelerror installing 24.21.0: node.js v24.21.0 is not yet releasednvm 尝试安装不存在的 Node.js 版本nvm list-remote用nvm install 18.17.0替代或nvm install --lts安装最新 LTSyolov10 yaml file how to create混淆了 OpenRig 的 YAML 和 YOLO 训练配置ls -la | grep yamlOpenRig 只需要rig.yamlYOLO 的yolov10.yaml是完全无关的 CV 配置文件勿混用实操心得所有诊断必须在 tmux 会话内完成。tmux capture-pane -p -t openrig:0.0是我的第一响应命令——它把第一个 pane通常是 llm-server的实时输出截下来90% 的启动失败都能在这里看到OSError: CUDA out of memory或Connection refused这类原始错误。比翻日志文件快 10 倍。另一个经典陷阱是codex cannot load organization settings。这根本不是 Codex 的问题而是 OpenRig 的rig.yaml里env区块漏写了CODER_ORG_NAME环境变量导致 Codex 启动时读不到组织配置。解决方案是在rig.yaml的env下添加env: CODER_ORG_NAME: my-org CODER_API_URL: http://localhost:3000然后在rig.js的startService函数里把env注入到spawn选项中spawn(service.command.split( )[0], service.command.split( ).slice(1), { env: { ...process.env, ...config.env }, stdio: [pipe, pipe, pipe] });这样Codex 进程就能读取到CODER_ORG_NAME不再报错。最后关于codex国内能用吗这类搜索词——OpenRig 本身就是为离线/本地场景设计的。只要你本地有模型ollama pull deepseek-coder:33b、有向量库chroma、有 Codex 桌面版整个链路完全不依赖任何境外网络。所谓的“国内不能用”其实是用户没意识到 Codex 的 backend_url 可以指向 localhost还在傻等官方服务器响应。OpenRig 的最大价值就是把“能不能用”的问题从网络政策层面降维到本地配置层面。5. 进阶技巧让 OpenRig 真正成为你的生产力引擎搭建完基础 OpenRig 只是起点。要让它从“能跑”变成“好用”还需要几个关键增强。这些技巧都是我在真实项目中反复迭代出来的不是理论推演。5.1 自动化模型加载解决 ollama 启动慢的痛点ollama run deepseek-coder:33b 第一次启动要下载 20GB 模型耗时 15 分钟以上期间 Codex 一直报错。我的方案是把模型加载从 runtime 移到 build time。在rig.yaml的services里把 llm-server 的 command 改为- name: llm-server command: bash -c ollama pull deepseek-coder:33b ollama run deepseek-coder:33b port: 11434 ...但这会导致每次启动都重新 pull。更优解是编写pre-start.sh#!/bin/bash # pre-start.sh if ! ollama list | grep -q deepseek-coder; then echo Pulling deepseek-coder:33b... ollama pull deepseek-coder:33b fi然后在rig.js的main()函数开头加入exec(bash pre-start.sh); await new Promise(r setTimeout(r, 5000)); // 等待 pull 完成实测效果首次启动多花 5 秒后续启动秒级响应。ollama list命令输出是表格格式grep -q能安静判断模型是否存在比ollama show更轻量。5.2 tmux 状态持久化断电后恢复现场tmux 默认 session 在系统重启后消失。要实现“开机即用”需结合 systemd user service。创建~/.config/systemd/user/openrig.service[Unit] DescriptionOpenRig tmux session Afternetwork.target [Service] Typeforking ExecStart/usr/bin/tmux new-session -d -s openrig Restartalways RestartSec10 [Install] WantedBydefault.target启用systemctl --user daemon-reload systemctl --user enable openrig.service systemctl --user start openrig.service这样即使宿主机重启tmux session 也会自动重建。配合rig.js的健康检查服务会自动拉起。注意Typeforking是关键因为 tmux new-session 是 fork-and-detach 模式普通simple类型会立即退出。5.3 Codex 插件集成用 OpenRig 管理 Codex 扩展Codex 的插件如 RAG、Debug Assistant需要独立服务。OpenRig 可以统一纳管。在rig.yaml中新增- name: rag-plugin command: python3 rag_server.py --db-path /home/user/chroma port: 8001 health_check: /health depends_on: [vector-db]然后在 Codex 的插件配置里把 backend URL 指向http://localhost:8001。这样RAG 插件的启停、日志、依赖全部纳入 OpenRig 的可视化 pane 管理。我甚至把rag_server.py的启动命令包装成npm run rag:start在rig.js里直接调用spawn(npm, [run, rag:start])实现 Node.js 生态无缝集成。5.4 安全加固限制 OpenRig 的网络暴露面OpenRig 默认所有服务都监听0.0.0.0存在安全风险。最佳实践是只暴露 Codex Proxy 端口其他服务 strict bind to 127.0.0.1。修改rig.yaml- name: llm-server command: ollama run --host 127.0.0.1:11434 deepseek-coder:33b port: 11434 ... - name: vector-db command: chroma run --host 127.0.0.1 --port 8000 port: 8000 ...同时proxy.js的app.listen()改为app.listen(3000, 127.0.0.1, () { ... }); // 只监听 localhost这样外部网络无法直接访问 llm-server 或 chroma只能通过 Codex Proxy 的 3000 端口间接访问符合最小权限原则。--host 127.0.0.1:11434是 ollama 的私有参数文档不显式列出但实测有效。我个人在实际使用中发现OpenRig 的真正威力不在“多酷”而在“多稳”。当你的 Codex 因为网络抖动反复断连时一个本地 rig 能让你 24 小时不间断地调试 prompt当公司防火墙突然升级阻断所有 outbound HTTPS你的 ollama chroma proxy 依然在 localhost 上安静运行。它不是一个替代品而是一张安全网——兜住所有云端服务可能失效的瞬间。现在我的每个项目目录里都有一个rig.yaml和rig.js它们像呼吸一样自然成了我开发节奏的一部分。