1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 systemBuilder =
7 let
8 kernelPath = "${config.boot.kernelPackages.kernel}/" +
9 "${config.system.boot.loader.kernelFile}";
10 initrdPath = "${config.system.build.initialRamdisk}/" +
11 "${config.system.boot.loader.initrdFile}";
12 in ''
13 mkdir $out
14
15 # Containers don't have their own kernel or initrd. They boot
16 # directly into stage 2.
17 ${optionalString config.boot.kernel.enable ''
18 if [ ! -f ${kernelPath} ]; then
19 echo "The bootloader cannot find the proper kernel image."
20 echo "(Expecting ${kernelPath})"
21 false
22 fi
23
24 ln -s ${kernelPath} $out/kernel
25 ln -s ${config.system.modulesTree} $out/kernel-modules
26 ${optionalString (config.hardware.deviceTree.package != null) ''
27 ln -s ${config.hardware.deviceTree.package} $out/dtbs
28 ''}
29
30 echo -n "$kernelParams" > $out/kernel-params
31
32 ln -s ${initrdPath} $out/initrd
33
34 ln -s ${config.system.build.initialRamdiskSecretAppender}/bin/append-initrd-secrets $out
35
36 ln -s ${config.hardware.firmware}/lib/firmware $out/firmware
37 ''}
38
39 echo "$activationScript" > $out/activate
40 echo "$dryActivationScript" > $out/dry-activate
41 substituteInPlace $out/activate --subst-var out
42 substituteInPlace $out/dry-activate --subst-var out
43 chmod u+x $out/activate $out/dry-activate
44 unset activationScript dryActivationScript
45
46 ${if config.boot.initrd.systemd.enable then ''
47 cp ${config.system.build.bootStage2} $out/prepare-root
48 substituteInPlace $out/prepare-root --subst-var-by systemConfig $out
49 # This must not be a symlink or the abs_path of the grub builder for the tests
50 # will resolve the symlink and we end up with a path that doesn't point to a
51 # system closure.
52 cp "$systemd/lib/systemd/systemd" $out/init
53 '' else ''
54 cp ${config.system.build.bootStage2} $out/init
55 substituteInPlace $out/init --subst-var-by systemConfig $out
56 ''}
57
58 ln -s ${config.system.build.etc}/etc $out/etc
59 ln -s ${config.system.path} $out/sw
60 ln -s "$systemd" $out/systemd
61
62 echo -n "systemd ${toString config.systemd.package.interfaceVersion}" > $out/init-interface-version
63 echo -n "$nixosLabel" > $out/nixos-version
64 echo -n "${config.boot.kernelPackages.stdenv.hostPlatform.system}" > $out/system
65
66 mkdir $out/bin
67 export localeArchive="${config.i18n.glibcLocales}/lib/locale/locale-archive"
68 substituteAll ${./switch-to-configuration.pl} $out/bin/switch-to-configuration
69 chmod +x $out/bin/switch-to-configuration
70 ${optionalString (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) ''
71 if ! output=$($perl/bin/perl -c $out/bin/switch-to-configuration 2>&1); then
72 echo "switch-to-configuration syntax is not valid:"
73 echo "$output"
74 exit 1
75 fi
76 ''}
77
78 ${config.system.systemBuilderCommands}
79
80 echo -n "${toString config.system.extraDependencies}" > $out/extra-dependencies
81
82 ${config.system.extraSystemBuilderCmds}
83 '';
84
85 # Putting it all together. This builds a store path containing
86 # symlinks to the various parts of the built configuration (the
87 # kernel, systemd units, init scripts, etc.) as well as a script
88 # `switch-to-configuration' that activates the configuration and
89 # makes it bootable.
90 baseSystem = pkgs.stdenvNoCC.mkDerivation ({
91 name = "nixos-system-${config.system.name}-${config.system.nixos.label}";
92 preferLocalBuild = true;
93 allowSubstitutes = false;
94 buildCommand = systemBuilder;
95
96 inherit (pkgs) coreutils;
97 systemd = config.systemd.package;
98 shell = "${pkgs.bash}/bin/sh";
99 su = "${pkgs.shadow.su}/bin/su";
100 utillinux = pkgs.util-linux;
101
102 kernelParams = config.boot.kernelParams;
103 installBootLoader = config.system.build.installBootLoader;
104 activationScript = config.system.activationScripts.script;
105 dryActivationScript = config.system.dryActivationScript;
106 nixosLabel = config.system.nixos.label;
107
108 # Needed by switch-to-configuration.
109 perl = pkgs.perl.withPackages (p: with p; [ ConfigIniFiles FileSlurp ]);
110 } // config.system.systemBuilderArgs);
111
112 # Handle assertions and warnings
113
114 failedAssertions = map (x: x.message) (filter (x: !x.assertion) config.assertions);
115
116 baseSystemAssertWarn = if failedAssertions != []
117 then throw "\nFailed assertions:\n${concatStringsSep "\n" (map (x: "- ${x}") failedAssertions)}"
118 else showWarnings config.warnings baseSystem;
119
120 # Replace runtime dependencies
121 system = foldr ({ oldDependency, newDependency }: drv:
122 pkgs.replaceDependency { inherit oldDependency newDependency drv; }
123 ) baseSystemAssertWarn config.system.replaceRuntimeDependencies;
124
125in
126
127{
128 imports = [
129 ../build.nix
130 (mkRemovedOptionModule [ "nesting" "clone" ] "Use `specialisation.«name» = { inheritParentConfig = true; configuration = { ... }; }` instead.")
131 (mkRemovedOptionModule [ "nesting" "children" ] "Use `specialisation.«name».configuration = { ... }` instead.")
132 ];
133
134 options = {
135
136 system.boot.loader.id = mkOption {
137 internal = true;
138 default = "";
139 description = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
159 Name of the initrd file to be passed to the bootloader.
160 '';
161 };
162
163 system.build = {
164 installBootLoader = mkOption {
165 internal = true;
166 # "; true" => make the `$out` argument from switch-to-configuration.pl
167 # go to `true` instead of `echo`, hiding the useless path
168 # from the log.
169 default = "echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2; true";
170 description = lib.mdDoc ''
171 A program that writes a bootloader installation script to the path passed in the first command line argument.
172
173 See `nixos/modules/system/activation/switch-to-configuration.pl`.
174 '';
175 type = types.unique {
176 message = ''
177 Only one bootloader can be enabled at a time. This requirement has not
178 been checked until NixOS 22.05. Earlier versions defaulted to the last
179 definition. Change your configuration to enable only one bootloader.
180 '';
181 } (types.either types.str types.package);
182 };
183
184 toplevel = mkOption {
185 type = types.package;
186 readOnly = true;
187 description = lib.mdDoc ''
188 This option contains the store path that typically represents a NixOS system.
189
190 You can read this path in a custom deployment tool for example.
191 '';
192 };
193 };
194
195
196 system.copySystemConfiguration = mkOption {
197 type = types.bool;
198 default = false;
199 description = lib.mdDoc ''
200 If enabled, copies the NixOS configuration file
201 (usually {file}`/etc/nixos/configuration.nix`)
202 and links it from the resulting system
203 (getting to {file}`/run/current-system/configuration.nix`).
204 Note that only this single file is copied, even if it imports others.
205 '';
206 };
207
208 system.systemBuilderCommands = mkOption {
209 type = types.lines;
210 internal = true;
211 default = "";
212 description = ''
213 This code will be added to the builder creating the system store path.
214 '';
215 };
216
217 system.systemBuilderArgs = mkOption {
218 type = types.attrsOf types.unspecified;
219 internal = true;
220 default = {};
221 description = lib.mdDoc ''
222 `lib.mkDerivation` attributes that will be passed to the top level system builder.
223 '';
224 };
225
226 system.extraSystemBuilderCmds = mkOption {
227 type = types.lines;
228 internal = true;
229 default = "";
230 description = lib.mdDoc ''
231 This code will be added to the builder creating the system store path.
232 '';
233 };
234
235 system.extraDependencies = mkOption {
236 type = types.listOf types.package;
237 default = [];
238 description = lib.mdDoc ''
239 A list of packages that should be included in the system
240 closure but not otherwise made available to users. This is
241 primarily used by the installation tests.
242 '';
243 };
244
245 system.replaceRuntimeDependencies = mkOption {
246 default = [];
247 example = lib.literalExpression "[ ({ original = pkgs.openssl; replacement = pkgs.callPackage /path/to/openssl { }; }) ]";
248 type = types.listOf (types.submodule (
249 { ... }: {
250 options.original = mkOption {
251 type = types.package;
252 description = lib.mdDoc "The original package to override.";
253 };
254
255 options.replacement = mkOption {
256 type = types.package;
257 description = lib.mdDoc "The replacement package.";
258 };
259 })
260 );
261 apply = map ({ original, replacement, ... }: {
262 oldDependency = original;
263 newDependency = replacement;
264 });
265 description = lib.mdDoc ''
266 List of packages to override without doing a full rebuild.
267 The original derivation and replacement derivation must have the same
268 name length, and ideally should have close-to-identical directory layout.
269 '';
270 };
271
272 system.name = mkOption {
273 type = types.str;
274 default =
275 if config.networking.hostName == ""
276 then "unnamed"
277 else config.networking.hostName;
278 defaultText = literalExpression ''
279 if config.networking.hostName == ""
280 then "unnamed"
281 else config.networking.hostName;
282 '';
283 description = lib.mdDoc ''
284 The name of the system used in the {option}`system.build.toplevel` derivation.
285
286 That derivation has the following name:
287 `"nixos-system-''${config.system.name}-''${config.system.nixos.label}"`
288 '';
289 };
290
291 };
292
293
294 config = {
295
296 system.extraSystemBuilderCmds =
297 optionalString
298 config.system.copySystemConfiguration
299 ''ln -s '${import ../../../lib/from-env.nix "NIXOS_CONFIG" <nixos-config>}' \
300 "$out/configuration.nix"
301 '';
302
303 system.build.toplevel = system;
304
305 };
306
307}