at 23.05-pre 11 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.boot.loader.systemd-boot; 7 8 efi = config.boot.loader.efi; 9 10 systemdBootBuilder = pkgs.substituteAll { 11 src = ./systemd-boot-builder.py; 12 13 isExecutable = true; 14 15 inherit (pkgs) python3; 16 17 systemd = config.systemd.package; 18 19 nix = config.nix.package.out; 20 21 timeout = if config.boot.loader.timeout != null then config.boot.loader.timeout else ""; 22 23 editor = if cfg.editor then "True" else "False"; 24 25 configurationLimit = if cfg.configurationLimit == null then 0 else cfg.configurationLimit; 26 27 inherit (cfg) consoleMode graceful; 28 29 inherit (efi) efiSysMountPoint canTouchEfiVariables; 30 31 memtest86 = if cfg.memtest86.enable then pkgs.memtest86-efi else ""; 32 33 netbootxyz = if cfg.netbootxyz.enable then pkgs.netbootxyz-efi else ""; 34 35 copyExtraFiles = pkgs.writeShellScript "copy-extra-files" '' 36 empty_file=$(${pkgs.coreutils}/bin/mktemp) 37 38 ${concatStrings (mapAttrsToList (n: v: '' 39 ${pkgs.coreutils}/bin/install -Dp "${v}" "${efi.efiSysMountPoint}/"${escapeShellArg n} 40 ${pkgs.coreutils}/bin/install -D $empty_file "${efi.efiSysMountPoint}/efi/nixos/.extra-files/"${escapeShellArg n} 41 '') cfg.extraFiles)} 42 43 ${concatStrings (mapAttrsToList (n: v: '' 44 ${pkgs.coreutils}/bin/install -Dp "${pkgs.writeText n v}" "${efi.efiSysMountPoint}/loader/entries/"${escapeShellArg n} 45 ${pkgs.coreutils}/bin/install -D $empty_file "${efi.efiSysMountPoint}/efi/nixos/.extra-files/loader/entries/"${escapeShellArg n} 46 '') cfg.extraEntries)} 47 ''; 48 }; 49 50 checkedSystemdBootBuilder = pkgs.runCommand "systemd-boot" { 51 nativeBuildInputs = [ pkgs.mypy ]; 52 } '' 53 install -m755 ${systemdBootBuilder} $out 54 mypy \ 55 --no-implicit-optional \ 56 --disallow-untyped-calls \ 57 --disallow-untyped-defs \ 58 $out 59 ''; 60 61 finalSystemdBootBuilder = pkgs.writeScript "install-systemd-boot.sh" '' 62 #!${pkgs.runtimeShell} 63 ${checkedSystemdBootBuilder} "$@" 64 ${cfg.extraInstallCommands} 65 ''; 66in { 67 68 imports = 69 [ (mkRenamedOptionModule [ "boot" "loader" "gummiboot" "enable" ] [ "boot" "loader" "systemd-boot" "enable" ]) 70 ]; 71 72 options.boot.loader.systemd-boot = { 73 enable = mkOption { 74 default = false; 75 76 type = types.bool; 77 78 description = lib.mdDoc "Whether to enable the systemd-boot (formerly gummiboot) EFI boot manager"; 79 }; 80 81 editor = mkOption { 82 default = true; 83 84 type = types.bool; 85 86 description = lib.mdDoc '' 87 Whether to allow editing the kernel command-line before 88 boot. It is recommended to set this to false, as it allows 89 gaining root access by passing init=/bin/sh as a kernel 90 parameter. However, it is enabled by default for backwards 91 compatibility. 92 ''; 93 }; 94 95 configurationLimit = mkOption { 96 default = null; 97 example = 120; 98 type = types.nullOr types.int; 99 description = lib.mdDoc '' 100 Maximum number of latest generations in the boot menu. 101 Useful to prevent boot partition running out of disk space. 102 103 `null` means no limit i.e. all generations 104 that were not garbage collected yet. 105 ''; 106 }; 107 108 extraInstallCommands = mkOption { 109 default = ""; 110 example = '' 111 default_cfg=$(cat /boot/loader/loader.conf | grep default | awk '{print $2}') 112 init_value=$(cat /boot/loader/entries/$default_cfg | grep init= | awk '{print $2}') 113 sed -i "s|@INIT@|$init_value|g" /boot/custom/config_with_placeholder.conf 114 ''; 115 type = types.lines; 116 description = lib.mdDoc '' 117 Additional shell commands inserted in the bootloader installer 118 script after generating menu entries. It can be used to expand 119 on extra boot entries that cannot incorporate certain pieces of 120 information (such as the resulting `init=` kernel parameter). 121 ''; 122 }; 123 124 consoleMode = mkOption { 125 default = "keep"; 126 127 type = types.enum [ "0" "1" "2" "auto" "max" "keep" ]; 128 129 description = lib.mdDoc '' 130 The resolution of the console. The following values are valid: 131 132 - `"0"`: Standard UEFI 80x25 mode 133 - `"1"`: 80x50 mode, not supported by all devices 134 - `"2"`: The first non-standard mode provided by the device firmware, if any 135 - `"auto"`: Pick a suitable mode automatically using heuristics 136 - `"max"`: Pick the highest-numbered available mode 137 - `"keep"`: Keep the mode selected by firmware (the default) 138 ''; 139 }; 140 141 memtest86 = { 142 enable = mkOption { 143 default = false; 144 type = types.bool; 145 description = lib.mdDoc '' 146 Make MemTest86 available from the systemd-boot menu. MemTest86 is a 147 program for testing memory. MemTest86 is an unfree program, so 148 this requires `allowUnfree` to be set to 149 `true`. 150 ''; 151 }; 152 153 entryFilename = mkOption { 154 default = "memtest86.conf"; 155 type = types.str; 156 description = lib.mdDoc '' 157 `systemd-boot` orders the menu entries by the config file names, 158 so if you want something to appear after all the NixOS entries, 159 it should start with {file}`o` or onwards. 160 ''; 161 }; 162 }; 163 164 netbootxyz = { 165 enable = mkOption { 166 default = false; 167 type = types.bool; 168 description = lib.mdDoc '' 169 Make `netboot.xyz` available from the 170 `systemd-boot` menu. `netboot.xyz` 171 is a menu system that allows you to boot OS installers and 172 utilities over the network. 173 ''; 174 }; 175 176 entryFilename = mkOption { 177 default = "o_netbootxyz.conf"; 178 type = types.str; 179 description = lib.mdDoc '' 180 `systemd-boot` orders the menu entries by the config file names, 181 so if you want something to appear after all the NixOS entries, 182 it should start with {file}`o` or onwards. 183 ''; 184 }; 185 }; 186 187 extraEntries = mkOption { 188 type = types.attrsOf types.lines; 189 default = {}; 190 example = literalExpression '' 191 { "memtest86.conf" = ''' 192 title MemTest86 193 efi /efi/memtest86/memtest86.efi 194 '''; } 195 ''; 196 description = lib.mdDoc '' 197 Any additional entries you want added to the `systemd-boot` menu. 198 These entries will be copied to {file}`/boot/loader/entries`. 199 Each attribute name denotes the destination file name, 200 and the corresponding attribute value is the contents of the entry. 201 202 `systemd-boot` orders the menu entries by the config file names, 203 so if you want something to appear after all the NixOS entries, 204 it should start with {file}`o` or onwards. 205 ''; 206 }; 207 208 extraFiles = mkOption { 209 type = types.attrsOf types.path; 210 default = {}; 211 example = literalExpression '' 212 { "efi/memtest86/memtest86.efi" = "''${pkgs.memtest86-efi}/BOOTX64.efi"; } 213 ''; 214 description = lib.mdDoc '' 215 A set of files to be copied to {file}`/boot`. 216 Each attribute name denotes the destination file name in 217 {file}`/boot`, while the corresponding 218 attribute value specifies the source file. 219 ''; 220 }; 221 222 graceful = mkOption { 223 default = false; 224 225 type = types.bool; 226 227 description = lib.mdDoc '' 228 Invoke `bootctl install` with the `--graceful` option, 229 which ignores errors when EFI variables cannot be written or when the EFI System Partition 230 cannot be found. Currently only applies to random seed operations. 231 232 Only enable this option if `systemd-boot` otherwise fails to install, as the 233 scope or implication of the `--graceful` option may change in the future. 234 ''; 235 }; 236 237 }; 238 239 config = mkIf cfg.enable { 240 assertions = [ 241 { 242 assertion = (config.boot.kernelPackages.kernel.features or { efiBootStub = true; }) ? efiBootStub; 243 message = "This kernel does not support the EFI boot stub"; 244 } 245 ] ++ concatMap (filename: [ 246 { 247 assertion = !(hasInfix "/" filename); 248 message = "boot.loader.systemd-boot.extraEntries.${lib.strings.escapeNixIdentifier filename} is invalid: entries within folders are not supported"; 249 } 250 { 251 assertion = hasSuffix ".conf" filename; 252 message = "boot.loader.systemd-boot.extraEntries.${lib.strings.escapeNixIdentifier filename} is invalid: entries must have a .conf file extension"; 253 } 254 ]) (builtins.attrNames cfg.extraEntries) 255 ++ concatMap (filename: [ 256 { 257 assertion = !(hasPrefix "/" filename); 258 message = "boot.loader.systemd-boot.extraFiles.${lib.strings.escapeNixIdentifier filename} is invalid: paths must not begin with a slash"; 259 } 260 { 261 assertion = !(hasInfix ".." filename); 262 message = "boot.loader.systemd-boot.extraFiles.${lib.strings.escapeNixIdentifier filename} is invalid: paths must not reference the parent directory"; 263 } 264 { 265 assertion = !(hasInfix "nixos/.extra-files" (toLower filename)); 266 message = "boot.loader.systemd-boot.extraFiles.${lib.strings.escapeNixIdentifier filename} is invalid: files cannot be placed in the nixos/.extra-files directory"; 267 } 268 ]) (builtins.attrNames cfg.extraFiles); 269 270 boot.loader.grub.enable = mkDefault false; 271 272 boot.loader.supportsInitrdSecrets = true; 273 274 boot.loader.systemd-boot.extraFiles = mkMerge [ 275 # TODO: This is hard-coded to use the 64-bit EFI app, but it could probably 276 # be updated to use the 32-bit EFI app on 32-bit systems. The 32-bit EFI 277 # app filename is BOOTIA32.efi. 278 (mkIf cfg.memtest86.enable { 279 "efi/memtest86/BOOTX64.efi" = "${pkgs.memtest86-efi}/BOOTX64.efi"; 280 }) 281 (mkIf cfg.netbootxyz.enable { 282 "efi/netbootxyz/netboot.xyz.efi" = "${pkgs.netbootxyz-efi}"; 283 }) 284 ]; 285 286 boot.loader.systemd-boot.extraEntries = mkMerge [ 287 (mkIf cfg.memtest86.enable { 288 "${cfg.memtest86.entryFilename}" = '' 289 title MemTest86 290 efi /efi/memtest86/BOOTX64.efi 291 ''; 292 }) 293 (mkIf cfg.netbootxyz.enable { 294 "${cfg.netbootxyz.entryFilename}" = '' 295 title netboot.xyz 296 efi /efi/netbootxyz/netboot.xyz.efi 297 ''; 298 }) 299 ]; 300 301 system = { 302 build.installBootLoader = finalSystemdBootBuilder; 303 304 boot.loader.id = "systemd-boot"; 305 306 requiredKernelConfig = with config.lib.kernelConfig; [ 307 (isYes "EFI_STUB") 308 ]; 309 }; 310 }; 311}