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