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 ] ++ lib.optional (config.boot.kernelPackages.kernel.kernelAtLeast "6.2") "threads=multi";
58 neededForBoot = true;
59 };
60
61 fileSystems."/nix/.rw-store" = mkImageMediaOverride {
62 fsType = "tmpfs";
63 options = [ "mode=0755" ];
64 neededForBoot = true;
65 };
66
67 fileSystems."/nix/store" = mkImageMediaOverride {
68 overlay = {
69 lowerdir = [ "/nix/.ro-store" ];
70 upperdir = "/nix/.rw-store/store";
71 workdir = "/nix/.rw-store/work";
72 };
73 neededForBoot = true;
74 };
75
76 boot.initrd.availableKernelModules = [
77 "squashfs"
78 "overlay"
79 ];
80
81 boot.initrd.kernelModules = [
82 "loop"
83 "overlay"
84 ];
85
86 # Closures to be copied to the Nix store, namely the init
87 # script and the top-level system configuration directory.
88 netboot.storeContents = [ config.system.build.toplevel ];
89
90 # Create the squashfs image that contains the Nix store.
91 system.build.squashfsStore = pkgs.callPackage ../../../lib/make-squashfs.nix {
92 storeContents = config.netboot.storeContents;
93 comp = config.netboot.squashfsCompression;
94 };
95
96 # Create the initrd
97 system.build.netbootRamdisk = pkgs.makeInitrdNG {
98 inherit (config.boot.initrd) compressor;
99 prepend = [ "${config.system.build.initialRamdisk}/initrd" ];
100
101 contents = [
102 {
103 source = config.system.build.squashfsStore;
104 target = "/nix-store.squashfs";
105 }
106 ];
107 };
108
109 system.build.netbootIpxeScript = pkgs.writeTextDir "netboot.ipxe" ''
110 #!ipxe
111 # Use the cmdline variable to allow the user to specify custom kernel params
112 # when chainloading this script from other iPXE scripts like netboot.xyz
113 kernel ${pkgs.stdenv.hostPlatform.linux-kernel.target} init=${config.system.build.toplevel}/init initrd=initrd ${toString config.boot.kernelParams} ''${cmdline}
114 initrd initrd
115 boot
116 '';
117
118 # A script invoking kexec on ./bzImage and ./initrd.gz.
119 # Usually used through system.build.kexecTree, but exposed here for composability.
120 system.build.kexecScript = pkgs.writeScript "kexec-boot" ''
121 #!/usr/bin/env bash
122 if ! kexec -v >/dev/null 2>&1; then
123 echo "kexec not found: please install kexec-tools" 2>&1
124 exit 1
125 fi
126 SCRIPT_DIR=$( cd -- "$( dirname -- "''${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
127 kexec --load ''${SCRIPT_DIR}/bzImage \
128 --initrd=''${SCRIPT_DIR}/initrd.gz \
129 --command-line "init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams}"
130 kexec -e
131 '';
132
133 # A tree containing initrd.gz, bzImage and a kexec-boot script.
134 system.build.kexecTree = pkgs.linkFarm "kexec-tree" [
135 {
136 name = "initrd.gz";
137 path = "${config.system.build.netbootRamdisk}/initrd";
138 }
139 {
140 name = "bzImage";
141 path = "${config.system.build.kernel}/${config.system.boot.loader.kernelFile}";
142 }
143 {
144 name = "kexec-boot";
145 path = config.system.build.kexecScript;
146 }
147 ];
148
149 image.extension = "tar.xz";
150 image.filePath = "tarball/${config.image.fileName}";
151 system.nixos.tags = [ "kexec" ];
152 system.build.image = config.system.build.kexecTarball;
153 system.build.kexecTarball =
154 pkgs.callPackage "${toString modulesPath}/../lib/make-system-tarball.nix"
155 {
156 fileName = config.image.baseName;
157 storeContents = [
158 {
159 object = config.system.build.kexecScript;
160 symlink = "/kexec_nixos";
161 }
162 ];
163 contents = [ ];
164 };
165
166 boot.loader.timeout = 10;
167
168 boot.postBootCommands = ''
169 # After booting, register the contents of the Nix store
170 # in the Nix database in the tmpfs.
171 ${config.nix.package}/bin/nix-store --load-db < /nix/store/nix-path-registration
172
173 # nixos-rebuild also requires a "system" profile and an
174 # /etc/NIXOS tag.
175 touch /etc/NIXOS
176 ${config.nix.package}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
177
178 # Set password for user nixos if specified on cmdline
179 # Allows using nixos-anywhere in headless environments
180 for o in $(</proc/cmdline); do
181 case "$o" in
182 live.nixos.passwordHash=*)
183 set -- $(IFS==; echo $o)
184 ${pkgs.gnugrep}/bin/grep -q "root::" /etc/shadow && ${pkgs.shadow}/bin/usermod -p "$2" root
185 ;;
186 live.nixos.password=*)
187 set -- $(IFS==; echo $o)
188 ${pkgs.gnugrep}/bin/grep -q "root::" /etc/shadow && echo "root:$2" | ${pkgs.shadow}/bin/chpasswd
189 ;;
190 esac
191 done
192 '';
193 };
194}