引言
macOS 没有 NixOS 那样的「配置即系统」,但 nix-darwin 补上了这块拼图:用一份声明式配置管理系统偏好(defaults)、launchd 服务、Homebrew 包、用户与字体——darwin-rebuild switch 一次,机器进入声明的状态。
它和 NixOS 的最大差异在于底层不是 Nix 生成的世界:macOS 的系统组件由 Apple 掌控,nix-darwin 只能「调用系统工具去设置」,因此有些操作不可逆、有些需要重启。理解这条边界,是用好 nix-darwin 的关键。
目录
- 1. nix-darwin 是什么
- 2. 安装与 flake 集成
- 3. darwin 模块与系统配置
- 4. Homebrew 集成
- 5. defaults:系统偏好即代码
- 6. launchd 服务管理
- 7. 与 Home Manager 协同
- 8. 用户、字体与 shell
- 9. 常见坑与升级
- 10. 速查表与一句话记忆
- 延伸阅读
1. nix-darwin 是什么
1.1 定位:macOS 版的「系统配置层」
nix-darwin 提供一套与 NixOS 模块系统同源的 darwin 模块,让你用 configuration.nix 风格的表达式声明 macOS 的系统状态:
| 管理对象 | nix-darwin 手段 |
|---|---|
| 系统偏好 | system.defaults.* |
| 服务与定时任务 | launchd.* |
| Homebrew 包 | homebrew.* |
| 用户与组 | users.* |
| 系统级包 | environment.systemPackages |
| 键盘/网络/字体 | system.keyboard / networking / fonts |
1.2 与 NixOS 的根本差异
# NixOS:配置 → 生成整个系统(内核、init、服务全由 Nix 管)
# nix-darwin:配置 → 调用 macOS 原生工具去「设置」(defaults write / launchctl)
因此 nix-darwin 不是不可变系统:它管理的只是「设置项」,/System 与 /Applications 仍由 Apple 掌控,nix-darwin 只在 store 里放自己的包并用符号链接暴露。它适合「有大量偏好要跨机同步、用 flake 管开发环境、已在用 Home Manager 想再往上管系统层」的场景。
记忆:nix-darwin 是 macOS 的声明式系统配置层——用同源的 darwin 模块管理 defaults/launchd/Homebrew/用户;它不是不可变系统(macOS 由 Apple 掌控),只是「调用原生工具去设置」,理解这条边界是用好它的关键。
2. 安装与 flake 集成
2.1 前置:先装 Nix
# 推荐 Determinate Nix 或官方多用户安装,并启用 flakes
sh <(curl -L https://nixos.org/nix/install)
echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf
2.2 flake 里引入 nix-darwin
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
nix-darwin = {
url = "github:LnL7/nix-darwin";
inputs.nixpkgs.follows = "nixpkgs"; # 关键:避免两份 nixpkgs
};
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, nix-darwin, home-manager }: {
darwinConfigurations."my-mac" = nix-darwin.lib.darwinSystem {
modules = [ ./configuration.nix ];
};
};
}
2.3 首次激活
# 首次激活(bootstrap);之后可直接用 darwin-rebuild
nix run nix-darwin -- switch --flake .#my-mac
darwin-rebuild switch --flake .#my-mac # 应用配置
darwin-rebuild build --flake .#my-mac # 只构建不切换
darwin-rebuild --list-generations # 看历史世代
darwin-rebuild --rollback # 回滚到上一世代
记忆:nix-darwin 走 flake 集成——inputs 里 nix-darwin 的 nixpkgs 必须 follows;用
darwinSystem生成darwinConfigurations.<name>,darwin-rebuild switch --flake .#name应用;它同样有世代与 rollback。
3. darwin 模块与系统配置
3.1 一份最小 configuration.nix
{ pkgs, ... }:
{
# 声明主机名、平台、用户
networking.hostName = "my-mac";
nixpkgs.hostPlatform = "aarch64-darwin"; # Apple Silicon
system.primaryUser = "alice";
# 系统级包(对全机可见)
environment.systemPackages = with pkgs; [ git ripgrep fd jq ];
# Nix 守护进程设置
nix.settings.experimental-features = [ "nix-command" "flakes" ];
nix.settings.trusted-users = [ "alice" ];
}
3.2 系统级配置项一览
| 选项 | 作用 |
|---|---|
system.stateVersion | 配置格式版本(首次设定后别改) |
system.primaryUser | 主用户(影响 defaults 应用) |
nixpkgs.hostPlatform | aarch64-darwin 或 x86_64-darwin |
environment.systemPackages | 系统级包 |
environment.variables | 全局环境变量 |
security.pam.services | PAM 配置(谨慎) |
3.3 键盘与网络
{
system.keyboard = { enableKeyMapping = true; remapCapsLockToEscape = true; };
networking.computerName = "my-mac";
networking.localHostName = "my-mac";
}
记忆:darwin 模块与 NixOS 同源但选项不同——核心项是 hostPlatform(aarch64/x86_64-darwin)、primaryUser、stateVersion(首次设定后不改)、systemPackages;键盘与网络有独立选项。
4. Homebrew 集成
4.1 为什么还需要 Homebrew
有些 macOS 软件(尤其是 GUI 应用与 casks)没有 nixpkgs 包或维护滞后。nix-darwin 的做法是声明式地管理 Homebrew:把要装的包写进配置,让 nix-darwin 生成 Brewfile 并执行。
4.2 声明式 Homebrew
{
homebrew = {
enable = true;
onActivation = {
autoUpdate = true;
upgrade = true;
cleanup = "zap"; # 移除不在配置里的包(谨慎)
};
taps = [ "homebrew/cask-fonts" ];
brews = [ "gnu-sed" "coreutils" ]; # CLI 工具
casks = [ "google-chrome" "rectangle" "1password" ]; # GUI 应用
masApps = { "Xcode" = 497799835; }; # App Store 应用
};
}
4.3 cleanup 的三档语义
| 值 | 行为 |
|---|---|
"none" | 不清理,配置外的包保留 |
"uninstall" | 卸载不在配置里的 formula/cask |
"zap" | 连同配置文件一起删除(最彻底、最危险) |
4.4 与 nixpkgs 的分工
原则是优先用 nixpkgs(可复现、可回滚、参与 Nix 闭包),只在 nixpkgs 缺失或滞后时用 Homebrew(GUI/cask 为主);不要把同一个工具既装 nixpkgs 又装 brew,会 PATH 冲突。
记忆:nix-darwin 的 Homebrew 集成是声明式的——taps/brews/casks/masApps 写进配置、onActivation 控制更新与清理(cleanup 三档 none/uninstall/zap);原则是 nixpkgs 优先、Homebrew 只补 GUI 与滞后包,且不要重复安装。
5. defaults:系统偏好即代码
5.1 defaults 是什么
macOS 的偏好设置底层是 defaults write <domain> <key> <value>。nix-darwin 把常用 domain 封装成选项,让偏好可声明、可版本化。
5.2 常用声明
{
system.defaults = {
# Dock:自动隐藏、放大、位置、图标大小
dock = { autohide = true; magnification = true; orientation = "left"; tilesize = 48; };
# Finder:显示隐藏文件、显示扩展名
finder = { AppleShowAllFiles = true; AppleShowAllExtensions = true; FXPreferredViewStyle = "Nlsv"; };
# 全局:按键重复速度、深色外观
NSGlobalDomain = { KeyRepeat = 2; InitialKeyRepeat = 15; AppleInterfaceStyle = "Dark"; };
# 屏幕截图位置
screencapture.location = "~/Pictures/screenshots";
};
}
5.3 未封装的 domain 与注意事项
未封装的 domain 走 CustomUserPreferences,例如 system.defaults.CustomUserPreferences."com.apple.Safari" = { IncludeDevelopMenu = true; };。
# ☐ defaults 只在「用户登录后」应用;system.primaryUser 必须正确
# ☐ 部分偏好需要重启对应 App(Dock/Finder)或注销才生效
# ☐ 不是所有键都被 nix-darwin 封装——未封装的用 CustomUserPreferences
记忆:defaults 把 macOS 偏好变成可声明的代码——dock/finder/NSGlobalDomain/screencapture 等有封装选项、未封装的走 CustomUserPreferences;注意它只在用户登录后应用且部分需重启 App 才生效。
6. launchd 服务管理
6.1 launchd 与 systemd 的对应
| systemd(Linux) | launchd(macOS) |
|---|---|
| service | agent / daemon |
| timer | StartCalendarInterval |
systemctl | launchctl |
| system unit | daemon(root) |
| user unit | agent(用户) |
6.2 声明一个定时任务
{
launchd.user.agents.backup = {
serviceConfig = {
ProgramArguments = [ "/run/current-system/sw/bin/rsync" "-a" "/Users/alice/doc/" "/Volumes/Backup/" ];
# 每天 2:30 执行
StartCalendarInterval = [ { Hour = 2; Minute = 30; } ];
RunAtLoad = false;
};
};
}
6.3 系统级守护进程
{
launchd.daemons.myagent = {
serviceConfig = {
ProgramArguments = [ "/run/current-system/sw/bin/myagent" ];
KeepAlive = true; # 崩溃自动重启
RunAtLoad = true;
StandardOutPath = "/var/log/myagent.log";
};
};
}
6.4 调试
调试三件套:launchctl list | grep myagent 看是否加载、launchctl print gui/$(id -u)/myagent 看详情与状态、log show --predicate 'process == "myagent"' --last 1h 看日志。
记忆:launchd 是 macOS 的服务系统——agent(用户级)对应 systemd user unit、daemon(系统级)对应 system unit,用
StartCalendarInterval做定时;调试走launchctl list/print与log show。
7. 与 Home Manager 协同
7.1 职责划分
| 层 | 工具 | 管什么 |
|---|---|---|
| 系统 | nix-darwin | defaults、launchd、Homebrew、系统包 |
| 用户 | Home Manager | dotfiles、shell、用户级包与程序配置 |
7.2 作为 darwin 模块引入
{
outputs = { self, nixpkgs, nix-darwin, home-manager }: {
darwinConfigurations."my-mac" = nix-darwin.lib.darwinSystem {
modules = [ ./configuration.nix home-manager.darwinModules.home-manager {
home-manager.useGlobalPkgs = true; # 复用系统 pkgs
home-manager.useUserPackages = true;
home-manager.users.alice = import ./home.nix;
} ];
};
};
}
7.3 useGlobalPkgs 的意义
useGlobalPkgs = true 让 Home Manager 复用 nix-darwin 的 pkgs——避免重复求值 nixpkgs,也让 overlay 与 config 设置一致生效。不设时 Home Manager 会自己 import nixpkgs,可能造成两份包集。
记忆:nix-darwin 管系统层、Home Manager 管用户层,用
home-manager.darwinModules.home-manager引入;务必开useGlobalPkgs = true复用系统 pkgs,否则会出现两份 nixpkgs 求值。
8. 用户、字体与 shell
8.1 声明用户
{
# 注意:nix-darwin 不负责创建 macOS 账户,账户要先存在
users.users.alice = { home = "/Users/alice"; shell = "/run/current-system/sw/bin/zsh"; };
system.primaryUser = "alice";
}
8.2 安装字体
{
fonts.packages = with pkgs; [ nerd-fonts.jetbrains-mono noto-fonts-cjk-sans ];
}
字体安装到系统字体目录,对所有应用可见。
8.3 默认 shell
把 zsh 设为默认:environment.shells = [ pkgs.zsh ](写入 /etc/shells)+ programs.zsh.enable = true + users.users.alice.shell = pkgs.zsh。
记忆:nix-darwin 能声明用户属性、字体与 shell——但 macOS 账户本身要先存在(它不改账户创建);字体走
fonts.packages装到系统目录,shell 走environment.shells+users.users.<u>.shell。
9. 常见坑与升级
9.1 高频坑
| 现象 | 原因 | 解决 |
|---|---|---|
| defaults 不生效 | 用户未登录 / primaryUser 错 | 设对 primaryUser 并重新 switch |
darwin-rebuild 找不到 | PATH 里没有 | 用 nix run nix-darwin -- switch |
| 两份 nixpkgs | 忘记 follows | 给 nix-darwin/home-manager 加 follows |
| 升级后配置报错 | 选项改名/移除 | 看 release notes 逐项迁移 |
| Apple Silicon 上 x86 包 | hostPlatform 写错 | 改成 aarch64-darwin |
9.2 升级流程
# 1) 更新 inputs;2) 先 build 验证;3) 通过再 switch;4) 出问题 rollback
nix flake update
darwin-rebuild build --flake .#my-mac
darwin-rebuild switch --flake .#my-mac
darwin-rebuild --rollback
9.3 不可逆操作提醒
# ☐ defaults 写过的键,回滚 nix-darwin 世代不会自动还原(系统偏好已落盘)
# ☐ Homebrew cleanup = "zap" 会删除配置外的应用,第一次先设 "none"
# ☐ 不要用 nix-darwin 管理 macOS 系统组件(它做不到,也不该做)
记忆:nix-darwin 三大坑——defaults 不生效(查 primaryUser)、两份 nixpkgs(加 follows)、升级选项改名(看 release notes);升级走「update → build → switch → 可 rollback」,但要记住 defaults 与 brew zap 造成的系统侧改动不会随世代回滚。
10. 速查表与一句话记忆
| 需求 | 配置 / 命令 | 一句话 |
|---|---|---|
| 应用配置 | darwin-rebuild switch --flake .#name | 主命令 |
| 只构建 | darwin-rebuild build | 先验证再切换 |
| 回滚 | darwin-rebuild --rollback | 世代级 |
| 系统偏好 | system.defaults.* | 可声明 |
| 未封装偏好 | CustomUserPreferences | 兜底 |
| 服务 | launchd.user.agents / daemons | agent vs daemon |
| Homebrew | homebrew.brews/casks | cleanup 慎用 zap |
| 复用 pkgs | useGlobalPkgs = true | 防两份 nixpkgs |
一句话记忆:nix-darwin 把 macOS 系统层纳入声明式配置——用同源的 darwin 模块管理 system.defaults(偏好即代码,未封装走 CustomUserPreferences)、launchd(agent/daemon 对应 systemd 的 user/system unit)、homebrew(声明 brews/casks/masApps,cleanup 慎用 zap)、用户字体 shell 与系统包;flake 集成时 nix-darwin 与 home-manager 的 nixpkgs 都必须 follows,并开 useGlobalPkgs = true 避免两份包集;它管的是「设置项」而非不可变系统——Apple 掌控的组件改不了,且 defaults 与 brew 造成的系统侧改动不会随世代回滚。核心纪律是「nixpkgs 优先、Homebrew 补缺、系统层归 darwin、用户层归 Home Manager」。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。