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