NixOS modules: Difference between revisions

imported>Workflow
m Remove explicit lib call since lib is in scope
Klinger (talk | contribs)
m changed link from redirect to article
(22 intermediate revisions by 16 users not shown)
Line 1: Line 1:
'''Modules''' are files combined by NixOS to produce the full system configuration. A module contains a Nix expression. 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>
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>


For example, {{ic|/etc/nixos/configuration.nix}} is a module. Most other modules are in {{Nixpkgs Link|nixos/modules}}.
For example, {{ic|/etc/nixos/configuration.nix}} is a module. Most other modules are in {{Nixpkgs Link|nixos/modules}}.
Line 10: Line 10:
{
{
   imports = [
   imports = [
     # paths to other modules
     # Paths to other modules.
    # Compose this module out of smaller ones.
   ];
   ];


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


   config = {
   config = {
     # 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.
   };
   };
}
}
Line 27: Line 34:
<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
{
{
   require = [
   imports = [
     # paths to other modules
     # Paths to other modules.
    ./module.nix
    /path/to/absolute/module.nix
   ];
   ];


   # option definitions
   # Config definitions.
  services.othermodule.enable = true;
  # ...
  # Notice that you can leave out the "config { }" wrapper.
}
}
</syntaxhighlight>
</syntaxhighlight>
Beginners often confuse the modules attribute <code>imports = [./module.nix]</code> here with the Nix [https://nixos.org/manual/nix/stable/language/builtins.html#builtins-import builtins] function <code>import module.nix</code>. The first expects a path to a file containing a NixOS module (having the same specific structure we're describing here), while the second loads whatever Nix expression is in that file (no expected structure). See [https://discourse.nixos.org/t/import-list-in-configuration-nix-vs-import-function/11372/8 this post].
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 ===
=== Function ===
Line 42: Line 58:
{ config, pkgs, ... }:
{ config, pkgs, ... }:
{
{
  imports = [];
   # ...
   # ...
}
}
Line 49: Line 66:


<dl>
<dl>
<dt>[[NixOS:config_argument|<code>config</code>]]</dt>
<dt>[[NixOS:config_argument|<code>config</code>]]</dt>
<dd>The configuration of the entire system.</dd>
<dd>The configuration of the entire system.</dd>
<dt><code>options</code></dt>
<dt><code>options</code></dt>
<dd>All option declarations refined with all definition and declaration references.</dd>
<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>
<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>
<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>
<dt><code>modulesPath</code></dt>
<dd>The location of the <code>module</code> directory of NixOS.</dd>
<dd>The location of the <code>module</code> directory of NixOS.</dd>
</dl>
</dl>
==== 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 ===
Line 73: Line 178:
</syntaxhighlight>
</syntaxhighlight>


They are created with {{ic|mkOption}}, a function accepting a set with following attributes:<ref>{{Nixpkgs Link|lib/options.nix#L21-L54}}</ref><ref>{{manual:nixos|sec=#sec-option-declarations|chapter=42.1. Option Declarations}}</ref>
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>


<dl>
<dl>
Line 106: Line 211:
== Example ==
== Example ==


put into <code>hello.nix</code> in the same folder as your <code>configuration.nix</code>.
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">
{ lib, pkgs, config, ... }:
{ lib, pkgs, config, ... }:
with lib;                       
with lib;                       
let
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;
   cfg = config.services.hello;
in {
in {
  # Declare what settings a user of this "hello.nix" module CAN SET.
   options.services.hello = {
   options.services.hello = {
     enable = mkEnableOption "hello service";
     enable = mkEnableOption "hello service";
Line 121: Line 231:
   };
   };


  # 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 {
   config = mkIf cfg.enable {
     systemd.services.hello = {
     systemd.services.hello = {
Line 130: Line 243:
</syntaxhighlight>
</syntaxhighlight>


Add the following to your <code>configuration.nix</code>
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>
<syntaxhighlight lang=nix>
{
{
Line 187: Line 302:
}
}
</syntaxhighlight>
</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 ==
== More complex usages ==
Line 203: Line 385:
* https://github.com/nixcloud/nixcloud-webservices/blob/master/modules/services/reverse-proxy/options.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/TLS/default.nix
* https://github.com/nixcloud/nixcloud-webservices/blob/master/modules/services/email/nixcloud-email.nix#L59
* 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
(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
Line 214: Line 396:
</syntaxhighlight>
</syntaxhighlight>


If you're developing on top of master, this will potentially cause the compilation of lots of packages, since master is not cached on cache.nixos.org. To avoid that, you can develop your module on top of the <code>nixos-unstable</code> [[Channels|channel]], by adding <code>nixpkgs-channels</code> as a remote:
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">
<syntaxhighlight lang="bash">
git remote add -f channels https://github.com/NixOS/nixpkgs-channels
nixos-rebuild switch --override-input jovian <path-to-url>` --flake <uri>
git checkout -b mymodule channels/nixos-unstable
</syntaxhighlight>
</syntaxhighlight>
Of course, it doesn't have to be {{|c|nixos-rebuild}} in particular.


== References ==
== References ==