1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8with lib;
9
10let
11 systemBuilder = ''
12 mkdir $out
13
14 ${
15 if config.boot.initrd.systemd.enable then
16 ''
17 cp ${config.system.build.bootStage2} $out/prepare-root
18 substituteInPlace $out/prepare-root --subst-var-by systemConfig $out
19 # This must not be a symlink or the abs_path of the grub builder for the tests
20 # will resolve the symlink and we end up with a path that doesn't point to a
21 # system closure.
22 cp "$systemd/lib/systemd/systemd" $out/init
23 ''
24 else
25 ''
26 cp ${config.system.build.bootStage2} $out/init
27 substituteInPlace $out/init --subst-var-by systemConfig $out
28 ''
29 }
30
31 ln -s ${config.system.build.etc}/etc $out/etc
32
33 ${lib.optionalString config.system.etc.overlay.enable ''
34 ln -s ${config.system.build.etcMetadataImage} $out/etc-metadata-image
35 ln -s ${config.system.build.etcBasedir} $out/etc-basedir
36 ''}
37
38 ln -s ${config.system.path} $out/sw
39 ln -s "$systemd" $out/systemd
40
41 echo -n "systemd ${toString config.systemd.package.interfaceVersion}" > $out/init-interface-version
42 echo -n "$nixosLabel" > $out/nixos-version
43 echo -n "${config.boot.kernelPackages.stdenv.hostPlatform.system}" > $out/system
44
45 ${config.system.systemBuilderCommands}
46
47 cp "$extraDependenciesPath" "$out/extra-dependencies"
48
49 ${optionalString (!config.boot.isContainer && config.boot.bootspec.enable) ''
50 ${config.boot.bootspec.writer}
51 ${optionalString config.boot.bootspec.enableValidation ''${config.boot.bootspec.validator} "$out/${config.boot.bootspec.filename}"''}
52 ''}
53
54 ${config.system.extraSystemBuilderCmds}
55 '';
56
57 # Putting it all together. This builds a store path containing
58 # symlinks to the various parts of the built configuration (the
59 # kernel, systemd units, init scripts, etc.) as well as a script
60 # `switch-to-configuration' that activates the configuration and
61 # makes it bootable. See `activatable-system.nix`.
62 baseSystem = pkgs.stdenvNoCC.mkDerivation (
63 {
64 name = "nixos-system-${config.system.name}-${config.system.nixos.label}";
65 preferLocalBuild = true;
66 allowSubstitutes = false;
67 passAsFile = [ "extraDependencies" ];
68 buildCommand = systemBuilder;
69
70 systemd = config.systemd.package;
71
72 nixosLabel = config.system.nixos.label;
73
74 inherit (config.system) extraDependencies;
75 }
76 // config.system.systemBuilderArgs
77 );
78
79 # Handle assertions and warnings
80 baseSystemAssertWarn = lib.asserts.checkAssertWarn config.assertions config.warnings baseSystem;
81
82 # Replace runtime dependencies
83 system =
84 let
85 inherit (config.system.replaceDependencies) replacements cutoffPackages;
86 in
87 if replacements == [ ] then
88 # Avoid IFD if possible, by sidestepping replaceDependencies if no replacements are specified.
89 baseSystemAssertWarn
90 else
91 (pkgs.replaceDependencies.override {
92 replaceDirectDependencies = pkgs.replaceDirectDependencies.override {
93 nix = config.nix.package;
94 };
95 })
96 {
97 drv = baseSystemAssertWarn;
98 inherit replacements cutoffPackages;
99 };
100
101 systemWithBuildDeps = system.overrideAttrs (o: {
102 systemBuildClosure = pkgs.closureInfo { rootPaths = [ system.drvPath ]; };
103 buildCommand =
104 o.buildCommand
105 + ''
106 ln -sn $systemBuildClosure $out/build-closure
107 '';
108 });
109
110in
111
112{
113 imports = [
114 ../build.nix
115 (mkRemovedOptionModule [
116 "nesting"
117 "clone"
118 ] "Use `specialisation.«name» = { inheritParentConfig = true; configuration = { ... }; }` instead.")
119 (mkRemovedOptionModule [
120 "nesting"
121 "children"
122 ] "Use `specialisation.«name».configuration = { ... }` instead.")
123 (mkRenamedOptionModule
124 [ "system" "forbiddenDependenciesRegex" ]
125 [ "system" "forbiddenDependenciesRegexes" ]
126 )
127 (mkRenamedOptionModule
128 [ "system" "replaceRuntimeDependencies" ]
129 [ "system" "replaceDependencies" "replacements" ]
130 )
131 ];
132
133 options = {
134
135 system.boot.loader.id = mkOption {
136 internal = true;
137 default = "";
138 description = ''
139 Id string of the used bootloader.
140 '';
141 };
142
143 system.boot.loader.kernelFile = mkOption {
144 internal = true;
145 default = pkgs.stdenv.hostPlatform.linux-kernel.target;
146 defaultText = literalExpression "pkgs.stdenv.hostPlatform.linux-kernel.target";
147 type = types.str;
148 description = ''
149 Name of the kernel file to be passed to the bootloader.
150 '';
151 };
152
153 system.boot.loader.initrdFile = mkOption {
154 internal = true;
155 default = "initrd";
156 type = types.str;
157 description = ''
158 Name of the initrd file to be passed to the bootloader.
159 '';
160 };
161
162 system.build = {
163 toplevel = mkOption {
164 type = types.package;
165 readOnly = true;
166 description = ''
167 This option contains the store path that typically represents a NixOS system.
168
169 You can read this path in a custom deployment tool for example.
170 '';
171 };
172 };
173
174 system.copySystemConfiguration = mkOption {
175 type = types.bool;
176 default = false;
177 description = ''
178 If enabled, copies the NixOS configuration file
179 (usually {file}`/etc/nixos/configuration.nix`)
180 and symlinks it from the resulting system
181 (getting to {file}`/run/current-system/configuration.nix`).
182 Note that only this single file is copied, even if it imports others.
183 Warning: This feature cannot be used when the system is configured by a flake
184 '';
185 };
186
187 system.systemBuilderCommands = mkOption {
188 type = types.lines;
189 internal = true;
190 default = "";
191 description = ''
192 This code will be added to the builder creating the system store path.
193 '';
194 };
195
196 system.systemBuilderArgs = mkOption {
197 type = types.attrsOf types.unspecified;
198 internal = true;
199 default = { };
200 description = ''
201 `lib.mkDerivation` attributes that will be passed to the top level system builder.
202 '';
203 };
204
205 system.forbiddenDependenciesRegexes = mkOption {
206 default = [ ];
207 example = [ "-dev$" ];
208 type = types.listOf types.str;
209 description = ''
210 POSIX Extended Regular Expressions that match store paths that
211 should not appear in the system closure, with the exception of {option}`system.extraDependencies`, which is not checked.
212 '';
213 };
214
215 system.extraSystemBuilderCmds = mkOption {
216 type = types.lines;
217 internal = true;
218 default = "";
219 description = ''
220 This code will be added to the builder creating the system store path.
221 '';
222 };
223
224 system.extraDependencies = mkOption {
225 type = types.listOf types.pathInStore;
226 default = [ ];
227 description = ''
228 A list of paths that should be included in the system
229 closure but generally not visible to users.
230
231 This option has also been used for build-time checks, but the
232 `system.checks` option is more appropriate for that purpose as checks
233 should not leave a trace in the built system configuration.
234 '';
235 };
236
237 system.checks = mkOption {
238 type = types.listOf types.package;
239 default = [ ];
240 description = ''
241 Packages that are added as dependencies of the system's build, usually
242 for the purpose of validating some part of the configuration.
243
244 Unlike `system.extraDependencies`, these store paths do not
245 become part of the built system configuration.
246 '';
247 };
248
249 system.replaceDependencies = {
250 replacements = mkOption {
251 default = [ ];
252 example = lib.literalExpression "[ ({ oldDependency = pkgs.openssl; newDependency = pkgs.callPackage /path/to/openssl { }; }) ]";
253 type = types.listOf (
254 types.submodule (
255 { ... }:
256 {
257 imports = [
258 (mkRenamedOptionModule [ "original" ] [ "oldDependency" ])
259 (mkRenamedOptionModule [ "replacement" ] [ "newDependency" ])
260 ];
261
262 options.oldDependency = mkOption {
263 type = types.package;
264 description = "The original package to override.";
265 };
266
267 options.newDependency = mkOption {
268 type = types.package;
269 description = "The replacement package.";
270 };
271 }
272 )
273 );
274 apply = map (
275 { oldDependency, newDependency, ... }:
276 {
277 inherit oldDependency newDependency;
278 }
279 );
280 description = ''
281 List of packages to override without doing a full rebuild.
282 The original derivation and replacement derivation must have the same
283 name length, and ideally should have close-to-identical directory layout.
284 '';
285 };
286
287 cutoffPackages = mkOption {
288 default = [ config.system.build.initialRamdisk ];
289 defaultText = literalExpression "[ config.system.build.initialRamdisk ]";
290 type = types.listOf types.package;
291 description = ''
292 Packages to which no replacements should be applied.
293 The initrd is matched by default, because its structure renders the replacement process ineffective and prone to breakage.
294 '';
295 };
296 };
297
298 system.name = mkOption {
299 type = types.str;
300 default = if config.networking.hostName == "" then "unnamed" else config.networking.hostName;
301 defaultText = literalExpression ''
302 if config.networking.hostName == ""
303 then "unnamed"
304 else config.networking.hostName;
305 '';
306 description = ''
307 The name of the system used in the {option}`system.build.toplevel` derivation.
308
309 That derivation has the following name:
310 `"nixos-system-''${config.system.name}-''${config.system.nixos.label}"`
311 '';
312 };
313
314 system.includeBuildDependencies = mkOption {
315 type = types.bool;
316 default = false;
317 description = ''
318 Whether to include the build closure of the whole system in
319 its runtime closure. This can be useful for making changes
320 fully offline, as it includes all sources, patches, and
321 intermediate outputs required to build all the derivations
322 that the system depends on.
323
324 Note that this includes _all_ the derivations, down from the
325 included applications to their sources, the compilers used to
326 build them, and even the bootstrap compiler used to compile
327 the compilers. This increases the size of the system and the
328 time needed to download its dependencies drastically: a
329 minimal configuration with no extra services enabled grows
330 from ~670MiB in size to 13.5GiB, and takes proportionally
331 longer to download.
332 '';
333 };
334
335 };
336
337 config = {
338 assertions = [
339 {
340 assertion = config.system.copySystemConfiguration -> !lib.inPureEvalMode;
341 message = "system.copySystemConfiguration is not supported with flakes";
342 }
343 ];
344
345 system.extraSystemBuilderCmds =
346 optionalString config.system.copySystemConfiguration ''
347 ln -s '${import ../../../lib/from-env.nix "NIXOS_CONFIG" <nixos-config>}' \
348 "$out/configuration.nix"
349 ''
350 + optionalString (config.system.forbiddenDependenciesRegexes != [ ]) (
351 lib.concatStringsSep "\n" (
352 map (regex: ''
353 if [[ ${regex} != "" && -n $closureInfo ]]; then
354 if forbiddenPaths="$(grep -E -- "${regex}" $closureInfo/store-paths)"; then
355 echo -e "System closure $out contains the following disallowed paths:\n$forbiddenPaths"
356 exit 1
357 fi
358 fi
359 '') config.system.forbiddenDependenciesRegexes
360 )
361 );
362
363 system.systemBuilderArgs =
364 {
365
366 # Legacy environment variables. These were used by the activation script,
367 # but some other script might still depend on them, although unlikely.
368 installBootLoader = config.system.build.installBootLoader;
369 localeArchive = "${config.i18n.glibcLocales}/lib/locale/locale-archive";
370 distroId = config.system.nixos.distroId;
371 perl = pkgs.perl.withPackages (
372 p: with p; [
373 ConfigIniFiles
374 FileSlurp
375 ]
376 );
377 # End if legacy environment variables
378
379 preSwitchCheck = config.system.preSwitchChecksScript;
380
381 # Not actually used in the builder. `passedChecks` is just here to create
382 # the build dependencies. Checks are similar to build dependencies in the
383 # sense that if they fail, the system build fails. However, checks do not
384 # produce any output of value, so they are not used by the system builder.
385 # In fact, using them runs the risk of accidentally adding unneeded paths
386 # to the system closure, which defeats the purpose of the `system.checks`
387 # option, as opposed to `system.extraDependencies`.
388 passedChecks = concatStringsSep " " config.system.checks;
389 }
390 // lib.optionalAttrs (config.system.forbiddenDependenciesRegexes != [ ]) {
391 closureInfo = pkgs.closureInfo {
392 rootPaths = [
393 # override to avoid infinite recursion (and to allow using extraDependencies to add forbidden dependencies)
394 (config.system.build.toplevel.overrideAttrs (_: {
395 extraDependencies = [ ];
396 closureInfo = null;
397 }))
398 ];
399 };
400 };
401
402 system.build.toplevel =
403 if config.system.includeBuildDependencies then systemWithBuildDeps else system;
404
405 };
406
407}