Infinite recursion when importing a dynamically generated list of modules based on config

I created a custom set of options, which contains an attribute set of user settings. Attribute name is the username, attribute value is their settings.

Now I want to import a module for each user, passing the user’s userSettings as argument. This module will generate the user’s user and home-manager configs. The goal is to be able to simply define my list of users and all will be setup according to their personal config.

When I do this for a specific user there is no problem, but as soon as I pre-compute the list of modules to import (with mapAttrsToList) I get infinite recursion.

Here is the code:

{ config, lib, ... }:

# import create-user.nix for each user listed in config.sharedConfig.users

let
  getUserConfigModule = userSettings: (import ./create-user.nix { inherit userSettings; });

  allUserSettings = lib.attrsets.mapAttrsToList (name: value: value) config.sharedConfig.users;
  userModules = map (userSettings: getUserConfigModule userSettings) allUserSettings;

  #usri = getUserConfigModule (builtins.head allUserSettings);
in
{
  imports = userModules; # error: infinite recursion encountered
  # imports = [ usri ]; # works
}

The whole project is here: gaj-nixos / Shared Nixos · GitLab
I run nix flake check --show-trace to see the error.

Main files of interest are (the error occurs in the 3rd one):

I’ve been searching and trying things out for 10 hours on this problem and I feel I hit a brick wall.

Any help would be really appreciated. Thanks !

Of course, imports cannot depend on config since config already depends on imports.

What you probably meant to use was submodules.

Hey thanks for responding so quickly! Any idea what would be the proper way to do this ?

https://nixos.org/manual/nixos/stable/#section-option-types-submodule

You probably want attrsOf submodule here, then you can use a different config binding within the submodule for user-specific config. There are many such examples throughout nixpkgs if you search attrsOf submodule.

This is what I did already in options.nix:


          users = mkOption {
            type = with types; attrsOf (submodule userSettings);
            default = {};
            description = "Per-user settings";
          };

But I still don’t understand how to pass each userConfig that are under the attribute users as an argument of the create-user module…

Also why does it work when I pass a list containing just the first user, obtained with builtins.head ? It was obtained from config just the same way as with the failing method.

I tried modifying the userSettings module to add a config parameter and an imports but then it seems my user configs are not loaded at all.


  getUserConfigModule = userSettings: (import ./users/create-user.nix { inherit userSettings; });

  allUserSettings = config: lib.attrsets.mapAttrsToList (name: value: value) config.sharedConfig.users;
  userModules = config: map (getUserConfigModule (allUserSettings config));


  userSettings = { config, ... }: {
    imports = (userModules config);
    options = {
      username = mkOption {
        type = str;

Why don’t you just use the inner config module arg in the module itself?
Or if you want to set config for options outside of the submodule, do what you were doing but without using imports.

Hey thanks for your answers but I’m still confused. I did use config in the module itself but that’s not the problem.

What I want is, for each user in my config, to inject the userConfig of that user as argument in the imported module and I want to import the parameterised module for each defined user.

So the config looks like


            config = {
              system.stateVersion = "24.05";
              boot.loader.grub.devices = [ "/dev/sdx" ];
              sharedConfig = {
                homeStateVersion = "24.05";
                partitioning = "simple";
                users = {
                  "user1" = {
                    username = "user1";
                    email = "a@b.c";
                  };

                  "user2" = {
                    username = "user2";
                    email = "a2@b.c";
                  };
                };
              };
            };

and what I want is to have something so “convert” this to a complex config such as :

config = {
  users.users."user1" = {
    emailAddress = "a@b.c";
    [...]
  };
  users.users."user2" = {
    emailAddress = "a2@b.c";
    [...]
  };
};

I tried to follow your advice to get rid of imports (even though I haven’t understood why imports is specifically bad in this case) and so I figured out a different approach where I replaced lib.attrsets.mapAttrsToList and imports with lib.mkMerge and lib.genAttrs:

{ config, pkgs, lib, ... }:

let
  userConfigModule = import ./create-user.nix;
  usernames = builtins.attrNames config.sharedConfig.users;

in
{
  config = lib.mkMerge (
    lib.genAttrs usernames
      (username: userConfigModule { userSettings = config.sharedConfig.users.${username}; })
  );
}

where create-user.nix is just, for the time being:

{ userSettings, ... }:

{
}

and I still get infinite recursion :sob:
I also tried replacing config.foo with config.options.foo but it didn’t work either.

the solution is to not set the top level attribute (config) directly.
So config = myConfig gives infinite recursion but
config.home-manager = myConfig.home-manager will work.