Passing in extra arguments to configuration.nix

I’m trying to pass extra arguments into configuration.nix, so I can conditionally activate options based on whether the machine in question is, for instance, a desktop computer or not. This is currently how I’m going about it. The relevant bits of my system flake.nix:

mkNixosConfiguration = machineSpecific: nixpkgs.lib.nixosSystem {
      system = machineSpecific.system;
      modules = [
        ({ pkgs, ... }: {
          nixpkgs.overlays = [
            # ... stuff here ...
          ];
        })
        # why do we need to add the pkgs argument here? is it only passed if explicitly specified?
        (args @ { pkgs, ... }: import ./configuration.nix ({
            inherit machineSpecific;
        } // args))
        home-manager.nixosModules.home-manager {
          # Use the system nixpkgs, not home-manager's own
          # This causes it to use our overlays
          home-manager.useGlobalPkgs = true;
          home-manager.useUserPackages = true;
          home-manager.extraSpecialArgs = { inherit machineSpecific; };
          home-manager.users.<my username> = import ./home.nix;
        }
      ];
    }
nixosConfigurations.<hostname> = mkNixosConfiguration {
        system = "x86_64-linux";
        isDesktop = false;
}

Is there a better way to do this? Is there anything wrong with doing it this way?

I ask because it’s suggested here that it’s recommended not to do what I’ve done with configuration.nix, since the config variable might be incorrect or something? Which I don’t totally understand, since I’m passing along whatever would’ve been passed to the module in the first place.

Also, what’s going on with the pkgs argument to the function that imports ./configuration.nix? pkgs isn’t propagated into configuration.nix unless I explicitly specify it as an argument to that function. Is this intended behavior?

Would someone clarify? Thanks!

Unfortunately I can’t link to documentation because it’s not rendered for some reason (I should fix that), but the _module.args option should be used to add custom arguments:

So only this is needed, no custom configuration.nix import:

machineSpecific: nixpkgs.lib.nixosSystem {
  system = machineSpecific.system;
  modules = [
    # ...
    ./configuration.nix
    {
      _module.args = { inherit machineSpecific; };
    }
  ]
}

About your understanding questions, the weirdness is caused due to the way the module system needs to call arguments lazily (to support _module.args itself), you can read more about it in the implementation here: https://github.com/NixOS/nixpkgs/blob/dedffcef4d55d009ed0220157f0203ce875279c8/lib/modules.nix#L489-L513.

The benefit of _module.args however is that you can use this new argument in any module, not just configuration.nix :slight_smile:

2 Likes

Another option would be to set specialArgs in the call to nixosSystem.

While all the previous answers are technically correct, AFAIK the classic (and less digging-in-the-bowels-of-the-module-system) way is to declare and import a module with an option of appropriate type (e.g. Boolean for isDesktop or an enumeration or whatever suits your needs) and set your configuration values depending on what that option is set to in a given configuration.

2 Likes

The issue with incorrect config in the linked thread was with the comment preceding the one you linked. The pattern you use (and what I used as an example) is fine, just unnecessarily verbose. Well, in your case the verbosity is needed for your chosen approach but there are better ways as mentioned by Silvan and Jake. Plus the subjectively nicer, although more verbose option proposed by Valentin.

1 Like

Oh yeah very good point. Module arguments are really just untyped and non-namespaced options. Creating a custom option should be preferred over module arguments. In this case it would look like this:

machineSpecific: nixpkgs.lib.nixosSystem {
  system = machineSpecific.system;
  modules = [
    # ...
    ./configuration.nix
    ({ lib, ... }: {
      options.machineSpecific = lib.mkOption {
        type = lib.types.bool;
        default = machineSpecific;
      };
    })
  ]
}

Then to refer to the options value, use config.machineSpecific from any module, just like any other NixOS option.

3 Likes

Thanks for the help everyone-- this all makes sense now. :slight_smile:

Ah, alright-- good to know!