Merging configurations with Nix
Here's a problem: How to split up my Nix config?
I would like to split my config in multiple files, but:
- I want the individual files to be as stand-alone as possible, so I can copy them between projects, or give to friends
- I want to edit system config from those stand-alone units
This is a real problem without Nix that is usually dealt with by using something like Bash >>
operator to append text to the end of a config file and hope for the best. Or if your config files are YAML, you have to resort to jq
or regex
-fu.
How does Nix help us?
Lets imagine a scenario where I have a config like this:
# configuration.nix
{ pkgs, ... }:
{
# Some service definitions
services.abc = { ... };
services.xyz = { ... };
# Networking
networking.firewall = {
enable = true;
allowedTCPPorts = [
3000 # Service ABC
3001 # Service XYZ
];
};
}
Now I would like to split this by extracting services into their own files:
# configuration.nix
{ pkgs, ... }:
{
imports = [
./services/abc/default.nix
./services/xyz/default.nix
];
# Networking
networking.firewall = {
enable = true;
};
}
# services/abc/default.nix
{ pkgs, ... }:
{
services.abc = { ... };
networking.firewall.allowedTCPPorts = [
3000 # Service ABC
];
}
# services/xyz/default.nix
{ pkgs, ... }:
{
services.xyz = { ... };
networking.firewall.allowedTCPPorts = [
3001 # Service XYZ
];
}
This is very nice because now I can create my separate modules and Nix will take care of merging the configurations, in this case by concatenating the networking.firewall.allowedTCPPorts
list from the two modules. This option is defined here and the magical bit is that, by default, lists are merged using concatenation and additionally the apply
function canonicalizePortList
is applied to make sure each port only appears once.
The documentation for this is Extensible Option Types and basically what happens is that modules options are declared using types in such a way that also defines how these are supposed to be merged.