1# This module creates netboot media containing the given NixOS
2# configuration.
3
4{
5 config,
6 lib,
7 pkgs,
8 modulesPath,
9 ...
10}:
11
12with lib;
13
14{
15 imports = [
16 ../../image/file-options.nix
17 ];
18
19 options = {
20
21 netboot.squashfsCompression = mkOption {
22 default = "zstd -Xcompression-level 19";
23 description = ''
24 Compression settings to use for the squashfs nix store.
25 '';
26 example = "zstd -Xcompression-level 6";
27 type = types.str;
28 };
29
30 netboot.storeContents = mkOption {
31 example = literalExpression "[ pkgs.stdenv ]";
32 description = ''
33 This option lists additional derivations to be included in the
34 Nix store in the generated netboot image.
35 '';
36 };
37
38 };
39
40 config = {
41 # Don't build the GRUB menu builder script, since we don't need it
42 # here and it causes a cyclic dependency.
43 boot.loader.grub.enable = false;
44
45 fileSystems."/" = mkImageMediaOverride {
46 fsType = "tmpfs";
47 options = [ "mode=0755" ];
48 };
49
50 # In stage 1, mount a tmpfs on top of /nix/store (the squashfs
51 # image) to make this a live CD.
52 fileSystems."/nix/.ro-store" = mkImageMediaOverride {
53 fsType = "squashfs";
54 device = "../nix-store.squashfs";
55 options = [
56 "loop"
57 ]
58 ++ lib.optional (config.boot.kernelPackages.kernel.kernelAtLeast "6.2") "threads=multi";
59 neededForBoot = true;
60 };
61
62 fileSystems."/nix/.rw-store" = mkImageMediaOverride {
63 fsType = "tmpfs";
64 options = [ "mode=0755" ];
65 neededForBoot = true;
66 };
67
68 fileSystems."/nix/store" = mkImageMediaOverride {
69 overlay = {
70 lowerdir = [ "/nix/.ro-store" ];
71 upperdir = "/nix/.rw-store/store";
72 workdir = "/nix/.rw-store/work";
73 };
74 neededForBoot = true;
75 };
76
77 boot.initrd.availableKernelModules = [
78 "squashfs"
79 "overlay"
80 ];
81
82 boot.initrd.kernelModules = [
83 "loop"
84 "overlay"
85 ];
86
87 # Closures to be copied to the Nix store, namely the init
88 # script and the top-level system configuration directory.
89 netboot.storeContents = [ config.system.build.toplevel ];
90
91 # Create the squashfs image that contains the Nix store.
92 system.build.squashfsStore = pkgs.callPackage ../../../lib/make-squashfs.nix {
93 storeContents = config.netboot.storeContents;
94 comp = config.netboot.squashfsCompression;
95 };
96
97 # Create the initrd
98 system.build.netbootRamdisk = pkgs.makeInitrdNG {
99 inherit (config.boot.initrd) compressor;
100 prepend = [ "${config.system.build.initialRamdisk}/initrd" ];
101
102 contents = [
103 {
104 source = config.system.build.squashfsStore;
105 target = "/nix-store.squashfs";
106 }
107 ];
108 };
109
110 system.build.netbootIpxeScript = pkgs.writeTextDir "netboot.ipxe" ''
111 #!ipxe
112 # Use the cmdline variable to allow the user to specify custom kernel params
113 # when chainloading this script from other iPXE scripts like netboot.xyz
114 kernel ${pkgs.stdenv.hostPlatform.linux-kernel.target} init=${config.system.build.toplevel}/init initrd=initrd ${toString config.boot.kernelParams} ''${cmdline}
115 initrd initrd
116 boot
117 '';
118
119 # A script invoking kexec on ./bzImage and ./initrd.gz.
120 # Usually used through system.build.kexecTree, but exposed here for composability.
121 system.build.kexecScript = pkgs.writeScript "kexec-boot" ''
122 #!/usr/bin/env bash
123 if ! kexec -v >/dev/null 2>&1; then
124 echo "kexec not found: please install kexec-tools" 2>&1
125 exit 1
126 fi
127 SCRIPT_DIR=$( cd -- "$( dirname -- "''${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
128 kexec --load ''${SCRIPT_DIR}/bzImage \
129 --initrd=''${SCRIPT_DIR}/initrd.gz \
130 --command-line "init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams}"
131 kexec -e
132 '';
133
134 # A tree containing initrd.gz, bzImage and a kexec-boot script.
135 system.build.kexecTree = pkgs.linkFarm "kexec-tree" [
136 {
137 name = "initrd.gz";
138 path = "${config.system.build.netbootRamdisk}/initrd";
139 }
140 {
141 name = "bzImage";
142 path = "${config.system.build.kernel}/${config.system.boot.loader.kernelFile}";
143 }
144 {
145 name = "kexec-boot";
146 path = config.system.build.kexecScript;
147 }
148 ];
149
150 image.extension = "tar.xz";
151 image.filePath = "tarball/${config.image.fileName}";
152 system.nixos.tags = [ "kexec" ];
153 system.build.image = config.system.build.kexecTarball;
154 system.build.kexecTarball =
155 pkgs.callPackage "${toString modulesPath}/../lib/make-system-tarball.nix"
156 {
157 fileName = config.image.baseName;
158 storeContents = [
159 {
160 object = config.system.build.kexecScript;
161 symlink = "/kexec_nixos";
162 }
163 ];
164 contents = [ ];
165 };
166
167 boot.loader.timeout = 10;
168
169 boot.postBootCommands = ''
170 # After booting, register the contents of the Nix store
171 # in the Nix database in the tmpfs.
172 ${config.nix.package}/bin/nix-store --load-db < /nix/store/nix-path-registration
173
174 # nixos-rebuild also requires a "system" profile and an
175 # /etc/NIXOS tag.
176 touch /etc/NIXOS
177 ${config.nix.package}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
178
179 # Set password for user nixos if specified on cmdline
180 # Allows using nixos-anywhere in headless environments
181 for o in $(</proc/cmdline); do
182 case "$o" in
183 live.nixos.passwordHash=*)
184 set -- $(IFS==; echo $o)
185 ${pkgs.gnugrep}/bin/grep -q "root::" /etc/shadow && ${pkgs.shadow}/bin/usermod -p "$2" root
186 ;;
187 live.nixos.password=*)
188 set -- $(IFS==; echo $o)
189 ${pkgs.gnugrep}/bin/grep -q "root::" /etc/shadow && echo "root:$2" | ${pkgs.shadow}/bin/chpasswd
190 ;;
191 esac
192 done
193 '';
194 };
195}