Passing variable from flake.nix down to modules

Hi there,

I’m trying to pass down some config variables to modules. For instance in my flake.nix I have this hosts set in the let..in to avoid repeating the config for every host. Thing is, I could use this hostName inside my configuration.nix file for say networking.hostName or even in a HomeManager configuration where a script is based on the hostname.

So my question is, how to make sure this variable is usable inside my configuration.nix and my home.nix files? And generally speaking how to pass this set of variables that are host specific, is using the let .. in even a good approach, or is there a better one to make those parameters reachable everywhere?

Thanks in advance !

#flake.nix
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
    unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
    agenix = {
      url = "github:ryantm/agenix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    home-manager = {
      url = "github:nix-community/home-manager/release-24.11";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    nixos-hardware.url = "github:NixOS/nixos-hardware/master";
    nix-flatpak.url = "github:gmodena/nix-flatpak";
    impermanence.url = "github:nix-community/impermanence/master";
  };
  outputs = { self, nixpkgs, unstable, home-manager, nixos-hardware, agenix, nix-flatpak, impermanence }:
  let
    system = "x86_64-linux";
    allowUnfree = { nixpkgs.config.allowUnfree = true; };
    hosts = {
      mushroom = {
        hardwarePresets = nixos-hardware.nixosModules.some-thing;
      };
      sunbase = {
        hardwarePresets = nixos-hardware.nixosModules.some-thing-else;
      };
    };
  in {
    nixosConfigurations = nixpkgs.lib.genAttrs (nixpkgs.lib.attrNames hosts) (hostName: nixpkgs.lib.nixosSystem {
        inherit system;
        specialArgs = {
          unstablePkgs = unstable.legacyPackages.${system};
        };
        modules = [
          hosts.${hostName}.hardwarePresets
          impermanence.nixosModules.impermanence
          allowUnfree
          ./nixos/configuration.nix
          home-manager.nixosModules.home-manager
          {
            home-manager = {
              useGlobalPkgs = true;
              useUserPackages = true;
              users.martin = import ./home/home.nix;
              extraSpecialArgs = {
	            unstablePkgs = import unstable {
                      config.allowUnfree = true;
                      inherit system;
                    };
	            agenix = agenix;
	            agenixPkgs = agenix.packages.${system};
	            nix-flatpak = nix-flatpak;
	          };
            };
          }
        ];
      });
  };
}

You can pass anything via specialArgs for NixOS and extraSpecialArgs for Home Manager.

If you need a configuration only for either, I’d recommend using modules instead.

So I need to pass it to both ? Does using a module instead would solve having to repeat it ?

If you need it in both places, I think so, and no, a module would not avoid the repetition because NixOS modules and HM modules are different things if I’m not mistaken.

Though, in this example it seems you only need it for your NixOS configuration?

Another option is to use overlays (assuming both NixOS and HM share the same pkgs instance), which avoids the repetition of having to do things twice. E.g. assuming the following overlay:

f: p: {
  erwyn.hosts = { ... };
}

Then you could access it from anywhere via pkgs.erwyn.hosts.

Though, in this example it seems you only need it for your NixOS configuration?

I actually do, Home Manager is using this hostName for a shell alias for instance.

Another option is to use overlays (assuming both NixOS and HM share the same pkgs instance),

I see. I’m not sure it feels kind of hacky but that gave me an idea: would it be possible to pass it to config instead ?

I’m actually asking this because I just found Passing parameters into import - #4 by Infinisil but I’m not sure how I would pass the hostname to this module so that any other part of the config could access it through config.myConfig.hostName. Or if it’s even a thing actually…

It may be, as you’re using Home Manager as a NixOS module, you can always try. I use HM standalone, so I don’t know.

If this works, it would be the most elegant solution I believe, nothing better than modules :slight_smile:

Would you happen to have any guidance or pointers/examples? Following the post I linked I kinda see how to create this module, but what I don’t know is how to include it into my flake.nix and give it the proper parameters.

I don’t have examples for that exactly, but I can try to help you here. You’d have a module modules/hardware-presets/default.nix (name up to you):

{ lib, config, ... }: 

{
  options = {
    hardware.presets = lib.mkOption {
      type = lib.types.attrs;
      default = config.hosts.${config.networking.hostName}.hardware.presets;
      description = "hardware presets for the specific hostname";
    };

    hosts = {
      mushroom.hardware.presets = lib.mkOption {
        type = lib.types.attrs;
        description = "something";
      };
      sunbase.hardware.presets = lib.mkOption {
        type = lib.types.attrs;
        description = "something else";
      };
    };
  };

  config = {
    modules = [ config.hardware.presets ];
  };
}

Then in your configuration.nix you’d need to set values for each hosts’ presets (nixos-hardware needs to be passed via specialArgs):

{
  hosts = {
    mushroom = {
      hardware.presets = nixos-hardware.nixosModules.some-thing;
    };
    sunbase = {
      hardware.presets = nixos-hardware.nixosModules.some-thing-else;
    };
  };
}

Or you can also write this configuration inline in the modules = [ ... ] part on flake.nix if it’s short enough.

Then you glue it all together by importing your custom module in your flake.nix as follows:

modules = [
  ./modules/hardware-presets
  impermanence.nixosModules.impermanence
  allowUnfree
  ./nixos/configuration.nix
];

Hope this helps!

Thanks for this example! I think I understand better how it is supposed to be glued together but there still might be some blind spots on my side.

I have one issue I see here:

  • If I’m understanding correctly what’s done here, the config.networking.hostName attr is used to decide which hardware profile to load. In my case I would also like networking.hostName to be determined by name of the nixosConfiguration which is itself based on a list entries in the set. Each configuration has a name, the name has to be used as both hostName of the machine AND used to determine the hardware profile to load.

I feel like:

  1. There should be a descriptive set, somewhere, maybe in another nix file where I can have this:
hosts = {
    mushroom = {
      hardware.presets = nixos-hardware.nixosModules.some-thing;
      otherWhateverHostSpecificInfo = infoForMushroom;
    };
    sunbase = {
      hardware.presets = nixos-hardware.nixosModules.some-thing-else;
      otherWhateverHostSpecificInfo = infoForSunbase;
    };
  };
  1. This set would be available (how ?) in my flake.nix to generate all configurations based on:
nixosConfigurations = nixpkgs.lib.genAttrs (nixpkgs.lib.attrNames MySetComingFromWherever) (hostName: nixpkgs.lib.nixosSystem {
  ...
}
  1. And then, and I really don’t know how, maybe through the module, I need a few things set:
  • networking.hostName
  • whatever config coming from the nixos-hardware module selected from the hostName
  • whatever other config based on the otherWhateverHostSpecificInfo attrs

So I feel like the module needs to know the configuration’s name and can’t rely on the networking.hostName to determine it as it will set it itself, and the descriptive set needs either to be somewhere else and passed both to the module AND the flake.nix or be in the module itself and reachable from the flake.nix.

Does that make any sense ?

Yes, use an anonymous module in your flake.nix that you put in modules.
Then, access your NixOS config via osConfig in HM.

Not at all, you can manually choose a configuration using nixos-rebuild --flake FLAKE#CONFIGNAME. nixos-rebuild just happens to choose the configuration that matches your hostname, by default.

Agreed. What I meant is the module can’t rely on networking.hostName as it will iself declare it. So somewhere we need to have the name of the config available to decide which module/specificities apply there.

So put it in the flake.nix? I don’t understand the concern.

The context is that I have 2 hosts on which I want to deploy the same configuration but for a few minor adjustements. As of today those adjustments are:

  • The networking.hostName
  • A script that is deployed via HomeManager and that relies on the hostname of the machine
  • The nixos-hardware module

Although that’s all I need today, I’m trying to grasp the way of doing it properly so that when I need to introduce more specifities later on I know how to get there.

In order to do that, I started by introducing a set in the let...in of my flake.nix file that would describe each possible host and its specifities like this:

let
    system = "x86_64-linux";
    allowUnfree = { nixpkgs.config.allowUnfree = true; };
    hosts = {
      mushroom = {
        hardwarePresets = nixos-hardware.nixosModules.some-thing;
      };
      sunbase = {
        hardwarePresets = nixos-hardware.nixosModules.some-thing-else;
      };
    };
  in {

And then I “generate” the nixosConfigurations in my flake.nix like this:

    nixosConfigurations = nixpkgs.lib.genAttrs (nixpkgs.lib.attrNames hosts) (hostName: nixpkgs.lib.nixosSystem {
        inherit system;
        specialArgs = {
          unstablePkgs = unstable.legacyPackages.${system};
        };
        modules = [
          hosts.${hostName}.hardwarePresets
          impermanence.nixosModules.impermanence
          allowUnfree
          ./nixos/configuration.nix
          home-manager.nixosModules.home-manager
          {
            home-manager = {
              useGlobalPkgs = true;
              useUserPackages = true;
              users.martin = import ./home/home.nix;
              extraSpecialArgs = {
	            unstablePkgs = import unstable {
                      config.allowUnfree = true;
                      inherit system;
                    };
	            agenix = agenix;
	            agenixPkgs = agenix.packages.${system};
	            nix-flatpak = nix-flatpak;
	          };
            };
          }
        ];
      });

This works well for generating each conf and having the correct nixos-hardware module set up, but I don’t know how to provide the hostName variable that is the configuration name from the set to my configuration.nix and home.nix so that I can set networking.hostName and my script with it.

@gvolpe suggested using a module which seems to my novice eyes like a great idea. The thing is that in my understanding the proposed solution moves the descriptive set to configuration.nix which I kinda dislike and make it unavailable in my flake.nix to generate the configurations; also, it relies on the networking.hostName value to choose the nixos-hardware module to select but I don’t know it as I need to get this hostName variable from the flake.nix file to know what the actual hostname of the machine is based on the configuration’s name.

So, I would like to iterate from there (or not using modules if it’s not the correct tool here) so that the descriptive set is somewhere usable in the flake.nix for the configurations generation and in the module itself. Or whatever other solution that is more appropriate.

I hope it’s clearer?

What’s stopping you from adding a line below that with an anonymous module?

{ networking = { inherit hostName; }; }

Then you don’t need an additional module arg in that case.

Nothing apart from the fact that I would like to know how not to rely on that and passing args down to my modules.

I would like to have this in a module and pass the hostName down there.

Wasn’t specialArgs and extraSpecialArgs mentioned above? Either use those to create a new module arg, or use the anonymous module; those are your options. Unless I am misunderstanding your question entirely.

EDIT: Or, there is the option to wrap your flake itself in a lib.evalModules call, in which case you can define options (or args) at the flake level, and pass those into your config. That’s essentially what flake-parts does, though you may find it overkill in some aspects.

Right, my bad, I discarded it because of my lack of knowledge and because we were talking about a slightly different solution when it was first mentioned but now I see that this is actually the way to provide more context to modules.

I’ll try that, thanks !

Hey @waffle8946 and @gvolpe!

Thank you both of you for your invaluable inputs that helped me grasping the concepts further and what I was trying to achieve. You’ll tell me if this is completely wrong, but in order to provide closure to this thread I wanted to show what I ended up implementing in case anybody ends up here. This seems to work well and is essentially a merger of both your inputs plus a bit of toying around with map and mergeAttrsList.

1. Having a hostsInventory.nix file to describe my different hosts

I create a hostsInventory.nix file that look like this:

{ nixos-hardware, ... }:
[
  {
    hostname = "mushroom";
    hardwarePresets = nixos-hardware.nixosModules.mushroom-hardware-specification;
    hostId = "whateverhostid";
  }
  {
    hostname = "sunbase";
    hardwarePresets = nixos-hardware.nixosModules.sunbase-hardware-specification;
    hostId = "anotherhostid";
  }
]

It needs nixos-hardware for the hardwarePresets attribute

2. Creating a module to set host specific attributes

I created a module specificHostConfig.nix that reads a hostConfiguration in order to set config attributes that are host specifics, appart from the nixos-hardware module that need to be set directly in flake.nix:

{ config, hostConfiguration,... }:
{
  config = {
    networking = {
      hostName = hostConfiguration.hostname;
      hostId = hostConfiguration.hostId;
    };
  };
}

3. Putting it all together in the flake.nix

In my flake.nix file I changed the nixosConfigurations to be generated from the hostsInventory.nix and to pass the hostConfiguration in the specialArgs so that the specificHostConfig.nix module could read it.

I also imported the specificHostConfig module in my modules and imported the correct nixos-hardware module based on the host entry in the inventory:

    nixosConfigurations = nixpkgs.lib.mergeAttrsList (map (host: { ${host.hostname} = nixpkgs.lib.nixosSystem {
        inherit system;
        specialArgs = {
          unstablePkgs = unstable.legacyPackages.${system};
          hostConfiguration = host;
        };
        modules = [
          ./modules/specificHostConfig.nix
          host.hardwarePresets
          impermanence.nixosModules.impermanence
          allowUnfree
          ./config/nixos/configuration.nix
          home-manager.nixosModules.home-manager
          {
            home-manager = {
              useGlobalPkgs = true;
              useUserPackages = true;
              users.martin = import ./config/home/home.nix;
              extraSpecialArgs = {
	              unstablePkgs = import unstable {
                  config.allowUnfree = true;
                  inherit system;
                };
	              agenix = agenix;
	              agenixPkgs = agenix.packages.${system};
	              nix-flatpak = nix-flatpak;
	            };
            };
          }
        ];
      };}) (import ./hostsInventory.nix { inherit nixos-hardware; }) );

Glad you found a way :slight_smile:

If I may suggest further optimization to reduce boilerplate, I think that your second step can be avoided completely by defining those settings directly in your hostsInventory.nix file, e.g.:

{ nixos-hardware, ... }:
[
  {
    hostName = "mushroom";
    config = { hostName, ... }: [
      hardwarePresets = nixos-hardware.nixosModules.mushroom-hardware-specification;
      networking = {
        inherit hostName;
        hostId = "whateverhostid";
      };
    ];
  }
  {
    hostName = "sunbase";
    config = { hostName, ... }: [
      hardwarePresets = nixos-hardware.nixosModules.mushroom-hardware-specification;
      networking = {
        inherit hostName;
        hostId = "anotherhostid";
      };
    ];
  }
]

This way you don’t need to pass the hostConfiguration via specialArgs, and in your flake.nix, the modules bit may look as follows:

modules = [
  (host.config { inherit (host) hostName; })
  impermanence.nixosModules.impermanence
  allowUnfree
  ./config/nixos/configuration.nix
  home-manager.nixosModules.home-manager
];

It may need some tweaks to actually build, as I didn’t test this, but hope you get the idea.