☰
openrig:声明式配置管理工具,一条命令搞定开发机环境
2026/10/15 13:11:11 网站建设 项目流程

我前天又一次启动了 openrig,把一台新笔记本从刚拆封的状态变成了能直接写代码的开发 rig,全程唯一做的事是运行一条命令然后等它结束。这个工具最初只是我自己零散的安装脚本仓库,后来慢慢长成了现在这套可以描述整台机器的开源小框架。这篇内容就是 openrig 的设计思路,以及我在真实环境里反复使用之后的经验记录,适合那些受够了每次换电脑都要重装环境的人。

1. 从"手动装两天"到"一条命令装完":openrig 想解决的问题

先说清楚 openrig 到底在解决什么。大多数开发者维护自己电脑的方式,其实是三套东西拼起来的:一份躺在 GitHub 上的 dotfiles 仓库、几段不知道什么时候写的安装脚本、以及大量只存在于脑子里的手工操作。平时没出问题还好,一旦遇到换电脑、电脑崩溃、或者要给第二台机器同步环境,这三套东西就会各自为战。

我第一次尝到教训是在一次临时出差中。公司给了我一台全新的工作机,我原本预计半小时装完环境,结果光是把 zsh、vim、终端字体、常用命令行工具逐个装好就花了将近一天。等所有软件就位,才发现.ssh/config漏掉了,某个调试工具装的是旧版本,和本地表现不一致。这种状态在之后的几个月里不断以"这台机器怎么和那台机器不一样"的形式冒出来。手动操作最大的问题就在这里:不可审计、不可复现、不可验证。

openrig 的基本思路其实很朴素:把一台电脑的"最终状态"用一份声明式文件描述出来,然后由工具负责从当前状态收敛到目标状态。它不像配置管理工具那样面向几百台服务器,也不像单纯的 dotfiles 管理器只管配置文件,而是站在两者中间,专门服务个人开发机和少数几台工作机。用我自己的话说,它管的东西就是"一台开发机该有的样子"。

这个取舍在项目早期非常重要。很多人一听"配置管理",下意识会想到一套庞大体系,但 openrig 从一开始就限定范围:只管一台机器上从操作系统到用户目录里的事。范围小了,工具就可以做得很轻,安装和上手都很快,也不必为了支持复杂网络环境引入一堆额外概念。它适合的人是:经常换机器、有多台电脑需要保持一致、需要给同事或同学快速交付一个可用开发环境的人。

1.1 手动配置机器为什么不可靠

手动配置看起来最直接,实际上却最容易埋雷。一个典型的安装过程包括:上网搜索安装命令、复制粘贴、等编译、改配置文件、重启终端、发现环境变量没生效、再去翻文档。这中间任何一步都没有留下"这台机器此时是什么状态"的记录。

更隐蔽的问题是"约等于"。装某个软件的时候选了默认版本,另一台机器上却装了最新版;某次调试时给配置文件加了一行临时参数,后来忘了删,这行参数就一直躺在你的 shell 启动文件里。时间越长,机器之间的差异越大,最后你甚至说不清"我平时用的那套环境"到底是什么组成的。

1.2 openrig 做了一个什么样的取舍

openrig 选择"描述最终状态"而不是"记录操作步骤"。安装脚本记录的是过程,声明式配置记录的是结果。这个区别在遇到环境漂移的时候非常明显:脚本跑一遍如果中间报错,你很难说清已经改了什么;而声明式配置每次都从当前状态出发,差的补上、多了的可以清理、多余的保持不动,整个收敛过程是有规则的。

代价是有的。声明式的方式要求你先想清楚要什么,不能像手动那样"装一步看一步"。但对大多数成熟开发者来说,自己电脑上需要什么其实是清楚的,写清楚并不困难。

1.3 安装 openrig 需要准备什么

openrig 目前主要支持 macOS 和常见 Linux 发行版,Windows 下建议配合 WSL 使用。安装它本身只需要一个可执行文件和一份配置,不需要数据库也不需要常驻服务。唯一的前置要求是机器上有 Git,因为配置文件默认从 Git 仓库拉取,这也顺便解决了多台机器之间同步的问题。

2. rigfile 的语法与模块设计:整台电脑如何被描述

核心配置文件叫rigfile.yaml,放在你的配置仓库根目录。它的作用是把整台机器拆成几类可管理的东西:系统设置、软件包、dotfiles、服务、以及一些需要在特定时机执行的 shell 命令。下面是一份我在主力机上实际在用的简化版本。

project: devbox version: 2 system: hostname: work-720 timezone: Asia/Shanghai locale: en_US.UTF-8 packages: brew: present: - fish - neovim - git - jq - ripgrep absent: - vim apt: present: - build-essential - htop dotfiles: - source: dotfiles/.gitconfig target: ~/.gitconfig - source: dotfiles/.zshrc target: ~/.zshrc - source: dotfiles/init.fish target: ~/.config/fish/conf.d/init.fish shell: default: /usr/local/bin/fish services: ensure_running: - name: docker ensure_enabled: - name: sshd hooks: post_apply: - command: "git config --global core.editor nvim"

2.1 一份最简单的 rigfile

如果你第一次用,一份只装了 Git 和 Neovim、再把.gitconfig和.zshrc放进去的配置就足够了。先跑通最小闭环,再逐步加模块。千万别一开始就想着把所有细节都写进去,否则第一天就会在配置语法上消耗大量耐心。

project: quickstart version: 1 packages: brew: present: - git - neovim dotfiles: - source: dotfiles/.gitconfig target: ~/.gitconfig

2.2 几类模块和它们各自的边界

packages是最常用的模块。它不做版本管理,只关心"装没装"。装了就不再动,没装就装最新稳定版。如果你需要对某个软件锁版本,可以在后面加version:字段,openrig 会调用对应包管理器处理。absent列表则用来清理机器上不需要的软件,这是手动维护时很少有人会做的事。

dotfiles模块负责把配置文件放到正确位置。默认策略是符号链接,这样你在仓库里改了配置,目标机器会立即同步。如果目标位置已经存在文件,openrig 会先对比内容:一致就跳过,不一致就备份成.bak-时间戳再创建链接。

system模块里我一般只放 hostname、时区这类真正不希望漂移的设置。services模块管理服务的运行状态和开机自启,它本质上是封装了systemctl和 macOS 的launchctl操作。hooks模块用来兜底那些不好抽象的场景,例如某个软件的初始化命令,所有 hook 都会在 plan 阶段显示出来,不会悄悄执行。

2.3 为什么不用纯 shell 脚本做这件事

项目一开始确实是 shell 脚本,但写到后面有几个问题绕不过去。第一是幂等,安装脚本通常默认"目标机器是干净的",一旦重跑就报错或做重复操作;第二是无法预览,脚本执行前你基本不知道它会改什么;第三是没有状态记录,脚本跑完,你拿不到一份"这次到底改了什么"的清单。

openrig 把这些问题从机制上解决:每个模块都实现了"检测当前状态"和"执行变更"两个动作,plan 命令把所有变更列给你看,apply 命令再真正动手,每步结果写进本地状态文件。这个设计很像基础设施领域常说的收敛模型,但放在个人电脑上,其实一样有效。

3. 在裸系统上把 openrig 跑起来:完整流程与输出解读

假设你刚拿到一台新电脑,系统是干净的。使用 openrig 的完整流程可以分成三件事:安装 openrig 本身、拉配置、执行 plan 和 apply。

3.1 安装 openrig 的两种方式

最简单的方式是用官方安装脚本:

curl -fsSL https://openrig.dev/install.sh | sh

如果你对从网络拉脚本这件事比较谨慎,也可以下载对应平台的二进制包,解压后放到PATH里就行。安装完成后,openrig version能正常输出版本号,就说明本体没问题了。

之后把配置文件仓库克隆到本地,比如放在~/rigs目录,然后告诉 openrig 使用哪个 rigfile:

openrig init ~/rigs/devbox/ # 生成一份 rigfile 模板并关联本地状态目录 openrig plan

3.2 plan 到底做了什么

plan 阶段不会修改系统,它逐模块扫描当前机器状态,和 rigfile 里声明的目标状态比对。输出类似这样:

[plan] packages: 3 to install, 1 to remove [plan] dotfiles: .zshrc -> will link (target exists, content differs, backup planned) [plan] services: docker will be started [plan] hooks: 1 command will run after apply

这份清单就是整个操作的"施工图"。我自己的习惯是每次 plan 完都认真扫一眼,尤其注意有没有自己预期之外的变更。执行前多花两分钟,可以避免很多上手后才发现的问题。

确认后运行:

openrig apply

apply 会按顺序处理模块:先系统设置、再包管理、然后是 dotfiles、服务,最后是 hooks。每完成一项会打印状态标记,[ok]表示已满足、[changed]表示做了变更、[failed]表示出错。全部完成后,openrig 会把整次执行的变更写入.openrig/state.yaml,方便事后回溯。

3.3 幂等性到底是怎么做的

openrig 宣称"可以重复执行",这不是靠运气,而是每个模块都严格遵守同一套模式。拿 packages 举例,它先调用包管理器查询某个包是否已安装,已安装就直接跳过;dotfiles 则先比较符号链接指向和文件内容;services 先查状态再决定是否执行启动命令。

所以同一份配置在一台机器上跑十遍,结果是一样的:第一遍做了所有变更,后面九遍几乎都只是扫描和跳过。这个特性对日常维护很有价值——你不再需要担心"我是不是多跑了某条初始化命令"。需要临时改环境时,可以直接改 rigfile 再执行一次 apply,效果类似给系统做了一次定向"对齐"。

4. 实战中踩过的坑和最终解决方式

工具写了不等于好用,真正让它变可靠的是反复踩坑和修补。下面这几个问题是我在实际使用中遇到的,每个都值得单独说明。

4.1 非交互 shell 导致的环境变量不生效

第一次给 macOS 机器配置 Fish shell 时,apply 跑完一切正常,但打开终端却发现fish没有成为默认 shell,$PATH也和我预期的不一致。排查后发现两个原因:chsh -s在非交互式 shell 里不会刷新当前会话的缓存;$PATH的设置在.zshrc里,而 Fish 根本不会读它。

现在的做法是:shell 模块里显示声明默认 shell,同时把环境变量统一放到~/.config/fish/conf.d/和~/.zshenv这种跨会话的地方,而不是写进某个交互式配置里。这个原则对所有 shell 都适用:启动时要用的东西放对位置,交互时才用的东西才写在 rc 文件里。

4.2 dotfiles 目标位置冲突

有台机器上~/.gitconfig是我手工创建的,内容带了一些本机特殊配置。第一次 apply 时 openrig 做了备份然后创建链接,备份文件倒是生成了,但我还是花了一点时间才找到。后来我在 dotfiles 模块里增加了conflict_strategy: backup选项,并规定所有机器的 dotfiles 仓库保持单一来源,本地特殊配置一律用git config --global的追加方式放到~/.gitconfig.local。这样既保留了单机差异,又不会污染公共配置。

4.3 包管理器的"有版本号但内容不对"

包管理器只保证"某个软件装了",不保证"它装的是你想要的版本"。有次我配置里的ripgrep在旧系统源里是 11.x,公司服务器上解析出来的却是 13.x。问题不严重,但"两台机器软件版本不一致"正是最初想解决的问题。

所以 packages 模块增加了一个可选字段version:。声明了版本号的包,apply 时会用包管理器查询当前版本,不一致就升级或降级。没有特别要求的话,我还是建议让包管理器保持最新版,但工作里涉及工具链一致性的场景,这个字段能省很多事。

4.4 需要 sudo 权限的操作卡在交互输入上

安装系统级包、修改 hostname、控制服务,这些操作大多需要 sudo。交互式运行没问题,但不交互执行时整条流程就会卡在密码输入上。我最终在 apply 命令里增加了--sudo模式,它会提前一次提示输入密码,只在需要时使用,不会缓存凭证,流程结束时立刻清空。这个细节改造完之后,整个 apply 过程才算真正能"无人值守"跑完。

5. openrig 和 Ansible、Chezmoi 的关系:边界在哪里

每次听到"配置管理"这个词,很多人第一反应都是 Ansible;一提到 dotfiles,又会想起 Chezmoi、GNU Stow 这些工具。openrig 不是要替代它们,而是站在一个不同层级上。用一句话概括:Ansible 管服务器群,Chezmoi 管配置文件,openrig 管个人开发机整机状态。

工具主要场景学习成本状态管理典型用户
Ansible批量服务器配置高面向远程主机,依赖 inventory运维工程师
Chezmoi管理 dotfiles 多处同步中仅配置文件,不涉及包和服务进阶开发者
GNU Stow符号链接管理 dotfiles低无状态,仅做链接熟悉 Unix 的用户
openrig个人开发机整机装配低有状态,覆盖包/配置/服务多机器开发者

Ansible 的优势在于批量、并行、面向无状态的远程服务器,但个人电脑的场景其实不太一样:机器有桌面环境、有用户会话、有各种 GUI 软件,很多状态 Ansible 并不理解。Chezmoi 在处理 dotfiles 这一件事上做得非常细,比如模板化和多机器差异,这是 openrig 不会去覆盖的功能。openrig 保留了这个能力圈:dotfiles 就是简单链接或复制,不做模板渲染。

如果你已经用 Chezmoi 管好了配置文件,也不一定非要迁移到 openrig。可以让 openrig 只负责 packages 和服务,dotfiles 部分继续沿用 Chezmoi。项目设计上允许在 rigfile 里把dotfiles模块整个禁用,两边互不干扰。

6. 用了两个月之后:日志、回滚和一些个人体会

openrig 整个设计里,我最满意的是状态文件和日志。以前手动装环境的记忆全靠脑内,事后想不起来"那天为什么装了那个包"。现在每个变更都有迹可循:rigfile 是目标状态,.openrig/state.yaml是当前状态,apply.log是变更历史。有一次我发现自己机器上多了个不明服务,查日志发现是两周前一个测试用的 rigfile 改的,顺手就清理掉了。

仓库管理上,我的建议是把整个 rigs 目录做成一个 Git 仓库,rigfile.yaml和dotfiles/放在一起。这样一来,任何一次对环境的修改都成了一次 commit,提交信息就是"今天我装了 X、删了 Y",机器的变化历史变得和代码仓库一样清晰。你也可以给仓库打 tag,比如stable、dev,不同机器可以锁定在不同版本上。

另外一个小技巧:把 openrig 的仓库提交到 GitHub 后,新机器只需要一条命令克隆,外加openrig apply,就能恢复 90% 的开发环境。剩下 10% 是各类 GUI 应用的登录态和授权信息,这部分我目前还是手动处理。真要自动化,可以后续加一个"打开这些应用的登录页"的 hook,但隐私和安全性我还没想好怎么平衡。

openrig 现在还在持续演进,下一个想做的方向是多机器之间的配置差异管理。比如工作机器和个人机器的 dotfiles 共享,但某些系统设置不同。这个问题在设计层面还没完全想清楚,可能会借鉴一些同类工具的思路。不过就目前的使用体验来说,从"手动装两天"到"一条命令装完"这个转变,已经足够让我觉得当初把这个工具认真做完是值得的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询