OFFSET偏移
← 文章目录OFFSET / FIELD NOTESNO. 079

用 Specialisation 构建 GNOME 变体

在父模块图上派生独立系统 toplevel,并明确继承、覆盖与恢复路径。

所在专题NixOS 上的 GNOME 声明式桌面 ↗
约 7 分钟阅读开始阅读

核验范围:2026 年 10 月查询的 nixpkgs nixos-unstable 模块与上游文档。分组列表、包数和服务默认值会随 revision 改变;部署时应以项目锁定的 nixpkgs 和 Home Manager 源码为准。本文没有构建、激活或桌面运行测试。

为什么需要#

NixOS 的配置是"一份 flake 描述整个系统"。但有时你想要多个形态:默认走 kmscon + Hyprland/niri,偶尔想要完整 GNOME 桌面。直接加 GNOME 会污染主配置(换登录管理器、塞一堆包)。Specialisation 允许你在一个 flake 里派生多个独立系统配置,各自有自己的 boot 条目。

用途

  • 多桌面环境共存(示例:base 使用 TTY,gnome 变体使用 GDM)
  • 实验性配置隔离(换内核、换内核参数)
  • 可保留默认系统作为恢复入口;用户数据、磁盘与部分运行状态仍共享

机制:它不是一个配置文件复制#

Specialisation 的构筑本质(nixpkgs 源码 activation/specialisation.nix):

  1. 每个 specialisation.<name> 用 extendModules 在父配置的模块图上扩展出一个独立子配置(不是复制配置,是模块系统的原生能力)
  2. inheritParentConfig = true(默认)→ 子配置继承整个主系统,configuration 块叠加其上
  3. 每个变体生成一个完整独立的 toplevel(自己的 systemd 单元、activation 脚本、/etc)
  4. 父 toplevel 里 symlink 引用它们:$out/specialisation/<name>
  5. bootloader 通过 bootspec(RFC 0125)发现并生成 boot 条目

所以变体有独立的系统 toplevel,但不是 VM、容器或独立用户数据隔离。Nix store 会共享相同的依赖;增加的 closure 引用、差异包和构建工作才是实际成本。

配置#

# host/gnome.nix(被 host/default.nix import)
{ config, lib, pkgs, ... }:
{
  specialisation.gnome.configuration = {
    services.desktopManager.gnome.enable = true;
    services.displayManager.gdm.enable = true;
    services.kmscon.enable = lib.mkForce false;   # ← 见下方 mkForce
  };
}

继承与覆盖:mkForce / mkDefault

  • 变体继承主配置(例如 kmscon、其他 compositor、输入法和 Home Manager 声明,依父配置而定)
  • 主配置里硬赋值(enable = true)的选项,变体要覆盖必须 lib.mkForce,与父配置值不同且优先级相同,通常会报 conflicting definitions;列表等可合并类型则依其合并规则处理
  • 如果想让主配置的值允许被子变体覆盖,主配置里写 lib.mkDefault(降低优先级)

嵌套 specialisation 被禁用 变体内部不能再有变体(no-clone.nix 强制清空)。所以在 gnome 变体里 /run/current-system/specialisation/ 是空的——切回 base 用 nixos-rebuild switch,不能在 gnome 里用同样的脚本切回。

切换#

# 1. 重启,boot 菜单选 "NixOS (gnome)"(条目名 = specialisation 的 key)
# 2. 运行时切换(不重启,激活当前 generation 的变体)
sudo nixos-rebuild switch --specialisation gnome

# 3. 直接用已构建变体的激活脚本
sudo /run/current-system/specialisation/gnome/bin/switch-to-configuration switch   # 激活并更新启动配置;具体启动项由 bootloader 决定
sudo /run/current-system/specialisation/gnome/bin/switch-to-configuration test     # 临时激活,不更新启动默认

回 base 时明确选择原始父 flake,例如 sudo nixos-rebuild switch --flake .#workstation(不带 --specialisation)。构建前可记录父系统 store 路径,按恢复计划运行其 bin/switch-to-configuration test;不要依赖当前变体下存在父路径 symlink。

验证(不切换就构建变体)#

# config.system.build.toplevel.specialisation.<name> 不存在,用这个路径:
nix build .#nixosConfigurations.workstation.config.specialisation.gnome.configuration.system.build.toplevel

坑:新文件要 git add Git flake 通常只纳入已跟踪文件;新 .nix 文件未跟踪会被过滤。可在适当的 Git 流程中加入跟踪,也可使用 path:.#... 明确纳入当前目录。git add 不等于提交,评估成功也不等于变体可启动。

关联#

切换前的运行边界#

切换显示管理器、桌面服务或内核可能影响当前图形会话。保存工作、保留可用 TTY/控制台与已知可启动的 base generation;内核、启动参数或驱动变化往往需要重新启动。nix build 只验证产物,不验证 GDM 登录、锁屏、输入法和用户服务,需要目标机器的实际会话检查。

参考与延伸

  1. NixOS specialisation 模块 ↗
  2. NixOS bootspec RFC ↗

根据技术笔记重新整理,示例使用通用名称。

沿专题继续阅读

NixOS 上的 GNOME 声明式桌面

  1. 01GNOME 的声明式桌面管理
  2. 02GNOME 模块选项参考
  3. 03管理 GSettings 默认值与 dconf 状态
  4. 04从设置 diff 到声明与扩展
  5. 05用 Specialisation 构建 GNOME 变体
  6. 06Wayland 会话中的 kmscon 与 uwsm
CONTINUE READING / 下一篇Wayland 会话中的 kmscon 与 uwsm

分别排查 DRM 交接、systemd 用户会话和 XDG autostart 的生命周期。