How to use a module returned by a function in NixOS system configuration

Hi,
I’m new to Nix and I’m trying structure my NixOS configuration. Therefore I created the following file which should eventually hold all settings of a specific user:

# users/tom/default.nix

{}: {
    systemSettings = { config, ... }: {
        imports = [];

        # Apply base user system settings
        inherit ((import ../base.nix).systemSettings {});

        # Apply user specific user system settings
        config = {
            users.users.tom = {
                # Set password
                initialPassword = "changeme";
            };
        };
    };

    homeSettings = { ... }: {
        # TODO
    };
}

As far as I understand, a module is a function taking the current config and returning a set with three keys (imports, config, options). Therefore the function systemSettings returns a module, correct?

To group all users together I created this file:

# users/default.nix

{}: {
    tom = import ./tom {};
}

Now I want to use the module the function returns in my NixOS configuration but I don’t know how to provide the config parameter. The code looks like this:

{
    description = "NixOS Configuration";

    inputs = {
        nixpkgs.url = "nixpkgs/nixos-22.11";
        home-manager = {
            url = "home-manager";
            inputs.nixpkgs.follows = "nixpkgs";
        };
    };

    outputs = { nixpkgs, home-manager, ...}@inputs:
        let            
            inherit (nixpkgs) lib;
            pkgs = import nixpkgs {
                inherit system;
                config.allowUnfree = true;
            };
            system = "x86_64-linux";

            # Import users
            users = import ./users {};

        in {
            nixosConfigurations = {
                nuc = lib.nixosSystem {
                    inherit system;

                    modules = [
                        {
                            # Base system configuration
                        }

                        {
                            imports = [];
                            options = {};
                            # Use users.tom.systemSettings
                            config = users.tom.systemSettings {
                                inherit super; # How to get "config"?
                            };
                        }
                    ];
                };
            };
        };
}

Thank you very much in advance for reading through all of this. I’m fairly new to Nix so my code is probably far from perfect. But I appreciate any help (regarding this topic or other stuff that’s wrong about the code) :smiley:

ok, so there’s a lot to unpack here. first of all: the file you import need not be a function! it could just as well be an attrset (by leaving out the {}:) that can be just imported with being called directly after (ie, import ./users instead of import ./users {}).

at that point you have a file that when imported returns an attrset with two values, each of which is a module. each of these values can just be stuck in imports to use it. the module system has functions as its base unit of composition, if you list a path in imports it’ll actually be imported and treated (almost) as if the value you get from that import had been listed in imports. to put it another way: systemSettings does not return a module, it is itself a module. (nitpicking: it does also return a module, but that’s because the module system wraps plain attrsets in {...}: <value> before processing them further. calling module functions yourself should usually be avoided because the ... actually hides quite a lot of parameters)

also: inherit (thing); is a no-op. inherit copies attributes into the attrset it is contained in, but you have to list which attrs you want to have copied. it’s not an OOP-style inheritance relation, more of a “take these from there”. in your example you probably want to imports = [ ((import ../base.nix).systemSettings) ] instead!

at this point it becomes easier to handle modules by using paths for namespaces and putting each attribute into a file, turning (import ../base.nix).systemSettings into just ../base/systemSettings.nix.

now for tour final question:

you’d do that like this:

(args: { config = users.tom.systemSettings args; })

which is just a more complicated and less debuggable way way of writing

{ imports = [ users.tom.systemSettings ]; }
1 Like

I just tried it and it works! Thank you so much @pennae :partying_face: You really helped me a lot and I’ve been stuck with this for a couple of days. I really appreciate your help :smiley:
On small question about inherit: Is inherit (thing); a no-op because I need to specify what to inherit from thing like inherit (thing) subthing; for it to actually inherit something?

yep. inherit is pretty much just syntactic sugar, where inherit x y ...; is transformed into x = x; y = y; ... and inherit (thing) x y ...; turns into x = (thing).x; y = (thing).y; .... notably thing is (currently) evaluated fully for each inherited item, so if you inherit many names from a thing that is expensive to compute you’ll pay that price many times (unless you bind your thing in a preceding let).

1 Like

When having an recursive set there is an importand difference:

nix-repl> let a = 1; in rec { inherit a; }
{ a = 1; }

nix-repl> let a = 1; in rec { a = a; }     
{ a = error: infinite recursion encountered

       at «string»:1:25:

            1| let a = 1; in rec { a = a; }
             |                         ^
1 Like