1<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">
2 <title>Writing NixOS Modules</title>
3 <para>
4 NixOS has a modular system for declarative configuration. This
5 system combines multiple <emphasis>modules</emphasis> to produce the
6 full system configuration. One of the modules that constitute the
7 configuration is <literal>/etc/nixos/configuration.nix</literal>.
8 Most of the others live in the
9 <link xlink:href="https://github.com/NixOS/nixpkgs/tree/master/nixos/modules"><literal>nixos/modules</literal></link>
10 subdirectory of the Nixpkgs tree.
11 </para>
12 <para>
13 Each NixOS module is a file that handles one logical aspect of the
14 configuration, such as a specific kind of hardware, a service, or
15 network settings. A module configuration does not have to handle
16 everything from scratch; it can use the functionality provided by
17 other modules for its implementation. Thus a module can
18 <emphasis>declare</emphasis> options that can be used by other
19 modules, and conversely can <emphasis>define</emphasis> options
20 provided by other modules in its own implementation. For example,
21 the module
22 <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/security/pam.nix"><literal>pam.nix</literal></link>
23 declares the option <literal>security.pam.services</literal> that
24 allows other modules (e.g.
25 <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/networking/ssh/sshd.nix"><literal>sshd.nix</literal></link>)
26 to define PAM services; and it defines the option
27 <literal>environment.etc</literal> (declared by
28 <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/system/etc/etc.nix"><literal>etc.nix</literal></link>)
29 to cause files to be created in <literal>/etc/pam.d</literal>.
30 </para>
31 <para>
32 In <xref linkend="sec-configuration-syntax" />, we saw the following
33 structure of NixOS modules:
34 </para>
35 <programlisting language="bash">
36{ config, pkgs, ... }:
37
38{ option definitions
39}
40</programlisting>
41 <para>
42 This is actually an <emphasis>abbreviated</emphasis> form of module
43 that only defines options, but does not declare any. The structure
44 of full NixOS modules is shown in
45 <link linkend="ex-module-syntax">Example: Structure of NixOS
46 Modules</link>.
47 </para>
48 <anchor xml:id="ex-module-syntax" />
49 <para>
50 <emphasis role="strong">Example: Structure of NixOS
51 Modules</emphasis>
52 </para>
53 <programlisting language="bash">
54{ config, pkgs, ... }:
55
56{
57 imports =
58 [ paths of other modules
59 ];
60
61 options = {
62 option declarations
63 };
64
65 config = {
66 option definitions
67 };
68}
69</programlisting>
70 <para>
71 The meaning of each part is as follows.
72 </para>
73 <itemizedlist>
74 <listitem>
75 <para>
76 The first line makes the current Nix expression a function. The
77 variable <literal>pkgs</literal> contains Nixpkgs (by default,
78 it takes the <literal>nixpkgs</literal> entry of
79 <literal>NIX_PATH</literal>, see the
80 <link xlink:href="https://nixos.org/manual/nix/stable/#sec-common-env">Nix
81 manual</link> for further details), while
82 <literal>config</literal> contains the full system
83 configuration. This line can be omitted if there is no reference
84 to <literal>pkgs</literal> and <literal>config</literal> inside
85 the module.
86 </para>
87 </listitem>
88 <listitem>
89 <para>
90 This <literal>imports</literal> list enumerates the paths to
91 other NixOS modules that should be included in the evaluation of
92 the system configuration. A default set of modules is defined in
93 the file <literal>modules/module-list.nix</literal>. These don't
94 need to be added in the import list.
95 </para>
96 </listitem>
97 <listitem>
98 <para>
99 The attribute <literal>options</literal> is a nested set of
100 <emphasis>option declarations</emphasis> (described below).
101 </para>
102 </listitem>
103 <listitem>
104 <para>
105 The attribute <literal>config</literal> is a nested set of
106 <emphasis>option definitions</emphasis> (also described below).
107 </para>
108 </listitem>
109 </itemizedlist>
110 <para>
111 <link linkend="locate-example">Example: NixOS Module for the
112 <quote>locate</quote> Service</link> shows a module that handles the
113 regular update of the <quote>locate</quote> database, an index of
114 all files in the file system. This module declares two options that
115 can be defined by other modules (typically the user’s
116 <literal>configuration.nix</literal>):
117 <literal>services.locate.enable</literal> (whether the database
118 should be updated) and <literal>services.locate.interval</literal>
119 (when the update should be done). It implements its functionality by
120 defining two options declared by other modules:
121 <literal>systemd.services</literal> (the set of all systemd
122 services) and <literal>systemd.timers</literal> (the list of
123 commands to be executed periodically by <literal>systemd</literal>).
124 </para>
125 <para>
126 Care must be taken when writing systemd services using
127 <literal>Exec*</literal> directives. By default systemd performs
128 substitution on <literal>%<char></literal> specifiers in these
129 directives, expands environment variables from
130 <literal>$FOO</literal> and <literal>${FOO}</literal>, splits
131 arguments on whitespace, and splits commands on
132 <literal>;</literal>. All of these must be escaped to avoid
133 unexpected substitution or splitting when interpolating into an
134 <literal>Exec*</literal> directive, e.g. when using an
135 <literal>extraArgs</literal> option to pass additional arguments to
136 the service. The functions
137 <literal>utils.escapeSystemdExecArg</literal> and
138 <literal>utils.escapeSystemdExecArgs</literal> are provided for
139 this, see <link linkend="exec-escaping-example">Example: Escaping in
140 Exec directives</link> for an example. When using these functions
141 system environment substitution should <emphasis>not</emphasis> be
142 disabled explicitly.
143 </para>
144 <anchor xml:id="locate-example" />
145 <para>
146 <emphasis role="strong">Example: NixOS Module for the
147 <quote>locate</quote> Service</emphasis>
148 </para>
149 <programlisting language="bash">
150{ config, lib, pkgs, ... }:
151
152with lib;
153
154let
155 cfg = config.services.locate;
156in {
157 options.services.locate = {
158 enable = mkOption {
159 type = types.bool;
160 default = false;
161 description = ''
162 If enabled, NixOS will periodically update the database of
163 files used by the locate command.
164 '';
165 };
166
167 interval = mkOption {
168 type = types.str;
169 default = "02:15";
170 example = "hourly";
171 description = ''
172 Update the locate database at this interval. Updates by
173 default at 2:15 AM every day.
174
175 The format is described in
176 systemd.time(7).
177 '';
178 };
179
180 # Other options omitted for documentation
181 };
182
183 config = {
184 systemd.services.update-locatedb =
185 { description = "Update Locate Database";
186 path = [ pkgs.su ];
187 script =
188 ''
189 mkdir -m 0755 -p $(dirname ${toString cfg.output})
190 exec updatedb \
191 --localuser=${cfg.localuser} \
192 ${optionalString (!cfg.includeStore) "--prunepaths='/nix/store'"} \
193 --output=${toString cfg.output} ${concatStringsSep " " cfg.extraFlags}
194 '';
195 };
196
197 systemd.timers.update-locatedb = mkIf cfg.enable
198 { description = "Update timer for locate database";
199 partOf = [ "update-locatedb.service" ];
200 wantedBy = [ "timers.target" ];
201 timerConfig.OnCalendar = cfg.interval;
202 };
203 };
204}
205</programlisting>
206 <anchor xml:id="exec-escaping-example" />
207 <para>
208 <emphasis role="strong">Example: Escaping in Exec
209 directives</emphasis>
210 </para>
211 <programlisting language="bash">
212{ config, lib, pkgs, utils, ... }:
213
214with lib;
215
216let
217 cfg = config.services.echo;
218 echoAll = pkgs.writeScript "echo-all" ''
219 #! ${pkgs.runtimeShell}
220 for s in "$@"; do
221 printf '%s\n' "$s"
222 done
223 '';
224 args = [ "a%Nything" "lang=\${LANG}" ";" "/bin/sh -c date" ];
225in {
226 systemd.services.echo =
227 { description = "Echo to the journal";
228 wantedBy = [ "multi-user.target" ];
229 serviceConfig.Type = "oneshot";
230 serviceConfig.ExecStart = ''
231 ${echoAll} ${utils.escapeSystemdExecArgs args}
232 '';
233 };
234}
235</programlisting>
236 <xi:include href="option-declarations.section.xml" />
237 <xi:include href="option-types.section.xml" />
238 <xi:include href="option-def.section.xml" />
239 <xi:include href="assertions.section.xml" />
240 <xi:include href="meta-attributes.section.xml" />
241 <xi:include href="importing-modules.section.xml" />
242 <xi:include href="replace-modules.section.xml" />
243 <xi:include href="freeform-modules.section.xml" />
244 <xi:include href="settings-options.section.xml" />
245</chapter>