1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 cfg = config.nix;
8
9 nix = cfg.package.out;
10
11 nixVersion = getVersion nix;
12
13 isNix23 = versionAtLeast nixVersion "2.3pre";
14
15 makeNixBuildUser = nr: {
16 name = "nixbld${toString nr}";
17 value = {
18 description = "Nix build user ${toString nr}";
19
20 /* For consistency with the setgid(2), setuid(2), and setgroups(2)
21 calls in `libstore/build.cc', don't add any supplementary group
22 here except "nixbld". */
23 uid = builtins.add config.ids.uids.nixbld nr;
24 isSystemUser = true;
25 group = "nixbld";
26 extraGroups = [ "nixbld" ];
27 };
28 };
29
30 nixbldUsers = listToAttrs (map makeNixBuildUser (range 1 cfg.nrBuildUsers));
31
32 nixConf =
33 assert versionAtLeast nixVersion "2.2";
34 pkgs.runCommand "nix.conf" { preferLocalBuild = true; extraOptions = cfg.extraOptions; } (
35 ''
36 cat > $out <<END
37 # WARNING: this file is generated from the nix.* options in
38 # your NixOS configuration, typically
39 # /etc/nixos/configuration.nix. Do not edit it!
40 build-users-group = nixbld
41 max-jobs = ${toString (cfg.maxJobs)}
42 cores = ${toString (cfg.buildCores)}
43 sandbox = ${if (builtins.isBool cfg.useSandbox) then boolToString cfg.useSandbox else cfg.useSandbox}
44 extra-sandbox-paths = ${toString cfg.sandboxPaths}
45 substituters = ${toString cfg.binaryCaches}
46 trusted-substituters = ${toString cfg.trustedBinaryCaches}
47 trusted-public-keys = ${toString cfg.binaryCachePublicKeys}
48 auto-optimise-store = ${boolToString cfg.autoOptimiseStore}
49 require-sigs = ${boolToString cfg.requireSignedBinaryCaches}
50 trusted-users = ${toString cfg.trustedUsers}
51 allowed-users = ${toString cfg.allowedUsers}
52 ${optionalString (!cfg.distributedBuilds) ''
53 builders =
54 ''}
55 system-features = ${toString cfg.systemFeatures}
56 ${optionalString isNix23 ''
57 sandbox-fallback = false
58 ''}
59 $extraOptions
60 END
61 '' + optionalString cfg.checkConfig (
62 if pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform then ''
63 echo "Ignore nix.checkConfig when cross-compiling"
64 '' else ''
65 echo "Checking that Nix can read nix.conf..."
66 ln -s $out ./nix.conf
67 NIX_CONF_DIR=$PWD ${cfg.package}/bin/nix show-config ${optionalString isNix23 "--no-net --option experimental-features nix-command"} >/dev/null
68 '')
69 );
70
71in
72
73{
74 imports = [
75 (mkRenamedOptionModule [ "nix" "useChroot" ] [ "nix" "useSandbox" ])
76 (mkRenamedOptionModule [ "nix" "chrootDirs" ] [ "nix" "sandboxPaths" ])
77 ];
78
79 ###### interface
80
81 options = {
82
83 nix = {
84
85 enable = mkOption {
86 type = types.bool;
87 default = true;
88 description = ''
89 Whether to enable Nix.
90 Disabling Nix makes the system hard to modify and the Nix programs and configuration will not be made available by NixOS itself.
91 '';
92 };
93
94 package = mkOption {
95 type = types.package;
96 default = pkgs.nix;
97 defaultText = literalExpression "pkgs.nix";
98 description = ''
99 This option specifies the Nix package instance to use throughout the system.
100 '';
101 };
102
103 maxJobs = mkOption {
104 type = types.either types.int (types.enum ["auto"]);
105 default = "auto";
106 example = 64;
107 description = ''
108 This option defines the maximum number of jobs that Nix will try to
109 build in parallel. The default is auto, which means it will use all
110 available logical cores. It is recommend to set it to the total
111 number of logical cores in your system (e.g., 16 for two CPUs with 4
112 cores each and hyper-threading).
113 '';
114 };
115
116 autoOptimiseStore = mkOption {
117 type = types.bool;
118 default = false;
119 example = true;
120 description = ''
121 If set to true, Nix automatically detects files in the store that have
122 identical contents, and replaces them with hard links to a single copy.
123 This saves disk space. If set to false (the default), you can still run
124 nix-store --optimise to get rid of duplicate files.
125 '';
126 };
127
128 buildCores = mkOption {
129 type = types.int;
130 default = 0;
131 example = 64;
132 description = ''
133 This option defines the maximum number of concurrent tasks during
134 one build. It affects, e.g., -j option for make.
135 The special value 0 means that the builder should use all
136 available CPU cores in the system. Some builds may become
137 non-deterministic with this option; use with care! Packages will
138 only be affected if enableParallelBuilding is set for them.
139 '';
140 };
141
142 useSandbox = mkOption {
143 type = types.either types.bool (types.enum ["relaxed"]);
144 default = true;
145 description = "
146 If set, Nix will perform builds in a sandboxed environment that it
147 will set up automatically for each build. This prevents impurities
148 in builds by disallowing access to dependencies outside of the Nix
149 store by using network and mount namespaces in a chroot environment.
150 This is enabled by default even though it has a possible performance
151 impact due to the initial setup time of a sandbox for each build. It
152 doesn't affect derivation hashes, so changing this option will not
153 trigger a rebuild of packages.
154 ";
155 };
156
157 sandboxPaths = mkOption {
158 type = types.listOf types.str;
159 default = [];
160 example = [ "/dev" "/proc" ];
161 description =
162 ''
163 Directories from the host filesystem to be included
164 in the sandbox.
165 '';
166 };
167
168 extraOptions = mkOption {
169 type = types.lines;
170 default = "";
171 example = ''
172 keep-outputs = true
173 keep-derivations = true
174 '';
175 description = "Additional text appended to <filename>nix.conf</filename>.";
176 };
177
178 distributedBuilds = mkOption {
179 type = types.bool;
180 default = false;
181 description = ''
182 Whether to distribute builds to the machines listed in
183 <option>nix.buildMachines</option>.
184 '';
185 };
186
187 daemonCPUSchedPolicy = mkOption {
188 type = types.enum ["other" "batch" "idle"];
189 default = "other";
190 example = "batch";
191 description = ''
192 Nix daemon process CPU scheduling policy. This policy propagates to
193 build processes. other is the default scheduling policy for regular
194 tasks. The batch policy is similar to other, but optimised for
195 non-interactive tasks. idle is for extremely low-priority tasks
196 that should only be run when no other task requires CPU time.
197
198 Please note that while using the idle policy may greatly improve
199 responsiveness of a system performing expensive builds, it may also
200 slow down and potentially starve crucial configuration updates
201 during load.
202 '';
203 };
204
205 daemonIOSchedClass = mkOption {
206 type = types.enum ["best-effort" "idle"];
207 default = "best-effort";
208 example = "idle";
209 description = ''
210 Nix daemon process I/O scheduling class. This class propagates to
211 build processes. best-effort is the default class for regular tasks.
212 The idle class is for extremely low-priority tasks that should only
213 perform I/O when no other task does.
214
215 Please note that while using the idle scheduling class can improve
216 responsiveness of a system performing expensive builds, it might also
217 slow down or starve crucial configuration updates during load.
218 '';
219 };
220
221 daemonIOSchedPriority = mkOption {
222 type = types.int;
223 default = 0;
224 example = 1;
225 description = ''
226 Nix daemon process I/O scheduling priority. This priority propagates
227 to build processes. The supported priorities depend on the
228 scheduling policy: With idle, priorities are not used in scheduling
229 decisions. best-effort supports values in the range 0 (high) to 7
230 (low).
231 '';
232 };
233
234 buildMachines = mkOption {
235 type = types.listOf (types.submodule ({
236 options = {
237 hostName = mkOption {
238 type = types.str;
239 example = "nixbuilder.example.org";
240 description = ''
241 The hostname of the build machine.
242 '';
243 };
244 system = mkOption {
245 type = types.nullOr types.str;
246 default = null;
247 example = "x86_64-linux";
248 description = ''
249 The system type the build machine can execute derivations on.
250 Either this attribute or <varname>systems</varname> must be
251 present, where <varname>system</varname> takes precedence if
252 both are set.
253 '';
254 };
255 systems = mkOption {
256 type = types.listOf types.str;
257 default = [];
258 example = [ "x86_64-linux" "aarch64-linux" ];
259 description = ''
260 The system types the build machine can execute derivations on.
261 Either this attribute or <varname>system</varname> must be
262 present, where <varname>system</varname> takes precedence if
263 both are set.
264 '';
265 };
266 sshUser = mkOption {
267 type = types.nullOr types.str;
268 default = null;
269 example = "builder";
270 description = ''
271 The username to log in as on the remote host. This user must be
272 able to log in and run nix commands non-interactively. It must
273 also be privileged to build derivations, so must be included in
274 <option>nix.trustedUsers</option>.
275 '';
276 };
277 sshKey = mkOption {
278 type = types.nullOr types.str;
279 default = null;
280 example = "/root/.ssh/id_buildhost_builduser";
281 description = ''
282 The path to the SSH private key with which to authenticate on
283 the build machine. The private key must not have a passphrase.
284 If null, the building user (root on NixOS machines) must have an
285 appropriate ssh configuration to log in non-interactively.
286
287 Note that for security reasons, this path must point to a file
288 in the local filesystem, *not* to the nix store.
289 '';
290 };
291 maxJobs = mkOption {
292 type = types.int;
293 default = 1;
294 description = ''
295 The number of concurrent jobs the build machine supports. The
296 build machine will enforce its own limits, but this allows hydra
297 to schedule better since there is no work-stealing between build
298 machines.
299 '';
300 };
301 speedFactor = mkOption {
302 type = types.int;
303 default = 1;
304 description = ''
305 The relative speed of this builder. This is an arbitrary integer
306 that indicates the speed of this builder, relative to other
307 builders. Higher is faster.
308 '';
309 };
310 mandatoryFeatures = mkOption {
311 type = types.listOf types.str;
312 default = [];
313 example = [ "big-parallel" ];
314 description = ''
315 A list of features mandatory for this builder. The builder will
316 be ignored for derivations that don't require all features in
317 this list. All mandatory features are automatically included in
318 <varname>supportedFeatures</varname>.
319 '';
320 };
321 supportedFeatures = mkOption {
322 type = types.listOf types.str;
323 default = [];
324 example = [ "kvm" "big-parallel" ];
325 description = ''
326 A list of features supported by this builder. The builder will
327 be ignored for derivations that require features not in this
328 list.
329 '';
330 };
331 };
332 }));
333 default = [];
334 description = ''
335 This option lists the machines to be used if distributed builds are
336 enabled (see <option>nix.distributedBuilds</option>).
337 Nix will perform derivations on those machines via SSH by copying the
338 inputs to the Nix store on the remote machine, starting the build,
339 then copying the output back to the local Nix store.
340 '';
341 };
342
343 # Environment variables for running Nix.
344 envVars = mkOption {
345 type = types.attrs;
346 internal = true;
347 default = {};
348 description = "Environment variables used by Nix.";
349 };
350
351 nrBuildUsers = mkOption {
352 type = types.int;
353 description = ''
354 Number of <literal>nixbld</literal> user accounts created to
355 perform secure concurrent builds. If you receive an error
356 message saying that “all build users are currently in use”,
357 you should increase this value.
358 '';
359 };
360
361 readOnlyStore = mkOption {
362 type = types.bool;
363 default = true;
364 description = ''
365 If set, NixOS will enforce the immutability of the Nix store
366 by making <filename>/nix/store</filename> a read-only bind
367 mount. Nix will automatically make the store writable when
368 needed.
369 '';
370 };
371
372 binaryCaches = mkOption {
373 type = types.listOf types.str;
374 description = ''
375 List of binary cache URLs used to obtain pre-built binaries
376 of Nix packages.
377
378 By default https://cache.nixos.org/ is added,
379 to override it use <literal>lib.mkForce []</literal>.
380 '';
381 };
382
383 trustedBinaryCaches = mkOption {
384 type = types.listOf types.str;
385 default = [ ];
386 example = [ "https://hydra.nixos.org/" ];
387 description = ''
388 List of binary cache URLs that non-root users can use (in
389 addition to those specified using
390 <option>nix.binaryCaches</option>) by passing
391 <literal>--option binary-caches</literal> to Nix commands.
392 '';
393 };
394
395 requireSignedBinaryCaches = mkOption {
396 type = types.bool;
397 default = true;
398 description = ''
399 If enabled (the default), Nix will only download binaries from binary caches if
400 they are cryptographically signed with any of the keys listed in
401 <option>nix.binaryCachePublicKeys</option>. If disabled, signatures are neither
402 required nor checked, so it's strongly recommended that you use only
403 trustworthy caches and https to prevent man-in-the-middle attacks.
404 '';
405 };
406
407 binaryCachePublicKeys = mkOption {
408 type = types.listOf types.str;
409 example = [ "hydra.nixos.org-1:CNHJZBh9K4tP3EKF6FkkgeVYsS3ohTl+oS0Qa8bezVs=" ];
410 description = ''
411 List of public keys used to sign binary caches. If
412 <option>nix.requireSignedBinaryCaches</option> is enabled,
413 then Nix will use a binary from a binary cache if and only
414 if it is signed by <emphasis>any</emphasis> of the keys
415 listed here. By default, only the key for
416 <uri>cache.nixos.org</uri> is included.
417 '';
418 };
419
420 trustedUsers = mkOption {
421 type = types.listOf types.str;
422 default = [ "root" ];
423 example = [ "root" "alice" "@wheel" ];
424 description = ''
425 A list of names of users that have additional rights when
426 connecting to the Nix daemon, such as the ability to specify
427 additional binary caches, or to import unsigned NARs. You
428 can also specify groups by prefixing them with
429 <literal>@</literal>; for instance,
430 <literal>@wheel</literal> means all users in the wheel
431 group.
432 '';
433 };
434
435 allowedUsers = mkOption {
436 type = types.listOf types.str;
437 default = [ "*" ];
438 example = [ "@wheel" "@builders" "alice" "bob" ];
439 description = ''
440 A list of names of users (separated by whitespace) that are
441 allowed to connect to the Nix daemon. As with
442 <option>nix.trustedUsers</option>, you can specify groups by
443 prefixing them with <literal>@</literal>. Also, you can
444 allow all users by specifying <literal>*</literal>. The
445 default is <literal>*</literal>. Note that trusted users are
446 always allowed to connect.
447 '';
448 };
449
450 nixPath = mkOption {
451 type = types.listOf types.str;
452 default =
453 [
454 "nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos"
455 "nixos-config=/etc/nixos/configuration.nix"
456 "/nix/var/nix/profiles/per-user/root/channels"
457 ];
458 description = ''
459 The default Nix expression search path, used by the Nix
460 evaluator to look up paths enclosed in angle brackets
461 (e.g. <literal><nixpkgs></literal>).
462 '';
463 };
464
465 systemFeatures = mkOption {
466 type = types.listOf types.str;
467 example = [ "kvm" "big-parallel" "gccarch-skylake" ];
468 description = ''
469 The supported features of a machine
470 '';
471 };
472
473 checkConfig = mkOption {
474 type = types.bool;
475 default = true;
476 description = ''
477 If enabled (the default), checks that Nix can parse the generated nix.conf.
478 '';
479 };
480
481 registry = mkOption {
482 type = types.attrsOf (types.submodule (
483 let
484 inputAttrs = types.attrsOf (types.oneOf [types.str types.int types.bool types.package]);
485 in
486 { config, name, ... }:
487 { options = {
488 from = mkOption {
489 type = inputAttrs;
490 example = { type = "indirect"; id = "nixpkgs"; };
491 description = "The flake reference to be rewritten.";
492 };
493 to = mkOption {
494 type = inputAttrs;
495 example = { type = "github"; owner = "my-org"; repo = "my-nixpkgs"; };
496 description = "The flake reference to which <option>from></option> is to be rewritten.";
497 };
498 flake = mkOption {
499 type = types.nullOr types.attrs;
500 default = null;
501 example = literalExpression "nixpkgs";
502 description = ''
503 The flake input to which <option>from></option> is to be rewritten.
504 '';
505 };
506 exact = mkOption {
507 type = types.bool;
508 default = true;
509 description = ''
510 Whether the <option>from</option> reference needs to match exactly. If set,
511 a <option>from</option> reference like <literal>nixpkgs</literal> does not
512 match with a reference like <literal>nixpkgs/nixos-20.03</literal>.
513 '';
514 };
515 };
516 config = {
517 from = mkDefault { type = "indirect"; id = name; };
518 to = mkIf (config.flake != null)
519 ({ type = "path";
520 path = config.flake.outPath;
521 } // lib.filterAttrs
522 (n: v: n == "lastModified" || n == "rev" || n == "revCount" || n == "narHash")
523 config.flake);
524 };
525 }
526 ));
527 default = {};
528 description = ''
529 A system-wide flake registry.
530 '';
531 };
532
533 };
534
535 };
536
537
538 ###### implementation
539
540 config = mkIf cfg.enable {
541
542 nix.binaryCachePublicKeys = [ "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" ];
543 nix.binaryCaches = [ "https://cache.nixos.org/" ];
544
545 environment.systemPackages =
546 [ nix
547 pkgs.nix-info
548 ]
549 ++ optional (config.programs.bash.enableCompletion && !versionAtLeast nixVersion "2.4pre") pkgs.nix-bash-completions;
550
551 environment.etc."nix/nix.conf".source = nixConf;
552
553 environment.etc."nix/registry.json".text = builtins.toJSON {
554 version = 2;
555 flakes = mapAttrsToList (n: v: { inherit (v) from to exact; }) cfg.registry;
556 };
557
558 # List of machines for distributed Nix builds in the format
559 # expected by build-remote.pl.
560 environment.etc."nix/machines" =
561 { enable = cfg.buildMachines != [];
562 text =
563 concatMapStrings (machine:
564 "${if machine.sshUser != null then "${machine.sshUser}@" else ""}${machine.hostName} "
565 + (if machine.system != null then machine.system else concatStringsSep "," machine.systems)
566 + " ${if machine.sshKey != null then machine.sshKey else "-"} ${toString machine.maxJobs} "
567 + toString (machine.speedFactor)
568 + " "
569 + concatStringsSep "," (machine.mandatoryFeatures ++ machine.supportedFeatures)
570 + " "
571 + concatStringsSep "," machine.mandatoryFeatures
572 + "\n"
573 ) cfg.buildMachines;
574 };
575 assertions =
576 let badMachine = m: m.system == null && m.systems == [];
577 in [
578 {
579 assertion = !(builtins.any badMachine cfg.buildMachines);
580 message = ''
581 At least one system type (via <varname>system</varname> or
582 <varname>systems</varname>) must be set for every build machine.
583 Invalid machine specifications:
584 '' + " " +
585 (builtins.concatStringsSep "\n "
586 (builtins.map (m: m.hostName)
587 (builtins.filter (badMachine) cfg.buildMachines)));
588 }
589 ];
590
591
592 systemd.packages = [ nix ];
593
594 systemd.sockets.nix-daemon.wantedBy = [ "sockets.target" ];
595
596 systemd.services.nix-daemon =
597 { path = [ nix pkgs.util-linux config.programs.ssh.package ]
598 ++ optionals cfg.distributedBuilds [ pkgs.gzip ];
599
600 environment = cfg.envVars
601 // { CURL_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt"; }
602 // config.networking.proxy.envVars;
603
604 unitConfig.RequiresMountsFor = "/nix/store";
605
606 serviceConfig =
607 { CPUSchedulingPolicy = cfg.daemonCPUSchedPolicy;
608 IOSchedulingClass = cfg.daemonIOSchedClass;
609 IOSchedulingPriority = cfg.daemonIOSchedPriority;
610 LimitNOFILE = 4096;
611 };
612
613 restartTriggers = [ nixConf ];
614 };
615
616 # Set up the environment variables for running Nix.
617 environment.sessionVariables = cfg.envVars //
618 { NIX_PATH = cfg.nixPath;
619 };
620
621 environment.extraInit =
622 ''
623 if [ -e "$HOME/.nix-defexpr/channels" ]; then
624 export NIX_PATH="$HOME/.nix-defexpr/channels''${NIX_PATH:+:$NIX_PATH}"
625 fi
626 '';
627
628 nix.nrBuildUsers = mkDefault (lib.max 32 (if cfg.maxJobs == "auto" then 0 else cfg.maxJobs));
629
630 users.users = nixbldUsers;
631
632 services.xserver.displayManager.hiddenUsers = attrNames nixbldUsers;
633
634 system.activationScripts.nix = stringAfter [ "etc" "users" ]
635 ''
636 install -m 0755 -d /nix/var/nix/{gcroots,profiles}/per-user
637
638 # Subscribe the root user to the NixOS channel by default.
639 if [ ! -e "/root/.nix-channels" ]; then
640 echo "${config.system.defaultChannel} nixos" > "/root/.nix-channels"
641 fi
642 '';
643
644 nix.systemFeatures = mkDefault (
645 [ "nixos-test" "benchmark" "big-parallel" "kvm" ] ++
646 optionals (pkgs.hostPlatform ? gcc.arch) (
647 # a builder can run code for `gcc.arch` and inferior architectures
648 [ "gccarch-${pkgs.hostPlatform.gcc.arch}" ] ++
649 map (x: "gccarch-${x}") lib.systems.architectures.inferiors.${pkgs.hostPlatform.gcc.arch}
650 )
651 );
652
653 };
654
655}