Nix Flake 项目

Nix Flake 项目

Shetty Yttehs Lv3

最近一个月我都在折腾一个项目:Nix-flake-config

前言

本文头图来自 Nix 官网

这事还要从去年 9 月份说起,作为一个 ArchLinux 老用户,我从几年前就一直将 ArchLinux 作为主力操作系统,中途经历过多次系统崩溃,迫使我重装系统。期间我还研究了 dotfiles 这种项目,并自己维护了一个,将配置保留到云端托管,以便于下次系统爆炸时可以直接拿来用。不得不说,dotfiles 的体验还是非常好的。我的 ArchLinux 后来趋于稳定,运行了一年多没有炸。顺带一提,期间我还把桌面换成了 Hyprland。

作为一个 nerd,尝试新事物是生活的一部分,过于庞大的系统配置,让我的 dotfiles 项目也难以维护。直到我某天突然发现了 NixOS,这是一个独特的 Linux 发行版,以声明式配置构建整个系统与所有软件包,并且拥有强大的软件生态,很多在 AUR 才存在的包,却能在 nixpkgs 中得到官方维护,甚至 oh-my-zsh 都属于 zsh 包的子配置项。

我意识到,这才是适合我的系统配置,过渡到 NixOS 必然发生,不如早点迁移。

Nix 语言和 Nix 配置

这部分内容不是本文的重点,只做简单讨论。

一开始我就直接使用了 flakehome-manager,并直接使用了 NixOS 而不是 Nix as package manager,这是因为我追求一步到位,并且声明式的配置难度不高。

  • flake:通过 lock 文件使配置固定,使不同的主机可通过一份配置复现相同的环境。
  • home-manager:用户层级的包管理,有效隔离环境,保持系统干净。

最开始的 Nix 配置项目

一开始,我只是把 Nix Flake 作为单主机配置使用,分离了 home-manager 与系统应用配置。软件包直接隔离,详细配置完全按照官方硬编码至单个软件包内部。此时我只是换了个发行版,并没有深入理解 nix 哲学。

后来我意识到,nix 函数的模块化可以实现自由组合,软件包之间可以共享配置,或形成自定义的依赖关系。一份软件包的复杂配置可以共享给多个使用者,一份 flake 可以维护多个主机。

重构 Nix Flake 项目

我在 GitHub 上阅读了很多 nix 个人配置项目,有一部分项目非常吸引我:

他们只用一个仓库就可以管理个人 NixOS、个人 macOS(利用 nix-darwin)、服务器 NixOS。我很欣赏这种作风,不仅可以跨主机共享用户,而且拥有一份稳健的配置管理所有主机真的很酷,并且有着很高的效率。如果需要添加一个软件包,只需配置一次,就可以在所有主机上生效。

因此,我想把我的 nix 项目改造成支持多主机、多用户的模式,用户与主机没有依赖,可以任意组合

层级分配

宏观下,不同的主机可以共享相同用户,微观下,主机或用户可以共享相同的软件包。因此需要引入一种声明式机制表达主机或用户所需的软件包,以此在库中查找并使用。

可以确定以下核心层级:用户配置、主机配置、用户层、系统层、统一 flake 层。实际项目层级如下,项目文档已经足够详细,此处只做简要说明:

用户配置

定义用户配置,包括用户信息、软件包、用户主题样式;管理所有用户配置。

主机配置

定义主机配置,包括主机信息、软件包、主机主题样式,还特别包括主机使用的用户列表;管理所有主机配置。

此处主机配置还包括由 nixos-generate-config 命令生成的硬件描述文件。

用户层

维护预设的用户层软件包配置,供用户选择启用。

系统层

维护预设的系统层软件包配置,供主机选择启用;生成用户配置中一些软件的系统层后端部分。

桌面层

将桌面环境、主题样式抽象出来形成的一个层级,内部分为用户部分与系统部分。

两部分都维护预设的主题。

用户部分

维护预设主题的用户后端实现;维护预设的字体;维护预设的桌面环境;维护预设的桌面辅助软件包的用户后端实现。

系统部分

维护预设主题的系统后端实现;维护预设的 display manager;维护预设的桌面辅助软件包的用户后端实现。

Flake 层

作为顶层入口,导入所有主机配置,世界线收束。


每个层级的作用区分明显,各司其职,共同维护着不同的抽象层。前端为声明式的配置,后端为复杂的实现。这带来的好处是可组合性,达成用户不依赖主机的目标。

我的理想状态是:在未来如果新增 darwin 架构的主机,仍然可以复用现有的用户,保持工作环境的一致性

适配与调整

在编写项目的过程中,我遇到了一些痛点,故作记录。

软件包的重定义

nix 的软件包不止局限于 pkgs.xxx 类型,我认为以下配置声明都是变相安装软件包的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 添加某个包到当前环境
somePackage = [ pkgs.xxx ];

# 启用某个程序
programs.xxx.enable = true;

# 启用某种服务
services.xxx.enable = true;

# 启用某个桌面环境
wayland.windowManager.xxx.enable = true;

# ...

毕竟在其他发行版,大部分东西都是以的形式存在,甚至 Linux 内核都通过包管理器管理。nix 对包进行了区分,但是对于普通用户来说,不需要关心一个包是服务或是普通程序,只需要维护配置,让 nix 去做背后的事情。nix 还有一个优点,用户可以自定义服务和脚本,这部分内容也具有成为软件包的能力。

所以我添加了一个抽象层,任何包都以 software(在桌面层也可能是 aux、dm、sessions) 的形式存在。诸如具体配置是什么、是程序还是服务等问题则交给内部文件处理。用户与主机的声明式配置只需定义 software.xxx.enable = true 就可以使用它。

software 将软件及其专有配置限制在了它们自己的小房间内,有效限制了作用域,隔离了环境。比如,我只在 bat 配置中定义了全局变量 PAGER="bat",这种全局注入只会在启用 bat 时发生;我在 wl-clipboard 配置中定义了自定义一个剪贴板相关服务,这个服务只会在启用 wl-clipboard 时生效。这种方式可以在共享配置时避免编写很多条件判断语句。

主题派生值

nix 提供了条件编译,使一个配置项可以受到另一个配置项的影响。这个机制特别适合主题,因为所有软件都可以使用主题,而用户可以选择各种各样的主题。这时就需要把主题部分解耦,因为软件的正常功能基本不依赖特定主题,即不关心主题是什么。

主题有个特殊的部分:调色板,它定义了各种基本色在这个主题的硬编码值,以供一些不支持主题直接应用、需要硬编码颜色的程序使用。然而我们不可能让用户在声明式配置中定义各种颜色的十六进制编码,正常情况应该是:主机或用户定义了某个主题,桌面层自动把调色板注入到声明式配置中。

由于声明式配置是写死的,不能在运行时注入,所以只好把配置注入到 config 中,再通过自定义的 runtime 模块读取主题并注入调色板属性。

你可能认为主题派生值带来了 config 注入,从而带来了复杂性,其实这只是 config 注入的次要原因。

Config 注入问题

config 注入指将用户与主机的声明式配置注入到 config.profile.{users,hosts} 中,这样可以让声明式配置成为真正的选项,接受 options 模块的类型检查,保证定义配置时的正确性。

不过在 flake 层时并不存在 config 这种东西,用户层和系统层都分别有自己的 config 模块,互相独立但是行为一致。

系统层的 config 注入

我在 flake 层中新建了临时变量 profiles,以正确的结构存储所有用户和主机的配置。在逻辑进入到系统层的 nixosSystem.modules 时,马上进行 config 注入,结束 profiles 变量的生命周期。

用户层的 config 注入

也是在系统层,此时系统层存在完整的 config.profile 定义,只提取当前主机的用户部分,组成 config.profile.users,在逻辑进入到 home-manager.sharedModules 时进行注入。


config 注入很像是在资源受限的环境中另辟蹊径,非常有趣。

Aside

举个资源受限的例子:
在编写某操作系统内核时,刚开始页表系统没有初始化,开发者不得不分配一个 4K 的内存区域用于创建初始巨页,并修改页表相关寄存器达到以一级页表初始化页表系统,等到执行到真正的页表初始化部分后,才能使用真正需要的三级页表。

感受

Nix-flake-config 是我第一个自发编写非业务类型开源项目,我第一次感受到开源项目不只是写代码而已,还包括可行性分析、需求定义、架构设计、文档编写、集成测试等等。项目中还有很多待改进的部分,我也有一些新想法(比如增加 darwin 主机),但目前先告一段落。

在整个重构过程时,经常出现 nix 设计与我的设计的冲突,使我被迫改写很多代码,同时我也更加深刻认识到了 nix 是如何设计的。

我很荣幸,在整个重构过程时,我都遵循了《重构:改善既有代码的设计》中其中一个原则——重构过程中项目始终可用。

总结

对于很多计算机工作者,使用 ArchLinux 等发行版也许是他们能达到的最终形态,也是最适合他们的形态。追求稳定的人使用 Ubuntu LTS,享受稳定性与适配最广的 apt;追求完全掌握电脑的人使用 ArchLinux,通过包管理器控制每一个包的安装。而其他人用的可能是其他操作系统。他们都将在生产环境的陪伴下达成目标和成就。

像我这种自诩为系统工程师的人(我的实力尚未达标),自然会过渡到 NixOS,爱上这种边界感分明,可在不同主机复现的环境。

感谢各大 GNU/Linux 社区,感谢开源社区,为曾经初学者的我带来了丰富多彩的体验。感谢 Nix 社区,带来了如此有趣的包管理工具。

  • Title: Nix Flake 项目
  • Author: Shetty Yttehs
  • Created at : 2026-01-30 22:41:00
  • Updated at : 2026-02-01 01:48:11
  • Link: https://blog.shettydev.com/2026/01/30/nix-flake-project/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments