You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

make-disk-image.nix 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. { pkgs
  2. , lib
  3. , # The NixOS configuration to be installed onto the disk image.
  4. config
  5. , # The size of the disk, in megabytes.
  6. diskSize
  7. # The files and directories to be placed in the target file system.
  8. # This is a list of attribute sets {source, target} where `source'
  9. # is the file system object (regular file or directory) to be
  10. # grafted in the file system at path `target'.
  11. , contents ? []
  12. , # Type of partition table to use; either "legacy", "efi", or "none".
  13. # For "efi" images, the GPT partition table is used and a mandatory ESP
  14. # partition of reasonable size is created in addition to the root partition.
  15. # If `installBootLoader` is true, GRUB will be installed in EFI mode.
  16. # For "legacy", the msdos partition table is used and a single large root
  17. # partition is created. If `installBootLoader` is true, GRUB will be
  18. # installed in legacy mode.
  19. # For "none", no partition table is created. Enabling `installBootLoader`
  20. # most likely fails as GRUB will probably refuse to install.
  21. partitionTableType ? "legacy"
  22. , # The root file system type.
  23. fsType ? "ext4"
  24. , # Filesystem label
  25. label ? "nixos"
  26. , # The initial NixOS configuration file to be copied to
  27. # /etc/nixos/configuration.nix.
  28. configFile ? null
  29. , # Shell code executed after the VM has finished.
  30. postVM ? ""
  31. , name ? "nixos-disk-image"
  32. , # Disk image format, one of qcow2, qcow2-compressed, vpc, raw.
  33. format ? "raw"
  34. }:
  35. assert partitionTableType == "legacy" || partitionTableType == "efi" || partitionTableType == "none";
  36. # We use -E offset=X below, which is only supported by e2fsprogs
  37. assert partitionTableType != "none" -> fsType == "ext4";
  38. with lib;
  39. let format' = format; in let
  40. format = if format' == "qcow2-compressed" then "qcow2" else format';
  41. compress = optionalString (format' == "qcow2-compressed") "-c";
  42. filename = "nixos." + {
  43. qcow2 = "qcow2";
  44. vpc = "vhd";
  45. raw = "img";
  46. }.${format};
  47. rootPartition = { # switch-case
  48. legacy = "1";
  49. efi = "2";
  50. }.${partitionTableType};
  51. partitionDiskScript = { # switch-case
  52. legacy = ''
  53. parted --script $diskImage -- \
  54. mklabel msdos \
  55. mkpart primary ext4 1MiB -1
  56. '';
  57. efi = ''
  58. parted --script $diskImage -- \
  59. mklabel gpt \
  60. mkpart ESP fat32 8MiB 256MiB \
  61. set 1 boot on \
  62. mkpart primary ext4 256MiB -1
  63. '';
  64. none = "";
  65. }.${partitionTableType};
  66. nixpkgs = cleanSource pkgs.path;
  67. # FIXME: merge with channel.nix / make-channel.nix.
  68. channelSources = pkgs.runCommand "nixos-${config.system.nixos.version}" {} ''
  69. mkdir -p $out
  70. cp -prd ${nixpkgs.outPath} $out/nixos
  71. chmod -R u+w $out/nixos
  72. if [ ! -e $out/nixos/nixpkgs ]; then
  73. ln -s . $out/nixos/nixpkgs
  74. fi
  75. rm -rf $out/nixos/.git
  76. echo -n ${config.system.nixos.versionSuffix} > $out/nixos/.version-suffix
  77. '';
  78. binPath = with pkgs; makeBinPath (
  79. [ rsync
  80. utillinux
  81. parted
  82. e2fsprogs
  83. lkl
  84. config.system.build.nixos-install
  85. config.system.build.nixos-enter
  86. nix
  87. ] ++ stdenv.initialPath);
  88. # I'm preserving the line below because I'm going to search for it across nixpkgs to consolidate
  89. # image building logic. The comment right below this now appears in 4 different places in nixpkgs :)
  90. # !!! should use XML.
  91. sources = map (x: x.source) contents;
  92. targets = map (x: x.target) contents;
  93. closureInfo = pkgs.closureInfo { rootPaths = [ config.system.build.toplevel channelSources ]; };
  94. prepareImage = ''
  95. export PATH=${binPath}
  96. # Yes, mkfs.ext4 takes different units in different contexts. Fun.
  97. sectorsToKilobytes() {
  98. echo $(( ( "$1" * 512 ) / 1024 ))
  99. }
  100. sectorsToBytes() {
  101. echo $(( "$1" * 512 ))
  102. }
  103. mkdir $out
  104. diskImage=nixos.raw
  105. truncate -s ${toString diskSize}M $diskImage
  106. ${partitionDiskScript}
  107. ${if partitionTableType != "none" then ''
  108. # Get start & length of the root partition in sectors to $START and $SECTORS.
  109. eval $(partx $diskImage -o START,SECTORS --nr ${rootPartition} --pairs)
  110. mkfs.${fsType} -F -L ${label} $diskImage -E offset=$(sectorsToBytes $START) $(sectorsToKilobytes $SECTORS)K
  111. '' else ''
  112. mkfs.${fsType} -F -L ${label} $diskImage
  113. ''}
  114. root="$PWD/root"
  115. mkdir -p $root
  116. # Copy arbitrary other files into the image
  117. # Semi-shamelessly copied from make-etc.sh. I (@copumpkin) shall factor this stuff out as part of
  118. # https://github.com/NixOS/nixpkgs/issues/23052.
  119. set -f
  120. sources_=(${concatStringsSep " " sources})
  121. targets_=(${concatStringsSep " " targets})
  122. set +f
  123. for ((i = 0; i < ''${#targets_[@]}; i++)); do
  124. source="''${sources_[$i]}"
  125. target="''${targets_[$i]}"
  126. if [[ "$source" =~ '*' ]]; then
  127. # If the source name contains '*', perform globbing.
  128. mkdir -p $root/$target
  129. for fn in $source; do
  130. rsync -a --no-o --no-g "$fn" $root/$target/
  131. done
  132. else
  133. mkdir -p $root/$(dirname $target)
  134. if ! [ -e $root/$target ]; then
  135. rsync -a --no-o --no-g $source $root/$target
  136. else
  137. echo "duplicate entry $target -> $source"
  138. exit 1
  139. fi
  140. fi
  141. done
  142. export HOME=$TMPDIR
  143. # Provide a Nix database so that nixos-install can copy closures.
  144. export NIX_STATE_DIR=$TMPDIR/state
  145. nix-store --load-db < ${closureInfo}/registration
  146. mkdir -m 0755 -p "$root/etc"
  147. touch "$root/etc/NIXOS"
  148. echo "copying system..."
  149. nix-env --store "$root" --substituters "auto?trusted=1" \
  150. -p "$root/nix/var/nix/profiles/system" --set "${config.system.build.toplevel}" --quiet
  151. echo "copying channel..."
  152. mkdir -p "$root/nix/var/nix/profiles/per-user/root"
  153. nix-env --store "$root" --substituters "auto?trusted=1" \
  154. -p "$root/nix/var/nix/profiles/per-user/root/channels" --set "${channelSources}" --quiet
  155. echo "copying staging root to image..."
  156. cptofs -p ${optionalString (partitionTableType != "none") "-P ${rootPartition}"} -t ${fsType} -i $diskImage $root/* /
  157. '';
  158. in pkgs.vmTools.runInLinuxVM (
  159. pkgs.runCommand name
  160. { preVM = prepareImage;
  161. buildInputs = with pkgs; [ utillinux e2fsprogs dosfstools ];
  162. postVM = ''
  163. ${if format == "raw" then ''
  164. mv $diskImage $out/${filename}
  165. '' else ''
  166. ${pkgs.qemu}/bin/qemu-img convert -f raw -O ${format} ${compress} $diskImage $out/${filename}
  167. ''}
  168. diskImage=$out/${filename}
  169. ${postVM}
  170. '';
  171. memSize = 1024;
  172. }
  173. ''
  174. export PATH=${binPath}:$PATH
  175. rootDisk=${if partitionTableType != "none" then "/dev/vda${rootPartition}" else "/dev/vda"}
  176. # Some tools assume these exist
  177. ln -s vda /dev/xvda
  178. ln -s vda /dev/sda
  179. mountPoint=/mnt
  180. mkdir $mountPoint
  181. mount $rootDisk $mountPoint
  182. # Create the ESP and mount it. Unlike e2fsprogs, mkfs.vfat doesn't support an
  183. # '-E offset=X' option, so we can't do this outside the VM.
  184. ${optionalString (partitionTableType == "efi") ''
  185. mkdir -p /mnt/boot
  186. mkfs.vfat -n ESP /dev/vda1
  187. mount /dev/vda1 /mnt/boot
  188. ''}
  189. # Install a configuration.nix
  190. mkdir -p /mnt/etc/nixos
  191. ${optionalString (configFile != null) ''
  192. cp ${configFile} /mnt/etc/nixos/configuration.nix
  193. ''}
  194. # Set up core system link, GRUB, etc.
  195. NIXOS_INSTALL_BOOTLOADER=1 nixos-enter --root $mountPoint -- /nix/var/nix/profiles/system/bin/switch-to-configuration boot
  196. # The above scripts will generate a random machine-id and we don't want to bake a single ID into all our images
  197. rm -f $mountPoint/etc/machine-id
  198. umount -R /mnt
  199. # Make sure resize2fs works. Note that resize2fs has stricter criteria for resizing than a normal
  200. # mount, so the `-c 0` and `-i 0` don't affect it. Setting it to `now` doesn't produce deterministic
  201. # output, of course, but we can fix that when/if we start making images deterministic.
  202. ${optionalString (fsType == "ext4") ''
  203. tune2fs -T now -c 0 -i 0 $rootDisk
  204. ''}
  205. ''
  206. )