Multiple machines/users

I’ve had a multi-user solution working for nixos-module / home manager working for awhile. I would like to clean it up to also work for multiple machines. Ideally the flake would: detect the right host of the current system and pick the right output to derive. Current attempt:

{
  description = "Jevin's Humi Home Manager configuration";

  inputs = {
    # Specify the source of Home Manager and Nixpkgs
    home-manager.url                    = "github:nix-community/home-manager/release-22.05";
    nixpkgs.url                         = "github:NixOS/nixpkgs/nixos-22.05";
    nixos-hardware.url                  = "github:NixOS/nixos-hardware";
    nixpkgs-unstable.url                = "github:NixOS/nixpkgs/nixos-unstable";
    home-manager.inputs.nixpkgs.follows = "nixpkgs";
    nix-colors.url                      = "github:misterio77/nix-colors";
  };
  outputs = { home-manager, nix-colors, nixpkgs, nixpkgs-unstable, nixos-hardware, ... }@inputs: {
    home-manager.useGlobalPkgs = true;
    home-manager.useUserPackages = true;
    # Modeling it after: https://rycee.gitlab.io/home-manager/index.html#sec-flakes-nixos-module
    nixosConfigurations = {

      # Lenovo has hostname `nixos`
      nixos = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        specialArgs = { inherit inputs; }; # Pass flake inputs to our config
        modules = [
          ./configuration.nix
          ./hardware-configuration.nix
          nixos-hardware.nixosModules.lenovo-thinkpad-x1-7th-gen
          home-manager.nixosModules.home-manager
          {
            home-manager.users.jevin = {
              imports = [ ./jevin-linux.nix ];
            };
          }
          {
            home-manager.users.jevinhumi = {
              imports = [ ./work-linux.nix ];
            };
          }
        ];
        home-manager.extraSpecialArgs = { inherit nix-colors; };
      };

      # Other laptop goes here. Either Linux or Mac

    };

  };
}

When trying to run: nixos-rebuild switch --flake .#nixos, it results in:

error: anonymous function at /nix/store/kacay40ndc0mwqdqlfp88fzl3lzh8w53-source/nixos/lib/eval-config.nix:11:1 called with unexpected argument 'home-manager'

       at /nix/store/kacay40ndc0mwqdqlfp88fzl3lzh8w53-source/flake.nix:23:11:

           22|         nixosSystem = args:
           23|           import ./nixos/lib/eval-config.nix (args // {
             |           ^
           24|             modules = args.modules ++ [ {

I expect there are some conceptual issues I’m missing. Any ideas?

That’s in “outputs” somehow, they don’t brlong there, those are NixOS options if you add the home-manager NixOS module. That doesn’t cause your issue though.

Your configuration will evaluate fine if you remove that line (until, presumably, nix-colors is missing.

The nixosSystem function isn’t there to just place arbitrary options and have nix guess whether they should be evaluated with the NixOS or home-manager module system. It has very specific arguments, specifically it’s exposing eval-config: nixpkgs/eval-config.nix at 74b5398c530d09ee5e4e66e2b6383c2c50c904a6 · NixOS/nixpkgs · GitHub

That function is what evaluates the whole nixos module system. Before this function is executed, all the options you set are just entries into a bunch of json files without any meaning. Hence all the home-manager settings littered outside of it (variably in outputs and arguments that don’t exist) don’t actually do anything.

The home-manager.* options in turn are options defined by home-manager.nixosModules.home-manager. This means they are NixOS modules, and your definitions for them should go into NixOS modules as well.

I.e., do this:

        modules = [
          ./configuration.nix
          ./hardware-configuration.nix
          nixos-hardware.nixosModules.lenovo-thinkpad-x1-7th-gen
          home-manager.nixosModules.home-manager
          {
            home-manager = {
              useGlobalPkgs = true;
              useUserPackages = true;
              extraSpecialArgs = { inherit nix-colors; };
              users = {
                jevin = import ./jevin-linux.nix;
                jevinhumi = import ./work-linux.nix;
            };
          }
        ];

Note that I use import instead of imports. Since those are just inline modules that import a single other module, cutting the middle man looks nicer. If you want to import additional modules, just do it in the imports of those two.

Note also that this is an inline module:

          {
            home-manager = {
              useGlobalPkgs = true;
              useUserPackages = true;
              extraSpecialArgs = { inherit nix-colors; };
              users = {
                jevin = import ./jevin-linux.nix;
                jevinhumi = import ./work-linux.nix;
            };
          }

You can place the home-manager configuration in any of your other configuration files if you like, by the way, just use inherit (inputs) nix-colors;. I find keeping flakes simple is best, it should be an entrypoint, not implementation.

On another side note:

nixos-rebuild <switch/build/etc> --flake .# will automatically substitute the current host name for the configuration to evaluate.

If you’d like more inspiration, as it happens, I’ve been refactoring my own flake this weekend: dotfiles/flake.nix at tlater/integrate-nixos-hosts · TLATER/dotfiles · GitHub

Currently it has two systems like you do, and it’s finally all pretty clean. I’ll add at least one homeConfiguration at some point as well, hopefully today :slight_smile:

Well that worked fantastically well! Thank you for both the thorough explanation and the implementation examples.

I made these changes: Based on @tlater feedback · jevy/home-manager-nix-config@063b2cf · GitHub

On reflection on your feedback:

nixosSystem function isn’t there to just place arbitrary options

I still struggle with when and how to options so your comment is fair. Making everything a module does make more sense to me now.

specifically it’s exposing eval-config

I’m still not sure how I would have drawn the line to make the connection to that eval-config file and nixosSystem. The file itself does make the options really clear once you showed me the way.

I spent a week digging through errors back in 2020 to figure that one out, and eventually just read the source code, following from the function definition in flake.nix.

It’s not as hard as you’d think once you accept that you have to go source delving, but yeah, this is why I comment here these days. Helping others get past that initial hurdle.