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