I’d like to split my configuration.nix
into multiple fragments so I can mix and match them in configuration.nix
.
This was the initial approach:
# ./configuration.nix
{ config, lib, pkgs, ... }:
{
imports = [
./fragments/category1/virtualbox.nix
./fragments/category2/fonts.nix
];
}
# ./fragments/category1/virtualbox.nix
{ config, lib, pkgs, ... }:
{
virtualisation = {
virtualbox.host.enable = true;
virtualbox.host.enableExtensionPack = true;
};
}
# ./fragments/category2/fonts.nix
{ config, lib, pkgs, ... }:
{
fonts.fonts = with pkgs; [
# ... some fonts ...
];
}
However, this approach has several drawbacks because fragments aren’t actual modules:
- can’t use fragments in a custom NixOS module because they can’t be imported without modifying the system config
- ugly and noisy syntax
A better approach would be to wrap each fragment in an actual module, which will be disabled by default:
# ./configuration.nix
{ config, lib, pkgs, ... }:
{
imports = [
./fragments
];
fragments = {
category1 = {
virtualbox = true;
};
category2 = {
# fonts = true;
};
};
}
# ./fragments/default.nix
{ config, lib, pkgs, ... }:
{
imports = [
./category1/virtualbox.nix
./category2/fonts.nix
];
}
# ./fragments/category1/virtualbox.nix
{ config, lib, pkgs, ... }:
{
options.fragments.category1.virtualbox = mkEnableOption "virtualbox";
config = mkIf config.fragments.category1.virtualbox {
virtualisation = {
virtualbox.host.enable = true;
virtualbox.host.enableExtensionPack = true;
};
};
}
# ./fragments/category2/fonts.nix
{ config, lib, pkgs, ... }:
{
options.fragments.category2.fonts = mkEnableOption "fonts";
config = mkIf config.fragments.category2.fonts {
fonts.fonts = with pkgs; [
# ... some fonts ...
];
};
}
I wanted to simplify this since all modules have only one option (that’s the point). I figured the best thing to do would be to keep the fragments as in the initial approach and create all the modules in bulk (in ./fragments/default.nix
).
I tried this first:
# ./configuration.nix
{ config, lib, pkgs, ... }:
{
imports = [
./fragments
];
fragments = {
category1 = {
virtualbox = true;
};
category2 = {
# fonts = true;
};
};
}
# ./fragments/default.nix
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.fragments;
in {
options.fragments = {
category1 = {
virtualbox = mkEnableOption "virtualbox";
};
category2 = {
fonts = mkEnableOption "fonts";
};
};
config = mkIf cfg.category1.virtualbox (import ./category1/virtualbox.nix { inherit config lib pkgs; })
// mkIf cfg.category2.fonts (import ./category2/fonts.nix { inherit config lib pkgs; });
}
# ./fragments/category1/virtualbox.nix
{ config, lib, pkgs, ... }:
{
virtualisation = {
virtualbox.host.enable = true;
virtualbox.host.enableExtensionPack = true;
};
}
# ./fragments/category2/fonts.nix
{ config, lib, pkgs, ... }:
{
fonts.fonts = with pkgs; [
# ... some fonts ...
];
}
That worked, so I replaced ./fragments/default.nix
with this:
{ config, lib, pkgs, ... }:
with lib;
let
paths = {
category1 = {
virtualbox = ./category1/virtualbox.nix;
};
category2 = {
fonts = ./category2/fonts.nix;
};
};
cfg = config.fragments;
in {
options.fragments = mapAttrsRecursive (path: value: mkEnableOption (concatStringsSep "." path)) paths;
config = let
getCfgOption = path: getAttrFromPath path cfg;
importInherit = file: import file { inherit config lib pkgs; };
paths' = mapAttrsRecursive (path: value: mkIf (getCfgOption path) (importInherit value)) paths;
result = collect isOption paths';
in mkMerge result;
}
That didn’t work due to infinite recursion, and I can’t figure out what’s causing it and how I could tackle this problem.
terminate called after throwing an instance of 'nix::EvalError'
what(): infinite recursion encountered, at /nix/var/nix/profiles/per-user/root/channels/nixos/lib/modules.nix:131:21
Anyone see anything obviously wrong with it? Is it a good approach at all?