How to declare a default value for flake module parameter

Hi all,

I have different modules I use in my flake and I would like to add parameters to some of them.
I succeeded in adding parameters named cursorThemePackage by adding the following in the first of the module:

{ config, pkgs, lib, inputs, cursorThemePackage, ... }:
[...]

And I can call it like this my flake:

[...]
modules= [
          ./mymodule
           {
             _module.args = {
               cursorThemePackage = pkgs.capitaine-cursors;
             };
           }
];
[...]

I would like now to set a default value for cursorThemePackage. However when I comment the argument given in _module.args of the flake and replace the first line of the module with this:

{ config, pkgs, lib, inputs, cursorThemePackage ? pkgs.capitaine-cursors, ... }:
[...]

it fails with an message error: attribute 'cursorThemePackage' missing. I thought the ? syntax was the way to declare a default value for a parameter.

How can I put a default value for the module then?
`

Module arguments are not intended to be used like this.

I’m not sure why it fails; presumably this is caused by the NixOS module system not considering that arguments may not be set at all in _module.args when they are asked for in module arguments - yes, the ? does set default values in function arguments, but in this case the caller also looks for those names in _module.args, and when that is not set it might break if it’s not made to consider unset arguments.

It’s odd, because I’ve definitely seen pkgs ? <something> before. Perhaps it would work if cursorThemePackage was set to null in _module.args?

Anyway, you’re abusing _module.args and I won’t stand for this! I’m assuming you’re trying to set up multiple nixosConfigurations, and you want to set different cursorThemePackages depending on which host you’re deploying. This is where you should be using the NixOS module system, not hacking around with function args.

The pattern for this is to write a custom option, i.e. something like:

{lib, pkgs, ...}: {
  options.local.cursorThemePackage = lib.mkOption {
    default = pkgs.capitaine-cursors;
    type = lib.types.package;
  };
}

You then make sure this module is included in your evaluation somewhere (add it to modules or the imports of ./mymodule/default.nix or something), and then you can refer to config.local.cursorThemePackage wherever you want to use it.

You can also override it just using any module, e.g.:

modules= [
  ./mymodule
  {
    local.cursorThemePackage = pkgs.comixcursors;
  }
];

But you can also set that in a host-specific module (e.g. ./hosts/mypc), which is a much cleaner way of setting these host-specific things that enables separating out things far more efficiently because you don’t need to define custom options or module args for everything (for e.g. multiple hardware-configuration.nix files, or setting up multiple machines with different GPUs).

This is of course assuming there isn’t already an option for this, how do you currently use that value?

2 Likes

Thank you for the response ! I felt the thing I was trying was not entirely a best practice so I’ll be happy to find a more idiomatic approach. I tried previously the error approach but it failed.

To give a bit more context, my flake is composed of a set of machine definition. In each of them I reuse modules to define base system, DE system and users (with Home-manager).
I simply include the correct modules depending on the machine.
I may need to inject specific value for a machine so it is the reason why I would like to have parameters and default value.

Now if I add the option to the top of the module defining the user I get this error;

error: Module `<modulepath>' has an unsupported attribute `home-manager'. This is caused by introducing a top-level `config' or `options' attribute. Add configuration attributes immediately on the top level instead, or move all of them (namely: home-manager users) into the explicit `config' attribute.
(use '--show-trace' to show detailed location information)

The file contains the option, the declaration of a user with users.users.<username> and a home definition with home-managers.users.<username>

If you have settings that are different per machine, set them in a module that is unique to that machine.

In general, the consensus seems to be to have one “entrypoint” per host/machine and include all other modules unconditionally, while having some enable options in the modules and switch them on in the “entrypoint”, rather than adjusting the list of modules/imports manually, or even worse, conditionally importing modules…

1 Like

If you want to put that options definition in the same module as some other configuration, you can’t use the shorthand form. See the wiki to compare the two forms. You’ll have to separate out the configuration like so:

{lib, pkgs, ...}: {
  options.local.cursorThemePackage = lib.mkOption {
    default = pkgs.capitaine-cursors;
    type = lib.types.package;
  };

  config = {
    # For example, any other module-level config should go here
    home-manager.users.eve = { pkgs, ... }: {
      home.packages = [ pkgs.atool pkgs.httpie ];
      programs.bash.enable = true;
    };

    users.users.eve = {
      isNormalUser = true;
    };
  };
}

That said, I don’t recommend this either; just put the option definition in a separate file and import that instead, you shouldn’t mix options with unrelated configuration if you can avoid it.

Thanks that does the trick !
It matches correctly with my module as I am using the option in it.

1 Like