Multiple users with home-manager as NixOS modules

Hi, I have an entrypoint to a NixOS setup here: nexpr/nixos/default.nix at 693821284ef144888e53891132006196a2f2867f · soyart/nexpr · GitHub

I write most of the config as modules, with special module arguments mainUser (str) and username (str).

My intention is to only have 1 mainUser, and multiple usernames that I’d override from the entrypoint, like this:

{ inputs, ... }:

let
  inherit (inputs.nixpkgs.lib) nixosSystem;

  mkHost = {
    modules,
    mainUser,
    hostname ? "mysystem",
    stateVersion ? "23.11",
    system ? "x86_64-linux",
  }: nixosSystem {
    inherit system modules;

    specialArgs = {
      inherit hostname mainUser inputs stateVersion ;
    };

  };
in {
  "t14" = mkHost {
    hostname = "t14";
    mainUser = "user1";

    modules = [
      ./hosts/t14

      # Use home-manager as NixOS modules
      ({ inputs, config, ... }: {
        imports = [
          inputs.home-manager.nixosModules.home-manager
        ];

        config.home-manager = {
          useGlobalPkgs = true;
          useUserPackages = true;
          extraSpecialArgs = { inherit inputs; };
        };
      })

      # User 1 module
      ({ config, ... }: {
        imports = [
          ../presets/sway-dev
        ];

        config._module.args = {
          username = "user1";
        };
      })

      # User 2 module, with identical config
      ({ config, ... }: {
        imports = [
          ../presets/sway-dev
        ];

        config._module.args = {
          username = "user2"; # << error, username already set to user1
        };
      })
    ];
  };
}

The intention for the system above is to have 2 users user1 and user2 sharing
the exact same configuration presets.

Other modules that ../presets/sway-dev import all accept username, and most of the modules have something like this inside:

{ config, username, ... }:

{
  config = {
    someSystemConfig = "foo";
    
    home-manager.users."${username}" = {
      # home-manager user config
      # ...
    };
  };
}

However, as you all might have already known, config._module.args is global, and therefore user1 and user2 will conflict.

Is there any way I could override the username module argument without having to rewrite most of my modules and structure?

I just want to be able to inject a username and then only import the configuration modules and all those modules will just accept a new username. These user modules should evaluate multiple home-manager.users.${user1, user2} separately by itself before finally merging with the “global” config.

I’m thinking about using lib.evalModules to separately evaluate each user module, with different usernames. Or mkMerge [ (modulesUser1) (modulesUser2) ].

Any examples or advices are welcomed! Or any “idiomatic” alternative structures are also appreciated.

Thanks in advance!

Have you seen the home-manager.sharedModules setting? Unless I’m misunderstanding, sounds like exactly what you want (having users share home-manager configurations). Any modules added to that list will be applied to all the users managed by home-manager on the system.

Thanks sir! I’ll have a look. Was implementing something like this on my own though, but it seems like code smell so I’ll try sharedModules.

My simple solutions

I just implemented my own simple (stupid) way to do user-specific modules: use factory functions.

The functions are simple: they take in username and returns the user-specific modules, like this:

username:

{ config, lib, ... }:

with lib;
with lib.types;

let
  cfg = config.foo.home."${username}";

in {
  options = {
    foo.home."${username}" = {
      bar = mkOption {
        type = int;
        default = -1;
      };
    };
  };

  config = {
    home-manager."${username}".someConfig = cfg.bar;
  };
}

Now, module consumers will have to use import ./path/to/mod "user1" to get the module specific to user user1.

Next step

I’ll have a look at home-manager.sharedModules

The main obstacle to this approach is that some of my modules touch both home-manager and NixOS system configs, which (I think) will cause eval error due to undefined options in home-manager.