<chapter xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="sec-writing-modules">
  <title>Writing NixOS Modules</title>
  <para>
    NixOS has a modular system for declarative configuration. This
    system combines multiple <emphasis>modules</emphasis> to produce the
    full system configuration. One of the modules that constitute the
    configuration is <literal>/etc/nixos/configuration.nix</literal>.
    Most of the others live in the
    <link xlink:href="https://github.com/NixOS/nixpkgs/tree/master/nixos/modules"><literal>nixos/modules</literal></link>
    subdirectory of the Nixpkgs tree.
  </para>
  <para>
    Each NixOS module is a file that handles one logical aspect of the
    configuration, such as a specific kind of hardware, a service, or
    network settings. A module configuration does not have to handle
    everything from scratch; it can use the functionality provided by
    other modules for its implementation. Thus a module can
    <emphasis>declare</emphasis> options that can be used by other
    modules, and conversely can <emphasis>define</emphasis> options
    provided by other modules in its own implementation. For example,
    the module
    <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/security/pam.nix"><literal>pam.nix</literal></link>
    declares the option <literal>security.pam.services</literal> that
    allows other modules (e.g.
    <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/networking/ssh/sshd.nix"><literal>sshd.nix</literal></link>)
    to define PAM services; and it defines the option
    <literal>environment.etc</literal> (declared by
    <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/system/etc/etc.nix"><literal>etc.nix</literal></link>)
    to cause files to be created in <literal>/etc/pam.d</literal>.
  </para>
  <para>
    In <xref linkend="sec-configuration-syntax" />, we saw the following
    structure of NixOS modules:
  </para>
  <programlisting language="bash">
{ config, pkgs, ... }:

{ option definitions
}
</programlisting>
  <para>
    This is actually an <emphasis>abbreviated</emphasis> form of module
    that only defines options, but does not declare any. The structure
    of full NixOS modules is shown in
    <link linkend="ex-module-syntax">Example: Structure of NixOS
    Modules</link>.
  </para>
  <anchor xml:id="ex-module-syntax" />
  <para>
    <emphasis role="strong">Example: Structure of NixOS
    Modules</emphasis>
  </para>
  <programlisting language="bash">
{ config, pkgs, ... }:

{
  imports =
    [ paths of other modules
    ];

  options = {
    option declarations
  };

  config = {
    option definitions
  };
}
</programlisting>
  <para>
    The meaning of each part is as follows.
  </para>
  <itemizedlist>
    <listitem>
      <para>
        The first line makes the current Nix expression a function. The
        variable <literal>pkgs</literal> contains Nixpkgs (by default,
        it takes the <literal>nixpkgs</literal> entry of
        <literal>NIX_PATH</literal>, see the
        <link xlink:href="https://nixos.org/manual/nix/stable/#sec-common-env">Nix
        manual</link> for further details), while
        <literal>config</literal> contains the full system
        configuration. This line can be omitted if there is no reference
        to <literal>pkgs</literal> and <literal>config</literal> inside
        the module.
      </para>
    </listitem>
    <listitem>
      <para>
        This <literal>imports</literal> list enumerates the paths to
        other NixOS modules that should be included in the evaluation of
        the system configuration. A default set of modules is defined in
        the file <literal>modules/module-list.nix</literal>. These don't
        need to be added in the import list.
      </para>
    </listitem>
    <listitem>
      <para>
        The attribute <literal>options</literal> is a nested set of
        <emphasis>option declarations</emphasis> (described below).
      </para>
    </listitem>
    <listitem>
      <para>
        The attribute <literal>config</literal> is a nested set of
        <emphasis>option definitions</emphasis> (also described below).
      </para>
    </listitem>
  </itemizedlist>
  <para>
    <link linkend="locate-example">Example: NixOS Module for the
    <quote>locate</quote> Service</link> shows a module that handles the
    regular update of the <quote>locate</quote> database, an index of
    all files in the file system. This module declares two options that
    can be defined by other modules (typically the user’s
    <literal>configuration.nix</literal>):
    <literal>services.locate.enable</literal> (whether the database
    should be updated) and <literal>services.locate.interval</literal>
    (when the update should be done). It implements its functionality by
    defining two options declared by other modules:
    <literal>systemd.services</literal> (the set of all systemd
    services) and <literal>systemd.timers</literal> (the list of
    commands to be executed periodically by <literal>systemd</literal>).
  </para>
  <para>
    Care must be taken when writing systemd services using
    <literal>Exec*</literal> directives. By default systemd performs
    substitution on <literal>%&lt;char&gt;</literal> specifiers in these
    directives, expands environment variables from
    <literal>$FOO</literal> and <literal>${FOO}</literal>, splits
    arguments on whitespace, and splits commands on
    <literal>;</literal>. All of these must be escaped to avoid
    unexpected substitution or splitting when interpolating into an
    <literal>Exec*</literal> directive, e.g. when using an
    <literal>extraArgs</literal> option to pass additional arguments to
    the service. The functions
    <literal>utils.escapeSystemdExecArg</literal> and
    <literal>utils.escapeSystemdExecArgs</literal> are provided for
    this, see <link linkend="exec-escaping-example">Example: Escaping in
    Exec directives</link> for an example. When using these functions
    system environment substitution should <emphasis>not</emphasis> be
    disabled explicitly.
  </para>
  <anchor xml:id="locate-example" />
  <para>
    <emphasis role="strong">Example: NixOS Module for the
    <quote>locate</quote> Service</emphasis>
  </para>
  <programlisting language="bash">
{ config, lib, pkgs, ... }:

with lib;

let
  cfg = config.services.locate;
in {
  options.services.locate = {
    enable = mkOption {
      type = types.bool;
      default = false;
      description = ''
        If enabled, NixOS will periodically update the database of
        files used by the locate command.
      '';
    };

    interval = mkOption {
      type = types.str;
      default = &quot;02:15&quot;;
      example = &quot;hourly&quot;;
      description = ''
        Update the locate database at this interval. Updates by
        default at 2:15 AM every day.

        The format is described in
        systemd.time(7).
      '';
    };

    # Other options omitted for documentation
  };

  config = {
    systemd.services.update-locatedb =
      { description = &quot;Update Locate Database&quot;;
        path  = [ pkgs.su ];
        script =
          ''
            mkdir -m 0755 -p $(dirname ${toString cfg.output})
            exec updatedb \
              --localuser=${cfg.localuser} \
              ${optionalString (!cfg.includeStore) &quot;--prunepaths='/nix/store'&quot;} \
              --output=${toString cfg.output} ${concatStringsSep &quot; &quot; cfg.extraFlags}
          '';
      };

    systemd.timers.update-locatedb = mkIf cfg.enable
      { description = &quot;Update timer for locate database&quot;;
        partOf      = [ &quot;update-locatedb.service&quot; ];
        wantedBy    = [ &quot;timers.target&quot; ];
        timerConfig.OnCalendar = cfg.interval;
      };
  };
}
</programlisting>
  <anchor xml:id="exec-escaping-example" />
  <para>
    <emphasis role="strong">Example: Escaping in Exec
    directives</emphasis>
  </para>
  <programlisting language="bash">
{ config, lib, pkgs, utils, ... }:

with lib;

let
  cfg = config.services.echo;
  echoAll = pkgs.writeScript &quot;echo-all&quot; ''
    #! ${pkgs.runtimeShell}
    for s in &quot;$@&quot;; do
      printf '%s\n' &quot;$s&quot;
    done
  '';
  args = [ &quot;a%Nything&quot; &quot;lang=\${LANG}&quot; &quot;;&quot; &quot;/bin/sh -c date&quot; ];
in {
  systemd.services.echo =
    { description = &quot;Echo to the journal&quot;;
      wantedBy = [ &quot;multi-user.target&quot; ];
      serviceConfig.Type = &quot;oneshot&quot;;
      serviceConfig.ExecStart = ''
        ${echoAll} ${utils.escapeSystemdExecArgs args}
      '';
    };
}
</programlisting>
  <xi:include href="option-declarations.section.xml" />
  <xi:include href="option-types.section.xml" />
  <xi:include href="option-def.section.xml" />
  <xi:include href="assertions.section.xml" />
  <xi:include href="meta-attributes.section.xml" />
  <xi:include href="importing-modules.section.xml" />
  <xi:include href="replace-modules.section.xml" />
  <xi:include href="freeform-modules.section.xml" />
  <xi:include href="settings-options.section.xml" />
</chapter>
