at v206 12 kB view raw
1# This module creates a bootable ISO image containing the given NixOS 2# configuration. The derivation for the ISO image will be placed in 3# config.system.build.isoImage. 4 5{ config, lib, pkgs, ... }: 6 7with lib; 8 9let 10 # Timeout in syslinux is in units of 1/10 of a second. 11 # 0 is used to disable timeouts. 12 syslinuxTimeout = if config.boot.loader.timeout == null then 13 0 14 else 15 max (config.boot.loader.timeout * 10) 1; 16 17 18 max = x: y: if x > y then x else y; 19 20 # The configuration file for syslinux. 21 22 # Notes on syslinux configuration and UNetbootin compatiblity: 23 # * Do not use '/syslinux/syslinux.cfg' as the path for this 24 # configuration. UNetbootin will not parse the file and use it as-is. 25 # This results in a broken configuration if the partition label does 26 # not match the specified config.isoImage.volumeID. For this reason 27 # we're using '/isolinux/isolinux.cfg'. 28 # * Use APPEND instead of adding command-line arguments directly after 29 # the LINUX entries. 30 # * COM32 entries (chainload, reboot, poweroff) are not recognized. They 31 # result in incorrect boot entries. 32 33 baseIsolinuxCfg = '' 34 SERIAL 0 38400 35 TIMEOUT ${builtins.toString syslinuxTimeout} 36 UI vesamenu.c32 37 MENU TITLE NixOS 38 MENU BACKGROUND /isolinux/background.png 39 DEFAULT boot 40 41 LABEL boot 42 MENU LABEL NixOS ${config.system.nixosVersion}${config.isoImage.appendToMenuLabel} 43 LINUX /boot/bzImage 44 APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} 45 INITRD /boot/initrd 46 ''; 47 48 isolinuxMemtest86Entry = '' 49 LABEL memtest 50 MENU LABEL Memtest86+ 51 LINUX /boot/memtest.bin 52 APPEND ${toString config.boot.loader.grub.memtest86.params} 53 ''; 54 55 isolinuxCfg = baseIsolinuxCfg + (optionalString config.boot.loader.grub.memtest86.enable isolinuxMemtest86Entry); 56 57 # The EFI boot image. 58 efiDir = pkgs.runCommand "efi-directory" {} '' 59 mkdir -p $out/EFI/boot 60 cp -v ${pkgs.gummiboot}/lib/gummiboot/gummiboot${targetArch}.efi $out/EFI/boot/boot${targetArch}.efi 61 mkdir -p $out/loader/entries 62 echo "title NixOS Live CD" > $out/loader/entries/nixos-livecd.conf 63 echo "linux /boot/bzImage" >> $out/loader/entries/nixos-livecd.conf 64 echo "initrd /boot/initrd" >> $out/loader/entries/nixos-livecd.conf 65 echo "options init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams}" >> $out/loader/entries/nixos-livecd.conf 66 echo "default nixos-livecd" > $out/loader/loader.conf 67 echo "timeout ${builtins.toString config.boot.loader.gummiboot.timeout}" >> $out/loader/loader.conf 68 ''; 69 70 efiImg = pkgs.runCommand "efi-image_eltorito" { buildInputs = [ pkgs.mtools pkgs.libfaketime ]; } 71 # Be careful about determinism: du --apparent-size, 72 # dates (cp -p, touch, mcopy -m, faketime for label), IDs (mkfs.vfat -i) 73 '' 74 mkdir ./contents && cd ./contents 75 cp -rp "${efiDir}"/* . 76 mkdir ./boot 77 cp -p "${config.boot.kernelPackages.kernel}/bzImage" \ 78 "${config.system.build.initialRamdisk}/initrd" ./boot/ 79 touch --date=@0 ./* 80 81 usage_size=$(du -sb --apparent-size . | tr -cd '[:digit:]') 82 # Make the image 110% as big as the files need to make up for FAT overhead 83 image_size=$(( ($usage_size * 110) / 100 )) 84 # Make the image fit blocks of 1M 85 block_size=$((1024*1024)) 86 image_size=$(( ($image_size / $block_size + 1) * $block_size )) 87 echo "Usage size: $usage_size" 88 echo "Image size: $image_size" 89 truncate --size=$image_size "$out" 90 ${pkgs.libfaketime}/bin/faketime "2000-01-01 00:00:00" ${pkgs.dosfstools}/sbin/mkfs.vfat -i 12345678 -n EFIBOOT "$out" 91 mcopy -bpsvm -i "$out" ./* :: 92 ''; # */ 93 94 targetArch = if pkgs.stdenv.isi686 then 95 "ia32" 96 else if pkgs.stdenv.isx86_64 then 97 "x64" 98 else 99 throw "Unsupported architecture"; 100 101in 102 103{ 104 options = { 105 106 isoImage.isoName = mkOption { 107 default = "${config.isoImage.isoBaseName}.iso"; 108 description = '' 109 Name of the generated ISO image file. 110 ''; 111 }; 112 113 isoImage.isoBaseName = mkOption { 114 default = "nixos"; 115 description = '' 116 Prefix of the name of the generated ISO image file. 117 ''; 118 }; 119 120 isoImage.compressImage = mkOption { 121 default = false; 122 description = '' 123 Whether the ISO image should be compressed using 124 <command>bzip2</command>. 125 ''; 126 }; 127 128 isoImage.volumeID = mkOption { 129 default = "NIXOS_BOOT_CD"; 130 description = '' 131 Specifies the label or volume ID of the generated ISO image. 132 Note that the label is used by stage 1 of the boot process to 133 mount the CD, so it should be reasonably distinctive. 134 ''; 135 }; 136 137 isoImage.contents = mkOption { 138 example = literalExample '' 139 [ { source = pkgs.memtest86 + "/memtest.bin"; 140 target = "boot/memtest.bin"; 141 } 142 ] 143 ''; 144 description = '' 145 This option lists files to be copied to fixed locations in the 146 generated ISO image. 147 ''; 148 }; 149 150 isoImage.storeContents = mkOption { 151 example = literalExample "[ pkgs.stdenv ]"; 152 description = '' 153 This option lists additional derivations to be included in the 154 Nix store in the generated ISO image. 155 ''; 156 }; 157 158 isoImage.includeSystemBuildDependencies = mkOption { 159 default = false; 160 example = true; 161 description = '' 162 Set this option to include all the needed sources etc in the 163 image. It significantly increases image size. Use that when 164 you want to be able to keep all the sources needed to build your 165 system or when you are going to install the system on a computer 166 with slow on non-existent network connection. 167 ''; 168 }; 169 170 isoImage.makeEfiBootable = mkOption { 171 default = false; 172 description = '' 173 Whether the ISO image should be an efi-bootable volume. 174 ''; 175 }; 176 177 isoImage.makeUsbBootable = mkOption { 178 default = false; 179 description = '' 180 Whether the ISO image should be bootable from CD as well as USB. 181 ''; 182 }; 183 184 isoImage.splashImage = mkOption { 185 default = pkgs.fetchurl { 186 url = https://raw.githubusercontent.com/NixOS/nixos-artwork/5729ab16c6a5793c10a2913b5a1b3f59b91c36ee/ideas/grub-splash/grub-nixos-1.png; 187 sha256 = "43fd8ad5decf6c23c87e9026170a13588c2eba249d9013cb9f888da5e2002217"; 188 }; 189 description = '' 190 The splash image to use in the bootloader. 191 ''; 192 }; 193 194 isoImage.appendToMenuLabel = mkOption { 195 default = " Installer"; 196 example = " Live System"; 197 description = '' 198 The string to append after the menu label for the NixOS system. 199 This will be directly appended (without whitespace) to the NixOS version 200 string, like for example if it is set to <literal>XXX</literal>: 201 202 <para><literal>NixOS 99.99-pre666XXX</literal></para> 203 ''; 204 }; 205 206 }; 207 208 config = { 209 210 boot.loader.grub.version = 2; 211 212 # Don't build the GRUB menu builder script, since we don't need it 213 # here and it causes a cyclic dependency. 214 boot.loader.grub.enable = false; 215 216 # !!! Hack - attributes expected by other modules. 217 system.boot.loader.kernelFile = "bzImage"; 218 environment.systemPackages = [ pkgs.grub2 pkgs.grub2_efi pkgs.syslinux ]; 219 220 boot.consoleLogLevel = mkDefault 7; 221 222 # In stage 1 of the boot, mount the CD as the root FS by label so 223 # that we don't need to know its device. We pass the label of the 224 # root filesystem on the kernel command line, rather than in 225 # `fileSystems' below. This allows CD-to-USB converters such as 226 # UNetbootin to rewrite the kernel command line to pass the label or 227 # UUID of the USB stick. It would be nicer to write 228 # `root=/dev/disk/by-label/...' here, but UNetbootin doesn't 229 # recognise that. 230 boot.kernelParams = 231 [ "root=LABEL=${config.isoImage.volumeID}" 232 "boot.shell_on_fail" 233 "nomodeset" 234 ]; 235 236 fileSystems."/" = 237 { fsType = "tmpfs"; 238 options = "mode=0755"; 239 }; 240 241 # Note that /dev/root is a symlink to the actual root device 242 # specified on the kernel command line, created in the stage 1 243 # init script. 244 fileSystems."/iso" = 245 { device = "/dev/root"; 246 neededForBoot = true; 247 noCheck = true; 248 }; 249 250 # In stage 1, mount a tmpfs on top of /nix/store (the squashfs 251 # image) to make this a live CD. 252 fileSystems."/nix/.ro-store" = 253 { fsType = "squashfs"; 254 device = "/iso/nix-store.squashfs"; 255 options = "loop"; 256 neededForBoot = true; 257 }; 258 259 fileSystems."/nix/.rw-store" = 260 { fsType = "tmpfs"; 261 options = "mode=0755"; 262 neededForBoot = true; 263 }; 264 265 fileSystems."/nix/store" = 266 { fsType = "unionfs-fuse"; 267 device = "unionfs"; 268 options = "allow_other,cow,nonempty,chroot=/mnt-root,max_files=32768,hide_meta_files,dirs=/nix/.rw-store=rw:/nix/.ro-store=ro"; 269 }; 270 271 boot.initrd.availableKernelModules = [ "squashfs" "iso9660" "usb-storage" ]; 272 273 boot.blacklistedKernelModules = [ "nouveau" ]; 274 275 boot.initrd.kernelModules = [ "loop" ]; 276 277 # Closures to be copied to the Nix store on the CD, namely the init 278 # script and the top-level system configuration directory. 279 isoImage.storeContents = 280 [ config.system.build.toplevel ] ++ 281 optional config.isoImage.includeSystemBuildDependencies 282 config.system.build.toplevel.drvPath; 283 284 # Create the squashfs image that contains the Nix store. 285 system.build.squashfsStore = import ../../../lib/make-squashfs.nix { 286 inherit (pkgs) stdenv squashfsTools perl pathsFromGraph; 287 storeContents = config.isoImage.storeContents; 288 }; 289 290 # Individual files to be included on the CD, outside of the Nix 291 # store on the CD. 292 isoImage.contents = 293 [ { source = pkgs.substituteAll { 294 name = "isolinux.cfg"; 295 src = pkgs.writeText "isolinux.cfg-in" isolinuxCfg; 296 bootRoot = "/boot"; 297 }; 298 target = "/isolinux/isolinux.cfg"; 299 } 300 { source = config.boot.kernelPackages.kernel + "/bzImage"; 301 target = "/boot/bzImage"; 302 } 303 { source = config.system.build.initialRamdisk + "/initrd"; 304 target = "/boot/initrd"; 305 } 306 { source = config.system.build.squashfsStore; 307 target = "/nix-store.squashfs"; 308 } 309 { source = "${pkgs.syslinux}/share/syslinux"; 310 target = "/isolinux"; 311 } 312 { source = config.isoImage.splashImage; 313 target = "/isolinux/background.png"; 314 } 315 ] ++ optionals config.isoImage.makeEfiBootable [ 316 { source = efiImg; 317 target = "/boot/efi.img"; 318 } 319 { source = "${efiDir}/EFI"; 320 target = "/EFI"; 321 } 322 { source = "${efiDir}/loader"; 323 target = "/loader"; 324 } 325 ] ++ optionals config.boot.loader.grub.memtest86.enable [ 326 { source = "${pkgs.memtest86plus}/memtest.bin"; 327 target = "/boot/memtest.bin"; 328 } 329 ]; 330 331 boot.loader.timeout = 10; 332 333 # Create the ISO image. 334 system.build.isoImage = import ../../../lib/make-iso9660-image.nix ({ 335 inherit (pkgs) stdenv perl pathsFromGraph xorriso syslinux; 336 337 inherit (config.isoImage) isoName compressImage volumeID contents; 338 339 bootable = true; 340 bootImage = "/isolinux/isolinux.bin"; 341 } // optionalAttrs config.isoImage.makeUsbBootable { 342 usbBootable = true; 343 isohybridMbrImage = "${pkgs.syslinux}/share/syslinux/isohdpfx.bin"; 344 } // optionalAttrs config.isoImage.makeEfiBootable { 345 efiBootable = true; 346 efiBootImage = "boot/efi.img"; 347 }); 348 349 boot.postBootCommands = 350 '' 351 # After booting, register the contents of the Nix store on the 352 # CD in the Nix database in the tmpfs. 353 ${config.nix.package}/bin/nix-store --load-db < /nix/store/nix-path-registration 354 355 # nixos-rebuild also requires a "system" profile and an 356 # /etc/NIXOS tag. 357 touch /etc/NIXOS 358 ${config.nix.package}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system 359 ''; 360 361 # Add vfat support to the initrd to enable people to copy the 362 # contents of the CD to a bootable USB stick. 363 boot.initrd.supportedFilesystems = [ "vfat" ]; 364 365 }; 366 367}