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::: {.title} 41**Example: Structure of NixOS Modules** 42::: 43```nix 44{ config, pkgs, ... }: 45 46{ 47 imports = 48 [ paths of other modules 49 ]; 50 51 options = { 52 option declarations 53 }; 54 55 config = { 56 option definitions 57 }; 58} 59``` 60::: 61 62The meaning of each part is as follows. 63 64- The first line makes the current Nix expression a function. The variable 65 `pkgs` contains Nixpkgs (by default, it takes the `nixpkgs` entry of 66 `NIX_PATH`, see the [Nix manual](https://nixos.org/manual/nix/stable/#sec-common-env) 67 for further details), while `config` contains the full system 68 configuration. This line can be omitted if there is no reference to 69 `pkgs` and `config` inside the module. 70 71- This `imports` list enumerates the paths to other NixOS modules that 72 should be included in the evaluation of the system configuration. A 73 default set of modules is defined in the file `modules/module-list.nix`. 74 These don\'t need to be added in the import list. 75 76- The attribute `options` is a nested set of *option declarations* 77 (described below). 78 79- The attribute `config` is a nested set of *option definitions* (also 80 described below). 81 82[Example: NixOS Module for the "locate" Service](#locate-example) 83shows a module that handles the regular update of the "locate" database, 84an index of all files in the file system. This module declares two 85options that can be defined by other modules (typically the user's 86`configuration.nix`): `services.locate.enable` (whether the database should 87be updated) and `services.locate.interval` (when the update should be done). 88It implements its functionality by defining two options declared by other 89modules: `systemd.services` (the set of all systemd services) and 90`systemd.timers` (the list of commands to be executed periodically by 91`systemd`). 92 93Care must be taken when writing systemd services using `Exec*` directives. By 94default systemd performs substitution on `%<char>` specifiers in these 95directives, expands environment variables from `$FOO` and `${FOO}`, splits 96arguments on whitespace, and splits commands on `;`. All of these must be escaped 97to avoid unexpected substitution or splitting when interpolating into an `Exec*` 98directive, e.g. when using an `extraArgs` option to pass additional arguments to 99the service. The functions `utils.escapeSystemdExecArg` and 100`utils.escapeSystemdExecArgs` are provided for this, see [Example: Escaping in 101Exec directives](#exec-escaping-example) for an example. When using these 102functions system environment substitution should *not* be disabled explicitly. 103 104::: {#locate-example .example} 105::: {.title} 106**Example: NixOS Module for the "locate" Service** 107::: 108```nix 109{ config, lib, pkgs, ... }: 110 111with lib; 112 113let 114 cfg = config.services.locate; 115in { 116 options.services.locate = { 117 enable = mkOption { 118 type = types.bool; 119 default = false; 120 description = '' 121 If enabled, NixOS will periodically update the database of 122 files used by the locate command. 123 ''; 124 }; 125 126 interval = mkOption { 127 type = types.str; 128 default = "02:15"; 129 example = "hourly"; 130 description = '' 131 Update the locate database at this interval. Updates by 132 default at 2:15 AM every day. 133 134 The format is described in 135 systemd.time(7). 136 ''; 137 }; 138 139 # Other options omitted for documentation 140 }; 141 142 config = { 143 systemd.services.update-locatedb = 144 { description = "Update Locate Database"; 145 path = [ pkgs.su ]; 146 script = 147 '' 148 mkdir -m 0755 -p $(dirname ${toString cfg.output}) 149 exec updatedb \ 150 --localuser=${cfg.localuser} \ 151 ${optionalString (!cfg.includeStore) "--prunepaths='/nix/store'"} \ 152 --output=${toString cfg.output} ${concatStringsSep " " cfg.extraFlags} 153 ''; 154 }; 155 156 systemd.timers.update-locatedb = mkIf cfg.enable 157 { description = "Update timer for locate database"; 158 partOf = [ "update-locatedb.service" ]; 159 wantedBy = [ "timers.target" ]; 160 timerConfig.OnCalendar = cfg.interval; 161 }; 162 }; 163} 164``` 165::: 166 167::: {#exec-escaping-example .example} 168::: {.title} 169**Example: Escaping in Exec directives** 170::: 171```nix 172{ config, lib, pkgs, utils, ... }: 173 174with lib; 175 176let 177 cfg = config.services.echo; 178 echoAll = pkgs.writeScript "echo-all" '' 179 #! ${pkgs.runtimeShell} 180 for s in "$@"; do 181 printf '%s\n' "$s" 182 done 183 ''; 184 args = [ "a%Nything" "lang=\${LANG}" ";" "/bin/sh -c date" ]; 185in { 186 systemd.services.echo = 187 { description = "Echo to the journal"; 188 wantedBy = [ "multi-user.target" ]; 189 serviceConfig.Type = "oneshot"; 190 serviceConfig.ExecStart = '' 191 ${echoAll} ${utils.escapeSystemdExecArgs args} 192 ''; 193 }; 194} 195``` 196::: 197 198```{=docbook} 199<xi:include href="option-declarations.section.xml" /> 200<xi:include href="option-types.section.xml" /> 201<xi:include href="option-def.section.xml" /> 202<xi:include href="assertions.section.xml" /> 203<xi:include href="meta-attributes.section.xml" /> 204<xi:include href="importing-modules.section.xml" /> 205<xi:include href="replace-modules.section.xml" /> 206<xi:include href="freeform-modules.section.xml" /> 207<xi:include href="settings-options.section.xml" /> 208```