nixos-and-flakes-book/docs/nixos-with-flakes/modularize-the-configuration.md

249 lines
11 KiB
Markdown
Raw Normal View History

2023-06-30 09:00:03 +00:00
# Modularize Your NixOS Configuration
2023-06-23 12:29:12 +00:00
2023-06-28 08:53:32 +00:00
At this point, the skeleton of the entire system is configured. The current configuration structure in `/etc/nixos` should be:
2023-06-23 12:29:12 +00:00
```
$ tree
.
├── flake.lock
├── flake.nix
├── home.nix
└── configuration.nix
```
2023-06-28 08:53:32 +00:00
The functions of these four files are:
2023-06-23 12:29:12 +00:00
2023-06-28 08:53:32 +00:00
- `flake.lock`: An automatically generated version-lock file that records all input sources, hash values, and version numbers of the entire flake to ensure reproducibility.
- `flake.nix`: The entry file that will be recognized and deployed when executing `sudo nixos-rebuild switch`.
2023-06-23 12:29:12 +00:00
- See [Flakes - NixOS Wiki](https://nixos.wiki/wiki/Flakes) for all options of flake.nix.
2023-06-28 08:53:32 +00:00
- `configuration.nix`: Imported as a Nix module in flake.nix, all system-level configuration is currently written here.
2023-06-23 12:29:12 +00:00
- See [Configuration - NixOS Manual](https://nixos.org/manual/nixos/unstable/index.html#ch-configuration) for all options of configuration.nix.
2023-06-28 08:53:32 +00:00
- `home.nix`: Imported by Home-Manager as the configuration of the user `ryan` in flake.nix, containing all of `ryan`'s configuration and managing `ryan`'s home folder.
- See [Appendix A. Configuration Options - Home-Manager](https://nix-community.github.io/home-manager/options.html) for all options of home.nix.
2023-06-23 12:29:12 +00:00
2023-06-28 08:53:32 +00:00
By modifying these files, you can declaratively change the system and home directory status.
2023-06-23 12:29:12 +00:00
2023-06-28 08:53:32 +00:00
As the configuration increases, it becomes difficult to maintain the configuration by relying solely on `configuration.nix` and `home.nix`. Therefore, a better solution is to use the Nix module system to split the configuration into multiple modules and write them in a classified manner.
2023-06-23 12:29:12 +00:00
2023-06-28 08:53:32 +00:00
The Nix module system provides a parameter, `imports`, which accepts a list of `.nix` files and merges all the configuration defined in these files into the current Nix module. Note that `imports` will not simply overwrite duplicate configuration, but handle it more reasonably. For example, if `program.packages = [...]` is defined in multiple modules, then `imports` will merge all `program.packages` defined in all Nix modules into one list. Attribute sets can also be merged correctly. The specific behavior can be explored by yourself.
2023-06-23 12:29:12 +00:00
2023-06-28 08:53:32 +00:00
> I only found a description of `imports` in [Nixpkgs-Unstable Official Manual - evalModules Parameters](https://nixos.org/manual/nixpkgs/unstable/#module-system-lib-evalModules-parameters): `A list of modules. These are merged together to form the final configuration.`, it's a bit ambiguous...
2023-06-23 12:29:12 +00:00
2023-06-28 08:53:32 +00:00
With the help of `imports`, we can split `home.nix` and `configuration.nix` into multiple Nix modules defined in different `.nix` files.
2023-06-23 12:29:12 +00:00
2023-06-28 08:53:32 +00:00
For example, [ryan4yin/nix-config/v0.0.2](https://github.com/ryan4yin/nix-config/tree/v0.0.2) is the configuration of my previous NixOS system with i3 window manager. Its structure is:
2023-06-23 12:29:12 +00:00
```shell
├── flake.lock
├── flake.nix
├── home
│ ├── default.nix # here we import all submodules by imports = [...]
│ ├── fcitx5 # fcitx5 input method's configuration
│ │ ├── default.nix
│ │ └── rime-data-flypy
│ ├── i3 # i3 window manager's configuration
│ │ ├── config
│ │ ├── default.nix
│ │ ├── i3blocks.conf
│ │ ├── keybindings
│ │ └── scripts
│ ├── programs
│ │ ├── browsers.nix
│ │ ├── common.nix
│ │ ├── default.nix # here we import all modules in programs folder by imports = [...]
│ │ ├── git.nix
│ │ ├── media.nix
│ │ ├── vscode.nix
│ │ └── xdg.nix
│ ├── rofi # rofi launcher's configuration
│ │ ├── configs
│ │ │ ├── arc_dark_colors.rasi
│ │ │ ├── arc_dark_transparent_colors.rasi
│ │ │ ├── power-profiles.rasi
│ │ │ ├── powermenu.rasi
│ │ │ ├── rofidmenu.rasi
│ │ │ └── rofikeyhint.rasi
│ │ └── default.nix
│ └── shell # shell/terminal related configuration
│ ├── common.nix
│ ├── default.nix
│ ├── nushell
│ │ ├── config.nu
│ │ ├── default.nix
│ │ └── env.nu
│ ├── starship.nix
│ └── terminals.nix
├── hosts
│ ├── msi-rtx4090 # My main machine's configuration
│ │ ├── default.nix # This is the old configuration.nix, but most of the content has been split out to modules.
│ │ └── hardware-configuration.nix # hardware & disk related configuration, autogenerated by nixos
│ └── nixos-test # my test machine's configuration
│ ├── default.nix
│ └── hardware-configuration.nix
├── modules # some common NixOS modules that can be reused
│ ├── i3.nix
│ └── system.nix
└── wallpaper.jpg # wallpaper
```
For more details, see [ryan4yin/nix-config/v0.0.2](https://github.com/ryan4yin/nix-config/tree/v0.0.2).
2023-06-28 08:53:32 +00:00
## `lib.mkOverride`, `lib.mkDefault`, and `lib.mkForce`
2023-06-23 12:29:12 +00:00
2023-06-28 08:53:32 +00:00
Some people use `lib.mkDefault` and `lib.mkForce` to define values in Nix files. As their names suggest, `lib.mkDefault` and `lib.mkForce` are used to set default values or force values of options.
2023-06-23 12:29:12 +00:00
2023-06-28 08:53:32 +00:00
You can read the source code of `lib.mkDefault` and `lib.mkForce` by running `nix repl -f '<nixpkgs>'` and then entering `:e lib.mkDefault`. To learn the basic usage of `nix repl`, type `:?` to see the help information.
2023-06-23 12:29:12 +00:00
2023-06-28 08:53:32 +00:00
Here's the source code:
2023-06-23 12:29:12 +00:00
```nix
# ......
mkOverride = priority: content:
{ _type = "override";
inherit priority content;
};
mkOptionDefault = mkOverride 1500; # priority of option defaults
mkDefault = mkOverride 1000; # used in config sections of non-user modules to set a default
mkImageMediaOverride = mkOverride 60; # image media profiles can be derived by inclusion into host config, hence needing to override host config, but do allow user to mkForce
mkForce = mkOverride 50;
mkVMOverride = mkOverride 10; # used by nixos-rebuild build-vm
# ......
```
2023-06-28 08:53:32 +00:00
`lib.mkDefault` is used to set default values of options with a priority of 1000 internally, while `lib.mkForce` is used to force values of options with a priority of 50 internally. If you set a value of an option directly, it will be set with a default priority of 1000 (the same as `lib.mkDefault`).
2023-06-23 12:29:12 +00:00
2023-06-28 08:53:32 +00:00
The lower the `priority` value, the higher the actual priority. Therefore, `lib.mkForce` has a higher priority than `lib.mkDefault`. If you define multiple values with the same priority, Nix will throw an error.
2023-06-23 12:29:12 +00:00
2023-06-28 08:53:32 +00:00
These functions are useful for modularizing the configuration. You can set default values in a low-level module (base module) and force values in a high-level module.
2023-06-23 12:29:12 +00:00
2023-06-28 08:53:32 +00:00
As an example, in my configuration <https://github.com/ryan4yin/nix-config/blob/main/modules/nixos/core-server.nix#L30>, I define default values here:
2023-06-23 12:29:12 +00:00
```nix
{ lib, pkgs, ... }:
{
# ......
nixpkgs.config.allowUnfree = lib.mkDefault false;
# ......
}
```
And for my dekstop machine, I force the values to another value in <https://github.com/ryan4yin/nix-config/blob/main/modules/nixos/core-desktop.nix#L15>:
```nix
{ lib, pkgs, ... }:
{
# import the base module
imports = [
./core-server.nix
];
# override the default value defined in the base module
nixpkgs.config.allowUnfree = lib.mkForce true;
# ......
}
```
2023-06-28 08:53:32 +00:00
## `lib.mkOrder`, `lib.mkBefore`, and `lib.mkAfter`
2023-06-23 12:29:12 +00:00
2023-06-28 08:53:32 +00:00
`lib.mkBefore` and `lib.mkAfter` are used to set the merge order of **list-type options**. Like `lib.mkDefault` and `lib.mkForce`, they're also useful for modularizing the configuration.
2023-06-23 12:29:12 +00:00
2023-06-28 08:53:32 +00:00
As I mentioned earlier, if you define multiple values with the same **override priority**, Nix will throw an error. However, with `lib.mkOrder`, `lib.mkBefore`, or `lib.mkAfter`, you can define multiple values with the same override priority, and they will be merged in the order you defined.
2023-06-23 12:29:12 +00:00
2023-06-28 08:53:32 +00:00
To take a look at the source code of `lib.mkBefore`, run `nix repl -f '<nixpkgs>'` and then enter `:e lib.mkBefore`. To learn the basic usage of `nix repl`, type `:?` to see the help information:
2023-06-23 12:29:12 +00:00
```nix
# ......
mkOrder = priority: content:
{ _type = "order";
inherit priority content;
};
mkBefore = mkOrder 500;
mkAfter = mkOrder 1500;
# The default priority for things that don't have a priority specified.
defaultPriority = 100;
# ......
```
So `lib.mkBefore` is a shortcut for `lib.mkOrder 500`, and `lib.mkAfter` is a shortcut for `lib.mkOrder 1500`.
To test the usage of `lib.mkBefore` and `lib.mkAfter`, let's create a simple Flake project:
```shell
# create flake.nix with the following content
cat <<EOF | sudo tee flake.nix
{
description = "Ryan's NixOS Flake";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05";
};
outputs = { self, nixpkgs, ... }@inputs: {
nixosConfigurations = {
"nixos-test" = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
# demo module 1, insert git at the head of list
({lib, pkgs, ...}: {
environment.systemPackages = lib.mkBefore [pkgs.git];
})
# demo module 2, insert vim at the tail of list
({lib, pkgs, ...}: {
environment.systemPackages = lib.mkAfter [pkgs.vim];
})
# demo module 3, just add curl to the list normally
({lib, pkgs, ...}: {
environment.systemPackages = with pkgs; [curl];
})
];
};
};
};
}
EOF
# create flake.lock
nix flake update
# enter nix repl environment
nix repl
Welcome to Nix 2.13.3. Type :? for help.
# load the flake we just created
nix-repl> :lf .
Added 9 variables.
# check the order of systemPackages
nix-repl> outputs.nixosConfigurations.nixos-test.config.environment.systemPackages
[ «derivation /nix/store/0xvn7ssrwa0ax646gl4hwn8cpi05zl9j-git-2.40.1.drv»
«derivation /nix/store/7x8qmbvfai68sf73zq9szs5q78mc0kny-curl-8.1.1.drv»
«derivation /nix/store/bly81l03kh0dfly9ix2ysps6kyn1hrjl-nixos-container.drv»
......
......
«derivation /nix/store/qpmpvq5azka70lvamsca4g4sf55j8994-vim-9.0.1441.drv» ]
```
2023-06-28 08:53:32 +00:00
As we can see, the order of `systemPackages` is `git -> curl -> default packages -> vim`, which is the same as the order we defined in `flake.nix`.
2023-06-23 12:29:12 +00:00
> Though it's useless to adjust the order of `systemPackages`, it may be helpful at some other places...
## References
- [Nix modules: Improving Nix's discoverability and usability ](https://cfp.nixcon.org/nixcon2020/talk/K89WJY/)
- [Module System - Nixpkgs](https://github.com/NixOS/nixpkgs/blob/nixos-unstable/doc/module-system/module-system.chapter.md)