1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 cfg = config.nix;
8
9 nix = cfg.package.out;
10
11 isNix20 = versionAtLeast (getVersion nix) "2.0pre";
12
13 makeNixBuildUser = nr:
14 { name = "nixbld${toString nr}";
15 description = "Nix build user ${toString nr}";
16
17 /* For consistency with the setgid(2), setuid(2), and setgroups(2)
18 calls in `libstore/build.cc', don't add any supplementary group
19 here except "nixbld". */
20 uid = builtins.add config.ids.uids.nixbld nr;
21 group = "nixbld";
22 extraGroups = [ "nixbld" ];
23 };
24
25 nixbldUsers = map makeNixBuildUser (range 1 cfg.nrBuildUsers);
26
27 nixConf =
28 let
29 # In Nix < 2.0, If we're using sandbox for builds, then provide
30 # /bin/sh in the sandbox as a bind-mount to bash. This means we
31 # also need to include the entire closure of bash. Nix >= 2.0
32 # provides a /bin/sh by default.
33 sh = pkgs.runtimeShell;
34 binshDeps = pkgs.writeReferencesToFile sh;
35 in
36 pkgs.runCommand "nix.conf" { extraOptions = cfg.extraOptions; } (''
37 ${optionalString (!isNix20) ''
38 extraPaths=$(for i in $(cat ${binshDeps}); do if test -d $i; then echo $i; fi; done)
39 ''}
40 cat > $out <<END
41 # WARNING: this file is generated from the nix.* options in
42 # your NixOS configuration, typically
43 # /etc/nixos/configuration.nix. Do not edit it!
44 build-users-group = nixbld
45 ${if isNix20 then "max-jobs" else "build-max-jobs"} = ${toString (cfg.maxJobs)}
46 ${if isNix20 then "cores" else "build-cores"} = ${toString (cfg.buildCores)}
47 ${if isNix20 then "sandbox" else "build-use-sandbox"} = ${if (builtins.isBool cfg.useSandbox) then boolToString cfg.useSandbox else cfg.useSandbox}
48 ${if isNix20 then "extra-sandbox-paths" else "build-sandbox-paths"} = ${toString cfg.sandboxPaths} ${optionalString (!isNix20) "/bin/sh=${sh} $(echo $extraPaths)"}
49 ${if isNix20 then "substituters" else "binary-caches"} = ${toString cfg.binaryCaches}
50 ${if isNix20 then "trusted-substituters" else "trusted-binary-caches"} = ${toString cfg.trustedBinaryCaches}
51 ${if isNix20 then "trusted-public-keys" else "binary-cache-public-keys"} = ${toString cfg.binaryCachePublicKeys}
52 auto-optimise-store = ${boolToString cfg.autoOptimiseStore}
53 ${if isNix20 then ''
54 require-sigs = ${if cfg.requireSignedBinaryCaches then "true" else "false"}
55 '' else ''
56 signed-binary-caches = ${if cfg.requireSignedBinaryCaches then "*" else ""}
57 ''}
58 trusted-users = ${toString cfg.trustedUsers}
59 allowed-users = ${toString cfg.allowedUsers}
60 ${optionalString (isNix20 && !cfg.distributedBuilds) ''
61 builders =
62 ''}
63 $extraOptions
64 END
65 '' + optionalString cfg.checkConfig ''
66 echo "Checking that Nix can read nix.conf..."
67 ln -s $out ./nix.conf
68 NIX_CONF_DIR=$PWD ${cfg.package}/bin/nix show-config >/dev/null
69 '');
70
71in
72
73{
74
75 ###### interface
76
77 options = {
78
79 nix = {
80
81 package = mkOption {
82 type = types.package;
83 default = pkgs.nix;
84 defaultText = "pkgs.nix";
85 description = ''
86 This option specifies the Nix package instance to use throughout the system.
87 '';
88 };
89
90 maxJobs = mkOption {
91 type = types.either types.int (types.enum ["auto"]);
92 default = 1;
93 example = 64;
94 description = ''
95 This option defines the maximum number of jobs that Nix will try
96 to build in parallel. The default is 1. You should generally
97 set it to the total number of logical cores in your system (e.g., 16
98 for two CPUs with 4 cores each and hyper-threading).
99 '';
100 };
101
102 autoOptimiseStore = mkOption {
103 type = types.bool;
104 default = false;
105 example = true;
106 description = ''
107 If set to true, Nix automatically detects files in the store that have
108 identical contents, and replaces them with hard links to a single copy.
109 This saves disk space. If set to false (the default), you can still run
110 nix-store --optimise to get rid of duplicate files.
111 '';
112 };
113
114 buildCores = mkOption {
115 type = types.int;
116 default = 1;
117 example = 64;
118 description = ''
119 This option defines the maximum number of concurrent tasks during
120 one build. It affects, e.g., -j option for make. The default is 1.
121 The special value 0 means that the builder should use all
122 available CPU cores in the system. Some builds may become
123 non-deterministic with this option; use with care! Packages will
124 only be affected if enableParallelBuilding is set for them.
125 '';
126 };
127
128 useSandbox = mkOption {
129 type = types.either types.bool (types.enum ["relaxed"]);
130 default = true;
131 description = "
132 If set, Nix will perform builds in a sandboxed environment that it
133 will set up automatically for each build. This prevents impurities
134 in builds by disallowing access to dependencies outside of the Nix
135 store by using network and mount namespaces in a chroot environment.
136 This is enabled by default even though it has a possible performance
137 impact due to the initial setup time of a sandbox for each build. It
138 doesn't affect derivation hashes, so changing this option will not
139 trigger a rebuild of packages.
140 ";
141 };
142
143 sandboxPaths = mkOption {
144 type = types.listOf types.str;
145 default = [];
146 example = [ "/dev" "/proc" ];
147 description =
148 ''
149 Directories from the host filesystem to be included
150 in the sandbox.
151 '';
152 };
153
154 extraOptions = mkOption {
155 type = types.lines;
156 default = "";
157 example = ''
158 gc-keep-outputs = true
159 gc-keep-derivations = true
160 '';
161 description = "Additional text appended to <filename>nix.conf</filename>.";
162 };
163
164 distributedBuilds = mkOption {
165 type = types.bool;
166 default = false;
167 description = ''
168 Whether to distribute builds to the machines listed in
169 <option>nix.buildMachines</option>.
170 '';
171 };
172
173 daemonNiceLevel = mkOption {
174 type = types.int;
175 default = 0;
176 description = ''
177 Nix daemon process priority. This priority propagates to build processes.
178 0 is the default Unix process priority, 19 is the lowest.
179 '';
180 };
181
182 daemonIONiceLevel = mkOption {
183 type = types.int;
184 default = 0;
185 description = ''
186 Nix daemon process I/O priority. This priority propagates to build processes.
187 0 is the default Unix process I/O priority, 7 is the lowest.
188 '';
189 };
190
191 buildMachines = mkOption {
192 type = types.listOf types.attrs;
193 default = [];
194 example = literalExample ''
195 [ { hostName = "voila.labs.cs.uu.nl";
196 sshUser = "nix";
197 sshKey = "/root/.ssh/id_buildfarm";
198 system = "powerpc-darwin";
199 maxJobs = 1;
200 }
201 { hostName = "linux64.example.org";
202 sshUser = "buildfarm";
203 sshKey = "/root/.ssh/id_buildfarm";
204 system = "x86_64-linux";
205 maxJobs = 2;
206 speedFactor = 2;
207 supportedFeatures = [ "kvm" ];
208 mandatoryFeatures = [ "perf" ];
209 }
210 ]
211 '';
212 description = ''
213 This option lists the machines to be used if distributed
214 builds are enabled (see
215 <option>nix.distributedBuilds</option>). Nix will perform
216 derivations on those machines via SSH by copying the inputs
217 to the Nix store on the remote machine, starting the build,
218 then copying the output back to the local Nix store. Each
219 element of the list should be an attribute set containing
220 the machine's host name (<varname>hostname</varname>), the
221 user name to be used for the SSH connection
222 (<varname>sshUser</varname>), the Nix system type
223 (<varname>system</varname>, e.g.,
224 <literal>"i686-linux"</literal>), the maximum number of
225 jobs to be run in parallel on that machine
226 (<varname>maxJobs</varname>), the path to the SSH private
227 key to be used to connect (<varname>sshKey</varname>), a
228 list of supported features of the machine
229 (<varname>supportedFeatures</varname>) and a list of
230 mandatory features of the machine
231 (<varname>mandatoryFeatures</varname>). The SSH private key
232 should not have a passphrase, and the corresponding public
233 key should be added to
234 <filename>~<replaceable>sshUser</replaceable>/authorized_keys</filename>
235 on the remote machine.
236 '';
237 };
238
239 # Environment variables for running Nix.
240 envVars = mkOption {
241 type = types.attrs;
242 internal = true;
243 default = {};
244 description = "Environment variables used by Nix.";
245 };
246
247 nrBuildUsers = mkOption {
248 type = types.int;
249 description = ''
250 Number of <literal>nixbld</literal> user accounts created to
251 perform secure concurrent builds. If you receive an error
252 message saying that “all build users are currently in use”,
253 you should increase this value.
254 '';
255 };
256
257 readOnlyStore = mkOption {
258 type = types.bool;
259 default = true;
260 description = ''
261 If set, NixOS will enforce the immutability of the Nix store
262 by making <filename>/nix/store</filename> a read-only bind
263 mount. Nix will automatically make the store writable when
264 needed.
265 '';
266 };
267
268 binaryCaches = mkOption {
269 type = types.listOf types.str;
270 default = [ https://cache.nixos.org/ ];
271 description = ''
272 List of binary cache URLs used to obtain pre-built binaries
273 of Nix packages.
274 '';
275 };
276
277 trustedBinaryCaches = mkOption {
278 type = types.listOf types.str;
279 default = [ ];
280 example = [ http://hydra.nixos.org/ ];
281 description = ''
282 List of binary cache URLs that non-root users can use (in
283 addition to those specified using
284 <option>nix.binaryCaches</option>) by passing
285 <literal>--option binary-caches</literal> to Nix commands.
286 '';
287 };
288
289 requireSignedBinaryCaches = mkOption {
290 type = types.bool;
291 default = true;
292 description = ''
293 If enabled (the default), Nix will only download binaries from binary caches if
294 they are cryptographically signed with any of the keys listed in
295 <option>nix.binaryCachePublicKeys</option>. If disabled, signatures are neither
296 required nor checked, so it's strongly recommended that you use only
297 trustworthy caches and https to prevent man-in-the-middle attacks.
298 '';
299 };
300
301 binaryCachePublicKeys = mkOption {
302 type = types.listOf types.str;
303 example = [ "hydra.nixos.org-1:CNHJZBh9K4tP3EKF6FkkgeVYsS3ohTl+oS0Qa8bezVs=" ];
304 description = ''
305 List of public keys used to sign binary caches. If
306 <option>nix.requireSignedBinaryCaches</option> is enabled,
307 then Nix will use a binary from a binary cache if and only
308 if it is signed by <emphasis>any</emphasis> of the keys
309 listed here. By default, only the key for
310 <uri>cache.nixos.org</uri> is included.
311 '';
312 };
313
314 trustedUsers = mkOption {
315 type = types.listOf types.str;
316 default = [ "root" ];
317 example = [ "root" "alice" "@wheel" ];
318 description = ''
319 A list of names of users that have additional rights when
320 connecting to the Nix daemon, such as the ability to specify
321 additional binary caches, or to import unsigned NARs. You
322 can also specify groups by prefixing them with
323 <literal>@</literal>; for instance,
324 <literal>@wheel</literal> means all users in the wheel
325 group.
326 '';
327 };
328
329 allowedUsers = mkOption {
330 type = types.listOf types.str;
331 default = [ "*" ];
332 example = [ "@wheel" "@builders" "alice" "bob" ];
333 description = ''
334 A list of names of users (separated by whitespace) that are
335 allowed to connect to the Nix daemon. As with
336 <option>nix.trustedUsers</option>, you can specify groups by
337 prefixing them with <literal>@</literal>. Also, you can
338 allow all users by specifying <literal>*</literal>. The
339 default is <literal>*</literal>. Note that trusted users are
340 always allowed to connect.
341 '';
342 };
343
344 nixPath = mkOption {
345 type = types.listOf types.str;
346 default =
347 [
348 "$HOME/.nix-defexpr/channels"
349 "nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos"
350 "nixos-config=/etc/nixos/configuration.nix"
351 "/nix/var/nix/profiles/per-user/root/channels"
352 ];
353 description = ''
354 The default Nix expression search path, used by the Nix
355 evaluator to look up paths enclosed in angle brackets
356 (e.g. <literal><nixpkgs></literal>).
357 '';
358 };
359
360 checkConfig = mkOption {
361 type = types.bool;
362 default = true;
363 description = ''
364 If enabled (the default), checks that Nix can parse the generated nix.conf.
365 '';
366 };
367 };
368
369 };
370
371
372 ###### implementation
373
374 config = {
375
376 nix.binaryCachePublicKeys = [ "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" ];
377
378 environment.etc."nix/nix.conf".source = nixConf;
379
380 # List of machines for distributed Nix builds in the format
381 # expected by build-remote.pl.
382 environment.etc."nix/machines" =
383 { enable = cfg.buildMachines != [];
384 text =
385 concatMapStrings (machine:
386 "${if machine ? sshUser then "${machine.sshUser}@" else ""}${machine.hostName} "
387 + machine.system or (concatStringsSep "," machine.systems)
388 + " ${machine.sshKey or "-"} ${toString machine.maxJobs or 1} "
389 + toString (machine.speedFactor or 1)
390 + " "
391 + concatStringsSep "," (machine.mandatoryFeatures or [] ++ machine.supportedFeatures or [])
392 + " "
393 + concatStringsSep "," machine.mandatoryFeatures or []
394 + "\n"
395 ) cfg.buildMachines;
396 };
397
398 systemd.packages = [ nix ];
399
400 systemd.sockets.nix-daemon.wantedBy = [ "sockets.target" ];
401
402 systemd.services.nix-daemon =
403 { path = [ nix pkgs.utillinux ]
404 ++ optionals cfg.distributedBuilds [ config.programs.ssh.package pkgs.gzip ]
405 ++ optionals (!isNix20) [ pkgs.openssl.bin ];
406
407 environment = cfg.envVars
408 // { CURL_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt"; }
409 // config.networking.proxy.envVars;
410
411 unitConfig.RequiresMountsFor = "/nix/store";
412
413 serviceConfig =
414 { Nice = cfg.daemonNiceLevel;
415 IOSchedulingPriority = cfg.daemonIONiceLevel;
416 LimitNOFILE = 4096;
417 };
418
419 restartTriggers = [ nixConf ];
420 };
421
422 nix.envVars =
423 optionalAttrs (!isNix20) {
424 NIX_CONF_DIR = "/etc/nix";
425
426 # Enable the copy-from-other-stores substituter, which allows
427 # builds to be sped up by copying build results from remote
428 # Nix stores. To do this, mount the remote file system on a
429 # subdirectory of /run/nix/remote-stores.
430 NIX_OTHER_STORES = "/run/nix/remote-stores/*/nix";
431 }
432
433 // optionalAttrs (cfg.distributedBuilds && !isNix20) {
434 NIX_BUILD_HOOK = "${nix}/libexec/nix/build-remote.pl";
435 };
436
437 # Set up the environment variables for running Nix.
438 environment.sessionVariables = cfg.envVars //
439 { NIX_PATH = concatStringsSep ":" cfg.nixPath;
440 };
441
442 environment.extraInit = optionalString (!isNix20)
443 ''
444 # Set up secure multi-user builds: non-root users build through the
445 # Nix daemon.
446 if [ "$USER" != root -o ! -w /nix/var/nix/db ]; then
447 export NIX_REMOTE=daemon
448 fi
449 '';
450
451 nix.nrBuildUsers = mkDefault (lib.max 32 cfg.maxJobs);
452
453 users.users = nixbldUsers;
454
455 services.xserver.displayManager.hiddenUsers = map ({ name, ... }: name) nixbldUsers;
456
457 # FIXME: use systemd-tmpfiles to create Nix directories.
458 system.activationScripts.nix = stringAfter [ "etc" "users" ]
459 ''
460 # Nix initialisation.
461 install -m 0755 -d \
462 /nix/var/nix/gcroots \
463 /nix/var/nix/temproots \
464 /nix/var/nix/userpool \
465 /nix/var/nix/profiles \
466 /nix/var/nix/db \
467 /nix/var/log/nix/drvs
468 install -m 1777 -d \
469 /nix/var/nix/gcroots/per-user \
470 /nix/var/nix/profiles/per-user \
471 /nix/var/nix/gcroots/tmp
472 '';
473
474 };
475
476}