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