大约六个月前,在室友的推荐下,我把 PC 切换到了 NixOS。当时的原因很实际:我已经把 Ubuntu 桌面环境折腾到不好修了,重装反而更省事。
NixOS 是一个基于 Nix 包管理器的 Linux 发行版,它支持声明式配置和可重现构建。
NixOS 让包和配置管理少了很多临时手工状态,所以我把它作为 PC 上的主系统。后来熟悉 Nix 之后,我也在 MacBook 上安装了 Nix。目标很简单:让 NixOS 桌面和 macOS 笔记本尽量使用同一套开发环境。
- 使用 Nix 在两个系统上设置相同的全局开发环境,例如特定的 Python 解释器或 Java 编译器版本。
- 在两个系统上定义共享的 shell 环境(如 zsh),从而轻松重用 shell 脚本、变量和别名。
这套配置可以在这里看到。
我想要实现的目标
我需要一个解决方案,能够:
- 确保跨平台包版本完全相同。
- 将整个开发栈以代码形式声明,便于复制。
- 支持高级并行计算设置,包括 OpenCL、OpenMP 和 MPI。
- 允许针对特定平台的配置调整。
我的设置架构
为了实现这种整合,我以模块化方式构建了 Nix 配置。以下是目录结构的概述:
nix/├── home-manager/│ ├── shared/│ │ └── programming.nix # 跨平台包│ ├── linux/│ │ └── home.nix # Linux 特定配置│ └── darwin/│ └── home.nix # macOS 特定配置├── nixos/│ └── configuration.nix # NixOS 系统配置├── nix-darwin/│ └── configuration.nix # macOS 系统配置└── flake.nix # 主配置入口点这种结构将共享元素集中起来,同时允许操作系统特定的调整。
共享编程环境
我的设置核心是 programming.nix 文件,它定义了跨平台一致的开发工具。这确保了从编译器到库的一切版本都相同。
# 跨平台编程环境包{ config, pkgs, ... }:
{ home.packages = with pkgs; [ # 开发工具 git wget curl
# 编程语言和运行时 nodejs typescript (python3.withPackages (ps: with ps; [ numpy pandas scipy matplotlib scikit-learn jupyterlab ]))
# Java 开发 jdk21 maven gradle
# C/C++ 开发 gcc cmake gdb lldb pkg-config
# 并行编程栈 mpi llvmPackages.openmp opencl-headers opencl-clhpp ocl-icd clinfo
# 文档和文本处理 pandoc typst
# 开发环境 docker ];
home.sessionVariables = { JAVA_HOME = "${pkgs.jdk21}"; CC = "${pkgs.gcc}/bin/gcc"; CXX = "${pkgs.gcc}/bin/g++"; };}这份配置引入的是我最常用的工具:数据工作的 Python 包、后端开发需要的 Java 工具,以及 OpenCL/OpenMP/MPI 这类并行计算组件。
在 macOS 上整合 Homebrew 与 Nix
虽然 Nix 处理了我大部分命令行工具,但我依赖 Homebrew 来管理 GUI 应用程序和某些 Nix 中不可用的 macOS 特定包。为了保持一切声明式,我使用 nix-darwin 中的 homebrew 模块将 Homebrew 直接整合到 Nix 配置中。
# macOS 的 Homebrew 配置{ config, pkgs, ... }:
{ # Homebrew 声明式配置 homebrew = { enable = true;
# 应用安装偏好 onActivation = { autoUpdate = true; # 更新 Homebrew 本身 upgrade = true; # 将所有包升级到最新版本 cleanup = "zap"; # 卸载未在配置中列出的包
# 额外的更新选项(可选) extraFlags = [ "--verbose" # 在更新期间显示详细输出 ]; };
# 全局 Homebrew 设置 global = { brewfile = true; # 使用 Brewfile 进行管理 lockfiles = false; # 不创建锁文件 };
# 来自 brew 列表的 GUI 应用程序 casks = [ # 云存储和同步 "baidunetdisk" "google-drive" "nutstore"
# 学习和教育 "eudic" "pdf-expert"
# 浏览器和网络客户端 "firefox" "google-chrome" "bilibili"
# 开发工具 "github" "jetbrains-toolbox" "visual-studio-code" "cursor" "kate"
# macUI #"sketchybar" see home manager "font-hack-nerd-font"
# 设计和生产力 "canva" "figma" "obsidian" "typora"
# 通信 "qq" "wechat" "whatsapp" "microsoft-teams" "telegram-desktop"
# AI 和生产力 "chatgpt" "cherry-studio"
# 网络和系统工具 "clash-verge-rev" #"stats"
# 办公和生产力 "zoom" "slack" "microsoft-auto-update"
# 游戏和娱乐 "steam"
# 学术和研究 "zotero"
# 统计计算和分析 "r" "rstudio"
# 文档准备 "mactex" # 完整的 MacTeX 发行版,包括 TeXLive ];
# Formulae(命令行工具) - 目前保持为空,因为大多数由 nix 处理 brews = [ # AI 和 ML 工具现在由 nixpkgs 处理 ];
# Mac App Store 应用(如果有) masApps = { # 示例: "Xcode" = 497799835; }; };}这种方法让我的 macOS 设置保持干净且可重现。Homebrew 处理图形应用,而 Nix 管理其余部分,一切都以代码形式定义。
益处与挑战
这套配置让桌面和笔记本之间的切换少了很多脆弱状态。一致的环境减少了设置时间,也让配置问题更容易定位。
它也有成本:有些包需要平台特定的调整,初始设置对 Nix 新手来说会比较陡。对我自己的工作流来说,把配置版本化是值得的。
如果重新开始,我还是会从 Nix flakes 入手,因为它更容易把模块边界保留下来。
讨论