NixOS modules: Difference between revisions

From NixOS Wiki
imported>Tv
talk about module option incompatibilities
→‎Function: expand the description of the module arguments
 
(39 intermediate revisions by 27 users not shown)
Line 1: Line 1:
NixOS has modules to have non-intrusive extensions.  Modules are made to avoid manipulating the core of NixOS while allowing the user to separate their <code>configuration.nix</code> file into multiple sub-file which are specific to a device or a service configuration. Such separation permits the creation of services which can be tested for a while before being merged in the main line of NixOS.
NixOS produces a full system configuration by combining smaller, more isolated and reusable components: '''Modules'''. A module is a file containing a Nix expression with a specific structure. It ''declares'' options for other modules to ''define'' (give a value). It processes them and defines options declared in other modules.<ref>{{manual:nixos|sec=#sec-writing-modules|chapter=Chapter 42. Writing NixOS Modules}}</ref>


This page describes how you can create a module. If you want a pratical view, you should have a look into this small [[NixOS:extend_NixOS|tutorial]].
For example, {{ic|/etc/nixos/configuration.nix}} is a module. Most other modules are in {{Nixpkgs Link|nixos/modules}}.


== Vocabulary ==
== Structure ==


* [[NixOS:Declaration|Declaration]]: All options which are used with the <code>mkOption</code> function are declared.  Declared options should at least have a <code>description</code> attribute which contains a multiple-line text for describing the effect of the option.
Modules have the following syntax:
* [[NixOS:Definition|Definition]]: All options which are set to plain values are considered as definitions.  The plain value may vary depending on the expected type of the option.
* Use: All result of options which are used to compute values of other option are option usages.
* [[NixOS:Properties|Properties]]: These are option definition modifiers.  Properties may change the influence of an option definition in the computation of an option result.
 
To find all files which are defining or declaring an option, you may refer to the generated [http://nixos.org/nixos/manual NixOS manual] or to the <code>man</code> page of <code>configuration.nix</code>.
 
== Syntax ==
 
Modules are declared with the following syntax:


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
{
{
   imports = [
   imports = [
     # list of path to other modules.
     # Paths to other modules.
    # Compose this module out of smaller ones.
   ];
   ];


   options = {
   options = {
     # attribute set of option declarations.
     # Option declarations.
    # Declare what settings a user of this module can set.
    # Usually this includes a global "enable" option which defaults to false.
   };
   };


   config = {
   config = {
     # attribute set of option definitions.
     # Option definitions.
    # Define what other settings, services and resources should be active.
    # Usually these depend on whether a user of this module chose to "enable" it
    # using the "option" above.
    # Options for modules imported in "imports" can be set here.
   };
   };
}
}
</syntaxhighlight>
</syntaxhighlight>


Another syntax exists for cases where no option declaration are necessary:
There is a shorthand for modules without any option declarations:


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
{
{
   require = [
   imports = [
     # list of path to other modules.
     # Paths to other modules.
    ./module.nix
    /path/to/absolute/module.nix
   ];
   ];


   # attribute set of option definitions.
   # Config definitions.
  services.othermodule.enable = true;
  # ...
  # Notice that you can leave out the "config { }" wrapper.
}
}
</syntaxhighlight>
</syntaxhighlight>


Both of the previous syntax can be refined with an attribute set argument added on top:
Note that despite the name, <code>imports = [./module.nix]</code> should not be confused with the Nix [https://nixos.org/manual/nix/stable/language/builtins.html#builtins-import builtins] function <code>import module.nix</code>.
 
<code>imports</code> expects a path to a file containing a NixOS module structured as described here. <code>import</code> can load arbitrary Nix expression from provided file with no expectation of structure. (no expected structure). See [https://discourse.nixos.org/t/import-list-in-configuration-nix-vs-import-function/11372/8 this post] for more details.
 
Note: <code>imports</code> provides the same behavior as the obsolete <code>require</code>. There is no reason to use <code>require</code> anymore, however it may still linger in some legacy code.
 
=== Function ===
 
A module may be a function accepting an attribute set.


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
{config, pkgs, ...}:
{ config, pkgs, ... }:
{
  imports = [];
  # ...
}
</syntaxhighlight>
 
Following arguments are available in NixOS modules by default:
 
<dl>
 
<dt>[[NixOS:config_argument|<code>config</code>]]</dt>
<dd>The configuration of the entire system.</dd>
 
<dt><code>options</code></dt>
<dd>All option declarations refined with all definition and declaration references.</dd>
 
<dt><code>lib</code></dt>
<dd>An instance of the nixpkgs "standard library", providing what usually is in <code>pkgs.lib</code>.</dd>
 
<dt><code>pkgs</code></dt>
<dd>The attribute set extracted from the Nix package collection and enhanced with the <code>nixpkgs.config</code> option.</dd>
 
<dt><code>modulesPath</code></dt>
<dd>The location of the <code>module</code> directory of NixOS.</dd>
 
</dl>The "<code>...</code>"  part of the argument attribute set indicates that this module does not depend on the rest of the arguments. When the module is defined as a function, this pattern is typically required, otherwise the evaluation will fail citing unexpected arguments.
 
==== Passing custom values to modules ====
The [[NixOS:config_argument|<code>config</code>]], <code>options</code>, <code>lib</code>, <code>pkgs</code>, and <code>modulesPath</code> arguments are passed automatically to modules, when the module is imported.


For example, in the following Nix flake, the `./configuration.nix` file will be provided with the default set of arguments listed above, plus `extraArg`, which was set in the `specialArgs` argument to the `nixosGenerate` function.<syntaxhighlight lang="nix">
{
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
    nixos-generators = {
      url = "github:nix-community/nixos-generators";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    xc = {
      url = "github:joerdav/xc";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };
  outputs = { nixpkgs, nixos-generators, xc, ... }:
    let
      pkgsForSystem = system: import nixpkgs {
        inherit system;
        overlays = [
          (final: prev: { xc = xc.packages.${system}.xc; })
        ];
      };
      allVMs = [ "x86_64-linux" "aarch64-linux" ];
      forAllVMs = f: nixpkgs.lib.genAttrs allVMs (system: f {
        inherit system;
        pkgs = pkgsForSystem system;
      });
    in
    {
      packages = forAllVMs ({ system, pkgs }: {
        vm = nixos-generators.nixosGenerate {
          system = system;
          specialArgs = {
            extraArg = "foobar";
          };
          modules = [
            ./configuration.nix
          ];
          format = "raw";
        };
      });
    };
}
</syntaxhighlight>
==== modulesPath ====
Some modules use <code>modulesPath</code> to import nixos libraries
For example <code>nixos/modules/virtualisation/digital-ocean-config.nix</code>
<pre>
{ config, pkgs, lib, modulesPath, ... }:
  imports = [
    (modulesPath + "/profiles/qemu-guest.nix")
    (modulesPath + "/virtualisation/digital-ocean-init.nix")
  ];
</pre>
The Nix variable <code>modulesPath</code> is parsed from the environment variable <code>NIX_PATH</code>
When <code>NIX_PATH</code> is empty, Nix can throw the error <code>undefined variable 'modulesPath'</code>
<code>NIX_PATH</code> should look something like this:
<pre>
echo $NIX_PATH | tr : '\n'
nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos
nixos-config=/etc/nixos/configuration.nix
/nix/var/nix/profiles/per-user/root/channels
</pre>
Here, the <code>modulesPath</code> is <code>/nix/var/nix/profiles/per-user/root/channels</code>
When a Nix expression calls <code>import <nixpkgs></code>,<br>
then Nix will load <code>/nix/var/nix/profiles/per-user/root/channels/nixos</code>
=== Imports ===
Imports are paths to other NixOS modules that should be included in the evaluation of the system configuration. A default set of modules is defined in {{Nixpkgs Link|nixos/modules/module-list.nix}}. These don't need to be added in the import list.
=== Declarations ===
Declarations specify a module's external interfaces.
<syntaxhighlight lang=nix>
optionName = mkOption {
   # ...
   # ...
}
}
</syntaxhighlight>
</syntaxhighlight>


The following arguments can be retrieved from this attribute set:
They are created with {{ic|mkOption}}, a function accepting a set with following attributes:<ref>{{Nixpkgs Link|lib/options.nix#L66-L88}}</ref><ref>{{manual:nixos|sec=#sec-option-declarations|chapter=42.1. Option Declarations}}</ref>
* [[NixOS:config_argument|<code>config</code>]]: The configuration of the entire system.
 
* <code>options</code>: All option declarations refined with all definition and declaration references.
<dl>
* <code>pkgs</code>: The attribute set extracted from the Nix package collection and enhanced with the <code>nixpkgs.config</code> option.
<dt><code>type</code></dt>
* <code>modulesPath</code>: The location of the <code>module</code> directory of NixOS.
<dd><p>
      The type of the option. It may
      be omitted, but that’s not advisable since it may lead to errors that
      are hard to diagnose.
</p></dd>
<dt><code>default</code></dt>
<dd><p>
      The default value used if no value is defined by any module. A default is
      not required; but if a default is not given, then users of the module
      will have to define the value of the option, otherwise an error will be
      thrown.
</p></dd>
<dt><code class="varname">example</code></dt>
<dd><p>
      An example value that will be shown in the NixOS manual.
</p></dd>
<dt><code>description</code></dt>
<dd><p>
      A textual description of the option, in DocBook format, that will be
      included in the NixOS manual.
</p></dd>
</dl>
 
== Rationale ==


For more information, you should refer to NixOS sources, with more insight into [https://github.com/NixOS/nixos/blob/master/lib/eval-config.nix <code>nixos/lib/eval-config.nix</code>].
Modules were introduced to allow extending NixOS without modifying its source code.<ref>[https://nixos.org/nix-dev/2008-November/001467.html <nowiki>[Nix-dev] NixOS: New scheme</nowiki>]</ref> They also allow splitting up <code>configuration.nix</code>, making the system configuration easier to maintain and to reuse.


More computation or organisation can be necessary to enhance modules. In which case, you can add a <code>let .. in ..</code> statement between the argument and the attribute set.
== Example ==
 
To see how modules are setup and reuse other modules in practice put <code>hello.nix</code> in the same folder as your <code>configuration.nix</code>:


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
{config, pkgs, ...}:
{ lib, pkgs, config, ... }:
with lib;                     
let
  # Shorter name to access final settings a
  # user of hello.nix module HAS ACTUALLY SET.
  # cfg is a typical convention.
  cfg = config.services.hello;
in {
  # Declare what settings a user of this "hello.nix" module CAN SET.
  options.services.hello = {
    enable = mkEnableOption "hello service";
    greeter = mkOption {
      type = types.str;
      default = "world";
    };
  };


let
  # Define what other settings, services and resources should be active IF
   # intermediate result.
  # a user of this "hello.nix" module ENABLED this module
in
   # by setting "services.hello.enable = true;".
  config = mkIf cfg.enable {
    systemd.services.hello = {
      wantedBy = [ "multi-user.target" ];
      serviceConfig.ExecStart = "${pkgs.hello}/bin/hello -g'Hello, ${escapeShellArg cfg.greeter}!'";
    };
  };
}
</syntaxhighlight>


The other <code>configuration.nix</code> module can then import this <code>hello.nix</code> module
and decide to enable it (and optionally set other allowed settings) as follows:
<syntaxhighlight lang=nix>
{
{
   # ...
   imports = [ ./hello.nix ];
  ...
  services.hello = {
    enable = true;
    greeter = "Bob";
  };
}
}
</syntaxhighlight>
</syntaxhighlight>


== Compatibility Issues with Different Nixpkgs Versions ==
== Advanced Use Cases ==
=== Compatibility Issues with Different Nixpkgs Versions ===


Module options between Nixpkgs revisions can sometimes change in incompatible ways.
Module options between Nixpkgs revisions can sometimes change in incompatible ways.
Line 85: Line 269:
{ options, ... }: {
{ options, ... }: {
   services.nginx.virtualHosts.somehost = { /* common configuration */ }
   services.nginx.virtualHosts.somehost = { /* common configuration */ }
     // (if builtins.hasAttr "port" (builtins.head options.services.nginx.virtualHosts.type.getSubModules).submodule.option
     // (if builtins.hasAttr "port" (builtins.head options.services.nginx.virtualHosts.type.getSubModules).submodule.options
           then { port = 8000; }
           then { port = 8000; }
           else { listen = [ { addr = "0.0.0.0"; port = 8000; } ]; });
           else { listen = [ { addr = "0.0.0.0"; port = 8000; } ]; });
}
}
</syntaxhighlight>
</syntaxhighlight>
    
 
=== Abstract imports ===
 
To import a module that's stored ''somewhere'' (but for which you have neither an absolute nor a relative path), you can use [https://github.com/musnix/musnix#basic-usage NIX_PATH elements] or <code>specialArgs</code> from <code>nixos/lib/eval-config.nix</code>.
 
This is useful for e.g. pulling modules from a git repository without adding it as a channel, or if you just prefer using paths relative to a root you can change (as opposed to the current file, which could move in the future).
 
<syntaxhighlight lang="nix">
let
   inherit (import <nixpkgs> {}) writeShellScriptBin fetchgit;
  yourModules = fetchgit { ... };
in rec {
  nixos = import <nixpkgs/nixos/lib/eval-config.nix> {
    modules = [ ./configuration.nix ];
    specialArgs.mod = name: "${yourModules}/${name}";
  };
 
  /* use nixos here, e.g. for deployment or building an image */
}
</syntaxhighlight>
 
<syntaxhighlight lang="nix">
{ config, lib, pkgs, mod, ... }: {
  imports = [
    (mod "foo.nix")
  ];
 
  ...
}
</syntaxhighlight>
 
=== Using external NixOS modules ===
 
Some external modules provide extra functionality to the NixOS module system. You can include these modules, after making them available as a file system path (e.g. through <code>builtins.fetchTarball</code>), by using <code>imports = [ `path to module`]</code> in your <code>configuration.nix</code>.
 
* [https://github.com/ip1981/nixsap Nixsap] - allows to run multiple instances of a service without containers.
* [https://github.com/musnix/musnix musnix] - real-time audio in NixOS.
* [https://gitlab.com/simple-nixos-mailserver/nixos-mailserver nixos-mailserver] - full-featured mail server module
* [https://github.com/xtruder/nix-profiles X-Truder Nix-profiles] - modules for Nix to quickly configure your system based on application profiles.
 
== Under the hood  ==
 
The following was taken from a comment by Infinisil on reddit <ref>Infinisil, https://www.reddit.com/r/NixOS/comments/gdnzhy/question_how_nixos_options_works_underthehood/</ref>.
 
A NixOS system is described by a single system derivation. {{ic|nixos-rebuild}} builds this derivation with {{ic|nix-build '<nixpkgs/nixos>' -A system}} and then switches to that system with {{ic|result/bin/switch-to-configuration}}.
 
The entrypoint is the file at {{ic|'<nixpkgs/nixos>' (./default.nix)}}, which defines the {{ic|system}} attribute to be the NixOS option {{ic|config.system.build.toplevel}}. This {{ic|toplevel}} option is the topmost level of the NixOS evaluation and it's what almost all options eventually end up influencing through potentially a number of intermediate options.
 
As an example:
* The high-level option services.nginx.enable uses the lower-level option systemd.services.nginx
* Which in turn uses the even-lower-level option systemd.units."nginx.service"
* Which in turn uses environment.etc."systemd/system"
* Which then ends up as result/etc/systemd/system/nginx.service in the toplevel derivation
 
So high-level options use lower-level ones, eventually ending up at {{ic|config.system.build.toplevel}}.
 
How do these options get evaluated though? That's what the NixOS module system does, which lives in the {{ic|./lib}} directory (in {{ic|modules.nix}}, {{ic|options.nix}} and {{ic|types.nix}}). The module system can even be used without NixOS, allowing you to use it for your own option sets. Here's a simple example of this, whose {{ic|toplevel}} option you can evaluate with {{ic|nix-instantiate --eval file.nix -A config.toplevel}}:
 
<syntaxhighlight lang="nix">
let
  systemModule = { lib, config, ... }: {
    options.toplevel = lib.mkOption {
      type = lib.types.str;
    };
 
    options.enableFoo = lib.mkOption {
      type = lib.types.bool;
      default = false;
    };
 
    config.toplevel = ''
      Is foo enabled? ${lib.boolToString config.enableFoo}
    '';
  };
 
  userModule = {
    enableFoo = true;
  };
 
in (import <nixpkgs/lib>).evalModules {
  modules = [ systemModule userModule ];
}
</syntaxhighlight>
 
The module system itself is rather complex, but here's a short overview. A module evaluation consists of a set of "modules", which can do three things:
 
* Import other modules (through imports = [ ./other-module.nix ])
* Declare options (through options = { ... })
* Define option values (through |config = { ... }, or without the config key as a shorthand if you don't have imports or options)
 
To do the actual evaluation, there's these rough steps:
 
* Recursively collect all modules by looking at all {{ic|imports}} statements
* Collect all option declarations (with {{ic|options}}) of all modules and merge them together if necessary
* For each option, evaluate it by collecting all its definitions (with {{ic|config}}) from all modules and merging them together according to the options type.
 
Note that the last step is lazy (only the options you need are evaluated) and depends on other options itself (all the ones that influence it)
 
== More complex usages ==
 
The examples below contain:
 
* a child `mkOption` inherits their default from a parent `mkOption`
* reading default values from neighbouring `mkOption`(s) for conditional defaults
* passing in the config, to read the hostName from a submodule (email system)
* setting default values from attrset (email system)
* generating documentation for custom modules (outside of nixpkgs). [https://discourse.nixos.org/t/franken-script-to-generate-nixos-options-docs-with-custom-modules/1674 See here]
 
Source:
 
* https://github.com/nixcloud/nixcloud-webservices/blob/master/modules/services/reverse-proxy/default.nix
* https://github.com/nixcloud/nixcloud-webservices/blob/master/modules/services/reverse-proxy/options.nix
* https://github.com/nixcloud/nixcloud-webservices/blob/master/modules/services/TLS/default.nix
* https://github.com/nixcloud/nixcloud-webservices/blob/master/modules/services/email/nixcloud-email.nix#L114
 
(sorry, dont' have more time to make this into a nice little guide yet, but this links should be pretty good introductions into more advanced module system usages) qknight
 
== Developing modules ==
 
To test your module out, you can run the following from a local checkout of nixpkgs with a copy of a <code>configuration.nix</code>:
<syntaxhighlight lang="bash">
nixos-rebuild build-vm --fast -I nixos-config=./configuration.nix -I nixpkgs=.
</syntaxhighlight>
 
If you're developing on top of master, this will potentially cause the compilation of lots of packages, since changes on master might not cached on cache.nixos.org yet. To avoid that, you can develop your module on top of the <code>nixos-unstable</code> [[Channel branches|channel branch]], tracked by the eponymous branch in https://github.com/NixOS/nixpkgs:
 
<syntaxhighlight lang="bash">
git checkout -b mymodule upstream/nixos-unstable
</syntaxhighlight>
 
=== With Flakes ===
If you're developing a module from nixpkgs, you can try and follow the directions here: https://github.com/Misterio77/nix-starter-configs/issues/28.
 
If you want to develop a module from a git repo, you can use `--override-input`. For example, if you have an input in your flake called {{ic|jovian}},, you can use
<syntaxhighlight lang="bash">
nixos-rebuild switch --override-input jovian <path-to-url>` --flake <uri>
</syntaxhighlight>
Of course, it doesn't have to be {{|c|nixos-rebuild}} in particular.


== References ==
== References ==


* [http://nixos.org/nixos/manual/ NixOS Documentation]
<references />


== Related Work ==
== See also ==


* Debian [http://wiki.debian.org/PackageConfigUpgrade Config::Model]:
* [[NixOS:extend_NixOS]]
target configuration upgrades by abstracting the option of the configuration.  Each file is a tree structure where leaves are values defined with an interpreted type.  The interpreters are defined for each meta-configuration files name *.conf.  Configuration files does not seems to interact with each other to make consistent configuration.  They provide an UI for editing their configuration file.
* [[NixOS:Properties]]
* [https://discourse.nixos.org/t/best-resources-for-learning-about-the-nixos-module-system NixOS discourse, "Best resources for learning about the NixOS module system?"]
* Debian [http://wiki.debian.org/PackageConfigUpgrade Config::Model]: target configuration upgrades by abstracting the option of the configuration.  Each file is a tree structure where leaves are values defined with an interpreted type.  The interpreters are defined for each meta-configuration files name *.conf.  Configuration files does not seems to interact with each other to make consistent configuration.  They provide an UI for editing their configuration file.


[[Category:Configuration]]
[[Category:Configuration]]
[[Category:Reference]]
[[Category:Reference]]
[[Category:NixOS]]
[[Category:NixOS]]

Latest revision as of 16:47, 18 December 2024

NixOS produces a full system configuration by combining smaller, more isolated and reusable components: Modules. A module is a file containing a Nix expression with a specific structure. It declares options for other modules to define (give a value). It processes them and defines options declared in other modules.[1]

For example, /etc/nixos/configuration.nix is a module. Most other modules are in nixos/modules.

Structure

Modules have the following syntax:

{
  imports = [
    # Paths to other modules.
    # Compose this module out of smaller ones.
  ];

  options = {
    # Option declarations.
    # Declare what settings a user of this module can set.
    # Usually this includes a global "enable" option which defaults to false.
  };

  config = {
    # Option definitions.
    # Define what other settings, services and resources should be active.
    # Usually these depend on whether a user of this module chose to "enable" it
    # using the "option" above. 
    # Options for modules imported in "imports" can be set here.
  };
}

There is a shorthand for modules without any option declarations:

{
  imports = [
    # Paths to other modules.
    ./module.nix
    /path/to/absolute/module.nix
  ];

  # Config definitions.
  services.othermodule.enable = true;
  # ...
  # Notice that you can leave out the "config { }" wrapper.
}

Note that despite the name, imports = [./module.nix] should not be confused with the Nix builtins function import module.nix.

imports expects a path to a file containing a NixOS module structured as described here. import can load arbitrary Nix expression from provided file with no expectation of structure. (no expected structure). See this post for more details.

Note: imports provides the same behavior as the obsolete require. There is no reason to use require anymore, however it may still linger in some legacy code.

Function

A module may be a function accepting an attribute set.

{ config, pkgs, ... }:
{
  imports = [];
  # ...
}

Following arguments are available in NixOS modules by default:

config
The configuration of the entire system.
options
All option declarations refined with all definition and declaration references.
lib
An instance of the nixpkgs "standard library", providing what usually is in pkgs.lib.
pkgs
The attribute set extracted from the Nix package collection and enhanced with the nixpkgs.config option.
modulesPath
The location of the module directory of NixOS.

The "..." part of the argument attribute set indicates that this module does not depend on the rest of the arguments. When the module is defined as a function, this pattern is typically required, otherwise the evaluation will fail citing unexpected arguments.

Passing custom values to modules

The config, options, lib, pkgs, and modulesPath arguments are passed automatically to modules, when the module is imported.

For example, in the following Nix flake, the `./configuration.nix` file will be provided with the default set of arguments listed above, plus `extraArg`, which was set in the `specialArgs` argument to the `nixosGenerate` function.

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
    nixos-generators = {
      url = "github:nix-community/nixos-generators";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    xc = {
      url = "github:joerdav/xc";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { nixpkgs, nixos-generators, xc, ... }:
    let
      pkgsForSystem = system: import nixpkgs {
        inherit system;
        overlays = [
          (final: prev: { xc = xc.packages.${system}.xc; })
        ];
      };
      allVMs = [ "x86_64-linux" "aarch64-linux" ];
      forAllVMs = f: nixpkgs.lib.genAttrs allVMs (system: f {
        inherit system;
        pkgs = pkgsForSystem system;
      });
    in
    {
      packages = forAllVMs ({ system, pkgs }: {
        vm = nixos-generators.nixosGenerate {
          system = system;
          specialArgs = { 
            extraArg = "foobar";
          };
          modules = [
            ./configuration.nix
          ];
          format = "raw";
        };
      });
    };
}

modulesPath

Some modules use modulesPath to import nixos libraries

For example nixos/modules/virtualisation/digital-ocean-config.nix

{ config, pkgs, lib, modulesPath, ... }:
  imports = [
    (modulesPath + "/profiles/qemu-guest.nix")
    (modulesPath + "/virtualisation/digital-ocean-init.nix")
  ];

The Nix variable modulesPath is parsed from the environment variable NIX_PATH

When NIX_PATH is empty, Nix can throw the error undefined variable 'modulesPath'

NIX_PATH should look something like this:

echo $NIX_PATH | tr : '\n' 
nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos
nixos-config=/etc/nixos/configuration.nix
/nix/var/nix/profiles/per-user/root/channels

Here, the modulesPath is /nix/var/nix/profiles/per-user/root/channels

When a Nix expression calls import <nixpkgs>,
then Nix will load /nix/var/nix/profiles/per-user/root/channels/nixos

Imports

Imports are paths to other NixOS modules that should be included in the evaluation of the system configuration. A default set of modules is defined in nixos/modules/module-list.nix. These don't need to be added in the import list.

Declarations

Declarations specify a module's external interfaces.

optionName = mkOption {
  # ...
}

They are created with mkOption, a function accepting a set with following attributes:[2][3]

type

The type of the option. It may be omitted, but that’s not advisable since it may lead to errors that are hard to diagnose.

default

The default value used if no value is defined by any module. A default is not required; but if a default is not given, then users of the module will have to define the value of the option, otherwise an error will be thrown.

example

An example value that will be shown in the NixOS manual.

description

A textual description of the option, in DocBook format, that will be included in the NixOS manual.

Rationale

Modules were introduced to allow extending NixOS without modifying its source code.[4] They also allow splitting up configuration.nix, making the system configuration easier to maintain and to reuse.

Example

To see how modules are setup and reuse other modules in practice put hello.nix in the same folder as your configuration.nix:

{ lib, pkgs, config, ... }:
with lib;                      
let
  # Shorter name to access final settings a 
  # user of hello.nix module HAS ACTUALLY SET.
  # cfg is a typical convention.
  cfg = config.services.hello;
in {
  # Declare what settings a user of this "hello.nix" module CAN SET.
  options.services.hello = {
    enable = mkEnableOption "hello service";
    greeter = mkOption {
      type = types.str;
      default = "world";
    };
  };

  # Define what other settings, services and resources should be active IF
  # a user of this "hello.nix" module ENABLED this module 
  # by setting "services.hello.enable = true;".
  config = mkIf cfg.enable {
    systemd.services.hello = {
      wantedBy = [ "multi-user.target" ];
      serviceConfig.ExecStart = "${pkgs.hello}/bin/hello -g'Hello, ${escapeShellArg cfg.greeter}!'";
    };
  };
}

The other configuration.nix module can then import this hello.nix module and decide to enable it (and optionally set other allowed settings) as follows:

{
  imports = [ ./hello.nix ];
  ...
  services.hello = {
    enable = true;
    greeter = "Bob";
  };
}

Advanced Use Cases

Compatibility Issues with Different Nixpkgs Versions

Module options between Nixpkgs revisions can sometimes change in incompatible ways.

For example, the option services.nginx.virtualHosts.*.port in nixpkgs-17.03 was replaced by services.nginx.virtualHosts.*.listen in nixpkgs-17.09. If configuration.nix has to accommodate both variants, options can be inspected:

{ options, ... }: {
  services.nginx.virtualHosts.somehost = { /* common configuration */ }
    // (if builtins.hasAttr "port" (builtins.head options.services.nginx.virtualHosts.type.getSubModules).submodule.options
          then { port = 8000; }
          else { listen = [ { addr = "0.0.0.0"; port = 8000; } ]; });
}

Abstract imports

To import a module that's stored somewhere (but for which you have neither an absolute nor a relative path), you can use NIX_PATH elements or specialArgs from nixos/lib/eval-config.nix.

This is useful for e.g. pulling modules from a git repository without adding it as a channel, or if you just prefer using paths relative to a root you can change (as opposed to the current file, which could move in the future).

let
  inherit (import <nixpkgs> {}) writeShellScriptBin fetchgit;
  yourModules = fetchgit { ... };
in rec {
  nixos = import <nixpkgs/nixos/lib/eval-config.nix> {
    modules = [ ./configuration.nix ];
    specialArgs.mod = name: "${yourModules}/${name}";
  };

  /* use nixos here, e.g. for deployment or building an image */
}
{ config, lib, pkgs, mod, ... }: {
  imports = [
    (mod "foo.nix")
  ];

  ...
}

Using external NixOS modules

Some external modules provide extra functionality to the NixOS module system. You can include these modules, after making them available as a file system path (e.g. through builtins.fetchTarball), by using imports = [ `path to module`] in your configuration.nix.

  • Nixsap - allows to run multiple instances of a service without containers.
  • musnix - real-time audio in NixOS.
  • nixos-mailserver - full-featured mail server module
  • X-Truder Nix-profiles - modules for Nix to quickly configure your system based on application profiles.

Under the hood

The following was taken from a comment by Infinisil on reddit [5].

A NixOS system is described by a single system derivation. nixos-rebuild builds this derivation with nix-build '<nixpkgs/nixos>' -A system and then switches to that system with result/bin/switch-to-configuration.

The entrypoint is the file at '<nixpkgs/nixos>' (./default.nix), which defines the system attribute to be the NixOS option config.system.build.toplevel. This toplevel option is the topmost level of the NixOS evaluation and it's what almost all options eventually end up influencing through potentially a number of intermediate options.

As an example:

  • The high-level option services.nginx.enable uses the lower-level option systemd.services.nginx
  • Which in turn uses the even-lower-level option systemd.units."nginx.service"
  • Which in turn uses environment.etc."systemd/system"
  • Which then ends up as result/etc/systemd/system/nginx.service in the toplevel derivation

So high-level options use lower-level ones, eventually ending up at config.system.build.toplevel.

How do these options get evaluated though? That's what the NixOS module system does, which lives in the ./lib directory (in modules.nix, options.nix and types.nix). The module system can even be used without NixOS, allowing you to use it for your own option sets. Here's a simple example of this, whose toplevel option you can evaluate with nix-instantiate --eval file.nix -A config.toplevel:

let
  systemModule = { lib, config, ... }: {
    options.toplevel = lib.mkOption {
      type = lib.types.str;
    };

    options.enableFoo = lib.mkOption {
      type = lib.types.bool;
      default = false;
    };

    config.toplevel = ''
      Is foo enabled? ${lib.boolToString config.enableFoo}
    '';
  };

  userModule = {
    enableFoo = true;
  };

in (import <nixpkgs/lib>).evalModules {
  modules = [ systemModule userModule ];
}

The module system itself is rather complex, but here's a short overview. A module evaluation consists of a set of "modules", which can do three things:

  • Import other modules (through imports = [ ./other-module.nix ])
  • Declare options (through options = { ... })
  • Define option values (through |config = { ... }, or without the config key as a shorthand if you don't have imports or options)

To do the actual evaluation, there's these rough steps:

  • Recursively collect all modules by looking at all imports statements
  • Collect all option declarations (with options) of all modules and merge them together if necessary
  • For each option, evaluate it by collecting all its definitions (with config) from all modules and merging them together according to the options type.

Note that the last step is lazy (only the options you need are evaluated) and depends on other options itself (all the ones that influence it)

More complex usages

The examples below contain:

  • a child `mkOption` inherits their default from a parent `mkOption`
  • reading default values from neighbouring `mkOption`(s) for conditional defaults
  • passing in the config, to read the hostName from a submodule (email system)
  • setting default values from attrset (email system)
  • generating documentation for custom modules (outside of nixpkgs). See here

Source:

(sorry, dont' have more time to make this into a nice little guide yet, but this links should be pretty good introductions into more advanced module system usages) qknight

Developing modules

To test your module out, you can run the following from a local checkout of nixpkgs with a copy of a configuration.nix:

nixos-rebuild build-vm --fast -I nixos-config=./configuration.nix -I nixpkgs=.

If you're developing on top of master, this will potentially cause the compilation of lots of packages, since changes on master might not cached on cache.nixos.org yet. To avoid that, you can develop your module on top of the nixos-unstable channel branch, tracked by the eponymous branch in https://github.com/NixOS/nixpkgs:

git checkout -b mymodule upstream/nixos-unstable

With Flakes

If you're developing a module from nixpkgs, you can try and follow the directions here: https://github.com/Misterio77/nix-starter-configs/issues/28.

If you want to develop a module from a git repo, you can use `--override-input`. For example, if you have an input in your flake called jovian,, you can use

nixos-rebuild switch --override-input jovian <path-to-url>` --flake <uri>

Of course, it doesn't have to be {{|c|nixos-rebuild}} in particular.

References

See also