把软件、服务和桌面选项写进 Nix 配置后,很容易期待“一次重建恢复整个环境”。但环境里还生活着可写偏好、应用数据库、文档和缓存。先找出每类内容由谁管理,才能知道配置能带回什么,以及还缺哪份备份。
声明描述的环境,不等于磁盘的完整快照#
NixOS 配置描述软件和系统服务等目标状态,构建产生相应结果,激活把它接到运行环境。系统代际允许回到旧配置,但不会因此把文档、聊天记录或应用数据库一起恢复到那个时间点。系统回滚与数据恢复是不同机制。[1]
也不能直接拿目录划界:同在 ~/.config 下,一些文件可能是 Home Manager 生成的链接,另一些由程序自行写入;dconf 中也可能同时存在声明写入的值与用户通过界面调整的值。位置只是线索,实际管理者与写入时机才是依据。
“运行时状态”不等于无价值,也不等于无法版本化。文本偏好可以选择性导出,业务数据可以备份;只是不能期待一个没有声明或备份它们的系统自动知道该恢复什么。相反,带有密钥、历史记录或机器相关路径的状态,通常不应直接装进公开配置仓库。
GNOME:默认值、用户值与锁定#
GNOME 的 GSettings 为应用提供带类型的设置接口,schema 定义键及默认值,常见的 dconf 后端保存可写设置。用户看到的最终值,可能来自用户数据库,也可能回退到系统提供的值或 schema 默认值。系统管理员还可以通过 dconf profile 组织数据库及锁定规则。[3]
NixOS 的 GNOME 模块提供 extraGSettingsOverrides,用于补充 schema 默认值。下面采用当前模块的选项路径,展示默认显示电量百分比、星期与启用夜灯;它是现有 GNOME 系统配置中的片段,不是一份完整系统配置。[5]
services.desktopManager.gnome.extraGSettingsOverrides = ''
[org.gnome.desktop.interface]
show-battery-percentage=true
clock-show-weekday=true
[org.gnome.settings-daemon.plugins.color]
night-light-enabled=true
'';改默认值不保证覆盖用户已经写入的显式值。这解释了一个常见现象:新账户表现符合预期,旧账户仍保留之前的偏好。此时先检查是否有更高优先级的用户值,而不是把每次“不生效”都归因于构建失败。
默认值也不是锁定。若要求用户不能改变某个键,需要另行设计 dconf locks;普通 schema override 与 Home Manager 写入设置本身都不等价于这一机制。[4]
Home Manager 写入的是一次激活结果#
要表达某个用户的稳定偏好,可以使用 Home Manager 的 dconf.settings。这里路径采用 dconf 的斜线分组;它和 gsettings 命令使用的点分 schema 名看起来相近,但写法不同。
dconf.settings = {
"org/gnome/desktop/interface" = {
color-scheme = "prefer-dark";
clock-show-weekday = true;
show-battery-percentage = true;
};
};当前核对的实现会在激活阶段生成并加载对应设置。它不是持续监视数据库的守护进程:激活之后,若没有其他锁定机制,界面或应用仍可能修改可写值;下一次相应激活又会应用声明。配置的是“何时写入什么”,不能据此宣称设置永远不能变。[2]
仅构建成功,也不能证明这些值已写入当前会话。排查时分别确认:这份 Home Manager 配置是否被使用、是否完成激活、键路径与类型是否正确、当前应用读取到什么。对于数组、枚举和特殊数值类型,要遵守对应 schema,不能只因为 Nix 表达式能求值就判断语义正确。
从声明中删掉一个键时,同样应检查模块的清理语义。该实现会处理此前管理而后来移除的键,不应把删除声明简单理解成“从此永远保留最后写进去的值”。
把界面里试好的偏好,有选择地带回配置#
不必一次性猜出所有设置键。可以先在界面调整一个明确选项,再观察对应的小范围数据,理解后写回配置。下面的命令只针对桌面界面这一组,避免把整个 dconf 数据库连同无关状态全部导出。
gsettings get org.gnome.desktop.interface color-scheme
dconf read /org/gnome/desktop/interface/color-scheme
dconf dump /org/gnome/desktop/interface/ > desktop-interface.inigsettings get 通过 schema 接口读设置,dconf read 和 dump 面向 dconf 中的值;它们所回答的问题并不完全相同。缺少显式写入值时,schema 的默认值仍可能让应用得到有效设置。dump 是该子树的设置摘录,不应机械地把它当作“所有与默认值不同的项目清单”。
接下来逐个挑选需要长期保留的键,去掉临时状态和机器相关值,把值转换为模块要求的类型。保留一个小而明确的声明,比把数据库整体复制成一大段看不懂的配置更容易维护。
验证顺序也应清楚:配置能求值和构建,只证明前面的结构成立;完成授权的激活后,还要查询设置并观察应用实际表现。导出文件是观察记录,加入 Git 前仍需审阅内容。
软件安装清单,管理范围也有边界#
把一个包从 environment.systemPackages 中移除,会改变新系统配置提供的那组软件。它不意味着这个程序在整台机器的所有来源都立刻消失:另一个用户 profile、容器或 Flatpak 可能仍提供同名程序,旧代际与 store 中也可能仍有对应对象。判断正在运行哪一个,应检查实际命中路径。
Flatpak 的声明管理尤其适合用来观察“范围”问题。以外部模块 nix-flatpak 为例,假设已经导入对应的 NixOS 模块,可以在配置中声明应用:
services.flatpak = {
enable = true;
packages = [ "org.gnome.Calculator" ];
uninstallUnmanaged = false;
};这里依赖 nix-flatpak 模块提供 packages 等选项,并非只启用 NixOS 的基础 Flatpak 服务就自动获得这些能力;还需要可用的对应 remote。使用 Home Manager 版本时,应导入该项目的 Home Manager 模块,按用户安装范围理解它。
该模块默认管理声明列出的项目,保留范围内其他手动安装的应用。设置 uninstallUnmanaged = true 才会要求清理未声明项目,而且仍受安装范围约束:系统安装与用户安装不是同一组对象。这个选项改变了清理权限边界,不能当作普通格式偏好随手开启。[6]
应用回来了,内容还要另行恢复#
安装应用与保存应用数据本来就是两条线。普通 Flatpak 卸载可以保留应用数据,带 --delete-data 的卸载会删除其管理的相应数据;应用写到其他位置的文件则可能另外存在。因此,“从清单删除软件”和“清理它的全部个人数据”也不能画等号。[7]
对于笔记、项目、数据库和需要延续的应用状态,应该明确:它存在哪里,备份是否覆盖,恢复时是否要求应用停止写入,目标版本能否读取。只备份启动配置,不会自动包含数据库;只复制数据目录,也不保证跨版本迁移无需处理。
缓存可以按重建成本决定是否保留。能重新生成不等于一定值得每次重建,也不意味着绝对不存在配置接口。把“要不要保留”“如何生成”“谁负责生成”分别回答,比按文件扩展名一刀切更实际。
给每类状态安排一个明确去处#
| 对象 | 适合的管理方式 | 需要验证的结果 |
|---|---|---|
| 长期使用的软件与服务 | Nix 声明和依赖锁定 | 构建、激活及实际运行 |
| 稳定的桌面偏好 | 挑选键写入相应模块 | 写入时机、优先级与覆盖关系 |
| 试验性调整 | 保留观察记录,稳定后再纳入配置 | 是否值得成为长期约定 |
| 文档、数据库与重要状态 | 备份、同步及恢复步骤 | 完整性、版本兼容和可读取性 |
| 可再生缓存 | 按成本保留或重建 | 恢复耗时是否可接受 |
整理环境的验收可以具体到一件事:选择一个重要应用,在独立位置验证“软件装回来、设置应用上、数据读得出”这三步。配置仓库与备份各自承担清楚的职责,换机和回滚才有可预测的结果。
参考与延伸
- NixOS:系统回滚 ↗
- Home Manager dconf 激活实现 ↗
- GNOME:dconf profile 与优先级 ↗
- GNOME:设置锁定 ↗
- NixOS GNOME 模块 ↗
- nix-flatpak 的声明与清理范围 ↗
- Flathub:卸载与应用数据 ↗
根据技术笔记重新整理,示例使用通用名称。