14

Let's say I have my NixOS configuration.nix set up as follows:

{config, pkgs, ...}:
{
  services.openssh.enable = true;
}

I now want to have a second file called networking.nix which sets my hostname based on an argument.

{config, pkgs, hostname, ...}:
{
  networking.hostName = hostname
}

Is this possible? How can I include the file. I already tried doing it by using imports = [ ./networking.nix { hostname = "helloworld"; } ]; but that didn't work.

Thanks.

lschuermann
  • 862
  • 1
  • 7
  • 17
  • Did you find a good solution for this? I'm wondering this myself. – PureW May 04 '19 at 12:39
  • I solved this with the module options described under https://nixos.org/nixos/manual/index.html#sec-writing-modules – PureW May 04 '19 at 13:13

3 Answers3

12

I encountered this problem today and came up with a fairly simple solution recommended in the manual.

foobar.nix

{ lib, withFoo ? "bar", ... }:

# simple error checking to ensure garbage isn't passed in
assert lib.asserts.assertOneOf "withFoo" withFoo [
  "bar"
  "baz"
  # other valid choices ...
];

{
  # ...
}

configuration.nix

args@{ ... }:
{
  imports = [
    # ...
    (
      import ./foobar.nix (
        args
        // { withFoo = "baz"; }
      )
    )
    # ...
  ];
}

This is ideal for 'one off' options in your configurations.

Mateen Ulhaq
  • 24,552
  • 19
  • 101
  • 135
nrdxp
  • 674
  • 7
  • 12
8

A 'NixOS configuration file' is simply a module that doesn't define options, so there is really no distinction. A configuration.nix file is just a module, and typically it does not define any options, so it can be written in the abbreviated form.

Defining options is the normal way for NixOS modules to pass information around, so that's the most idiomatic way to go about.

However, if you really must, for some very special reason, because you're doing very unusual things with NixOS, you can put arbitrary functions in imports. But you shouldn't, because it doesn't work well with the module system's custom error messages and potentially other aspects that rely on knowing where a module is defined. If you do so, do make sure it is an actual function. In your case, that would imply modifying the first line of networking.nix to make it a curried function:

hostname: {config, pkgs, ...}:

Not very pretty in my opinion. Although it is very explicit about what is going on, it deviates from what is to be expected of a NixOS module.

Robert Hensing
  • 6,708
  • 18
  • 23
  • I don't know whether I'm doing some unusual things with NixOS. My goal was to have a set of VMs share a common configuration. Only some parameters of these VMs should be change like for example the hostname or IP addresses. I thought importing configurations for these common services with parameters was the natural choice. Do you have a better approach? Or is the philosophy I'm after completely wrong? Sorry, I'm a total newbie when it comes to NixOS. :( – lschuermann Dec 09 '17 at 14:15
  • We should try to keep things simple, which is a subjective matter. We can use three methods of injecting values into a piece of configuration. 1. via NixOS module options; 2. via a module that is parameterized as a Nix function; 3. via the special `_module.args` option. Module options are used everywhere in NixOS, so you should try that first. It's probably sufficient and you get some type checking and a custom NixOS manual for free. I've only needed parameterized modules when I wrote a package depend on a VM image. `module.args` is probably the wrong tool. I'm not here to judge your code ;) – Robert Hensing Dec 11 '17 at 14:43
  • The `hostname: {...}:` solution should be avoided if possible because it works around the module system. Working around the module system will cause issues such as the lack of proper location in error messages, and the inability to remove them from third-party modules. For your information, this format still exists for legacy reasons and the fact that it is used for NixOS test suite. – nbp Dec 17 '17 at 14:55
  • @RobertHensing Could you add a link to the module options described under https://nixos.org/nixos/manual/index.html#sec-writing-modules ? When I first read your answer, I didn't really understand what you were talking about (I'm fairly new to nixos) but reading that manual page in conjunction helped me solve this. – PureW May 04 '19 at 13:14
6

You should be able to use the _module.argsoption [1] to do that. So your configuration.nix would be like:

{config, pkgs, ...}:
{
  _module.args.hostname = "ahostname";
  services.openssh.enable = true;
}

However where the values are very simple it will probably be much easier to just set them directly, e.g. just define networking.hostname in configuration.nix. This section of the manual re. merging and priorities may be helpful also [2].


Further discussion:

The value of _module.args is indeed applied to all imported configurations (though the value will only be used in modules that directly refer to it, such as the pkgs value, the ... represents all the values that aren't referenced).

For passing arguments to modules it seems a good approach to me, but from your comments perhaps a different approach might be more suitable.

Another solution could be to flip the relationship in the imports: rather than a single common config that passes multiple different arguments instead multiple different configs import the common configuration. E.g.

$cat ./common.nix
{ services.openssh.enable = true; }
$cat ./ahostname.nix
{ imports = [ ./common.nix ]; networking.hostname = "ahostname"; }

The NixOS config in this Reddit comment looks like it uses this approach. There are quite a few other NixOS configurations that people have shared publicly online so you might find some useful ideas there. The points in the answer from Robert Hensing are very useful to bear in mind as well.

However it's hard to say what might be a better solution in your case without knowing a bit more about the context in which you want to use it. You could create a new SO question with some more information on that which might make it easier to see a more appropriate solution.

brocking
  • 821
  • 6
  • 9
  • Your solution, whilst definitely working, doesn't seem to be the right approach for me. With this, I'm defining arguments for all files imported via imports, right? My goal is, as written under Robert's answer, to have a set of VMs share some common configuration files and only change a few parameters they should not share. What would be the most idiomatic way to achieve this? – lschuermann Dec 09 '17 at 14:18
  • I've added some further discussion in my answer. Hopefully that's a bit more useful for the particular context you're working in. – brocking Dec 13 '17 at 23:49