Evaluation Checks When configuration problems are detectable in a module, it is a good idea to write a check for catching it early. Doing so can provide clear feedback to the user and can prevent errors before the build. Although Nix has the abort and builtins.trace functions to perform such tasks generally, they are not ideally suited for NixOS modules. Instead of these functions, you can declare your evaluation checks using the NixOS module system.
Defining Checks Checks can be defined using the option. Each check needs an attribute name, under which you have to define an enable condition using and a message using . Note that the enable condition is inverse of what an assertion would be: To assert a value being true, the enable condition should be false in that case, so that it isn't triggered. For the check message, you can add options to the module arguments and use ${options.path.to.option} to print a context-aware string representation of the option path. Here is an example showing how this can be done. { config, options, ... }: { _module.checks.gpgSshAgent = { enable = config.programs.gnupg.agent.enableSSHSupport && config.programs.ssh.startAgent; message = "You can't enable both ${options.programs.ssh.startAgent}" + " and ${options.programs.gnupg.agent.enableSSHSupport}!"; }; _module.checks.grafanaPassword = { enable = config.services.grafana.database.password != ""; message = "The grafana password defined with ${options.services.grafana.database.password}" + " will be stored as plaintext in the Nix store!"; # This is a non-fatal warning type = "warning"; }; }
Ignoring Checks Sometimes you can get failing checks that don't apply to your specific case and you wish to ignore them, or at least make errors non-fatal. You can do so for all checks defined using by using the attribute name of the definition, which is conveniently printed using [...] when the check is triggered. For above example, the evaluation output when the checks are triggered looks as follows: trace: warning: [grafanaPassword] The grafana password defined with services.grafana.database.password will be stored as plaintext in the Nix store! error: Failed checks: - [gpgSshAgent] You can't enable both programs.ssh.startAgent and programs.gnupg.agent.enableSSHSupport! The [grafanaPassword] and [gpgSshAgent] strings tell you that these were defined under the grafanaPassword and gpgSshAgent attributes of respectively. With this knowledge you can adjust them to your liking: { lib, ... }: { # Change the error into a non-fatal warning _module.checks.gpgSshAgent.type = "warning"; # We don't care about this warning, disable it _module.checks.grafanaPassword.enable = lib.mkForce false; }
Checks in Submodules Evaluation checks can be defined within submodules in the same way. Here is an example: { lib, ... }: { options.myServices = lib.mkOption { type = lib.types.attrsOf (lib.types.submodule ({ config, options, ... }: { options.port = lib.mkOption {}; config._module.checks.portConflict = { enable = config.port == 80; message = "Port ${toString config.port} defined using" + " ${options.port} is usually used for HTTP"; type = "warning"; }; })); }; } When this check is triggered, it shows both the submodule path along with the check attribute within that submodule, joined by a /. Note also how ${options.port} correctly shows the context of the option. trace: warning: [myServices.foo/portConflict] Port 80 defined using myServices.foo.port is usually used for HTTP Therefore to disable such a check, you can do so by changing the option within the myServices.foo submodule: { lib, ... }: { myServices.foo._module.checks.portConflict.enable = lib.mkForce false; } Checks defined in submodules under types.listOf can't be ignored, since there's no way to change previously defined list items.