at 24.11-pre 9.0 kB view raw
1# Note: This is a private API, internal to NixOS. Its interface is subject 2# to change without notice. 3# 4# The result of this builder is a single disk image, partitioned like this: 5# 6# * partition #1: a very small, 1MiB partition to leave room for Grub. 7# 8# * partition #2: boot, a partition formatted with FAT to be used for /boot. 9# FAT is chosen to support EFI. 10# 11# * partition #3: nixos, a partition dedicated to a zpool. 12# 13# This single-disk approach does not satisfy ZFS's requirements for autoexpand, 14# however automation can expand it anyway. For example, with 15# `services.zfs.expandOnBoot`. 16{ lib 17, pkgs 18, # The NixOS configuration to be installed onto the disk image. 19 config 20 21, # size of the FAT partition, in megabytes. 22 bootSize ? 1024 23 24 , # memory allocated for virtualized build instance 25 memSize ? 1024 26 27, # The size of the root partition, in megabytes. 28 rootSize ? 2048 29 30, # The name of the ZFS pool 31 rootPoolName ? "tank" 32 33, # zpool properties 34 rootPoolProperties ? { 35 autoexpand = "on"; 36 } 37, # pool-wide filesystem properties 38 rootPoolFilesystemProperties ? { 39 acltype = "posixacl"; 40 atime = "off"; 41 compression = "on"; 42 mountpoint = "legacy"; 43 xattr = "sa"; 44 } 45 46, # datasets, with per-attribute options: 47 # mount: (optional) mount point in the VM 48 # properties: (optional) ZFS properties on the dataset, like filesystemProperties 49 # Notes: 50 # 1. datasets will be created from shorter to longer names as a simple topo-sort 51 # 2. you should define a root's dataset's mount for `/` 52 datasets ? { } 53 54, # The files and directories to be placed in the target file system. 55 # This is a list of attribute sets {source, target} where `source' 56 # is the file system object (regular file or directory) to be 57 # grafted in the file system at path `target'. 58 contents ? [ ] 59 60, # The initial NixOS configuration file to be copied to 61 # /etc/nixos/configuration.nix. This configuration will be embedded 62 # inside a configuration which includes the described ZFS fileSystems. 63 configFile ? null 64 65, # Shell code executed after the VM has finished. 66 postVM ? "" 67 68, name ? "nixos-disk-image" 69 70, # Disk image format, one of qcow2, qcow2-compressed, vdi, vpc, raw. 71 format ? "raw" 72 73, # Include a copy of Nixpkgs in the disk image 74 includeChannel ? true 75}: 76let 77 formatOpt = if format == "qcow2-compressed" then "qcow2" else format; 78 79 compress = lib.optionalString (format == "qcow2-compressed") "-c"; 80 81 filenameSuffix = "." + { 82 qcow2 = "qcow2"; 83 vdi = "vdi"; 84 vpc = "vhd"; 85 raw = "img"; 86 }.${formatOpt} or formatOpt; 87 rootFilename = "nixos.root${filenameSuffix}"; 88 89 # FIXME: merge with channel.nix / make-channel.nix. 90 channelSources = 91 let 92 nixpkgs = lib.cleanSource pkgs.path; 93 in 94 pkgs.runCommand "nixos-${config.system.nixos.version}" { } '' 95 mkdir -p $out 96 cp -prd ${nixpkgs.outPath} $out/nixos 97 chmod -R u+w $out/nixos 98 if [ ! -e $out/nixos/nixpkgs ]; then 99 ln -s . $out/nixos/nixpkgs 100 fi 101 rm -rf $out/nixos/.git 102 echo -n ${config.system.nixos.versionSuffix} > $out/nixos/.version-suffix 103 ''; 104 105 closureInfo = pkgs.closureInfo { 106 rootPaths = [ config.system.build.toplevel ] 107 ++ (lib.optional includeChannel channelSources); 108 }; 109 110 modulesTree = pkgs.aggregateModules 111 (with config.boot.kernelPackages; [ kernel zfs ]); 112 113 tools = lib.makeBinPath ( 114 with pkgs; [ 115 config.system.build.nixos-enter 116 config.system.build.nixos-install 117 dosfstools 118 e2fsprogs 119 gptfdisk 120 nix 121 parted 122 util-linux 123 zfs 124 ] 125 ); 126 127 hasDefinedMount = disk: ((disk.mount or null) != null); 128 129 stringifyProperties = prefix: properties: lib.concatStringsSep " \\\n" ( 130 lib.mapAttrsToList 131 ( 132 property: value: "${prefix} ${lib.escapeShellArg property}=${lib.escapeShellArg value}" 133 ) 134 properties 135 ); 136 137 createDatasets = 138 let 139 datasetlist = lib.mapAttrsToList lib.nameValuePair datasets; 140 sorted = lib.sort (left: right: (lib.stringLength left.name) < (lib.stringLength right.name)) datasetlist; 141 cmd = { name, value }: 142 let 143 properties = stringifyProperties "-o" (value.properties or { }); 144 in 145 "zfs create -p ${properties} ${name}"; 146 in 147 lib.concatMapStringsSep "\n" cmd sorted; 148 149 mountDatasets = 150 let 151 datasetlist = lib.mapAttrsToList lib.nameValuePair datasets; 152 mounts = lib.filter ({ value, ... }: hasDefinedMount value) datasetlist; 153 sorted = lib.sort (left: right: (lib.stringLength left.value.mount) < (lib.stringLength right.value.mount)) mounts; 154 cmd = { name, value }: 155 '' 156 mkdir -p /mnt${lib.escapeShellArg value.mount} 157 mount -t zfs ${name} /mnt${lib.escapeShellArg value.mount} 158 ''; 159 in 160 lib.concatMapStringsSep "\n" cmd sorted; 161 162 unmountDatasets = 163 let 164 datasetlist = lib.mapAttrsToList lib.nameValuePair datasets; 165 mounts = lib.filter ({ value, ... }: hasDefinedMount value) datasetlist; 166 sorted = lib.sort (left: right: (lib.stringLength left.value.mount) > (lib.stringLength right.value.mount)) mounts; 167 cmd = { name, value }: 168 '' 169 umount /mnt${lib.escapeShellArg value.mount} 170 ''; 171 in 172 lib.concatMapStringsSep "\n" cmd sorted; 173 174 175 fileSystemsCfgFile = 176 let 177 mountable = lib.filterAttrs (_: value: hasDefinedMount value) datasets; 178 in 179 pkgs.runCommand "filesystem-config.nix" 180 { 181 buildInputs = with pkgs; [ jq nixpkgs-fmt ]; 182 filesystems = builtins.toJSON { 183 fileSystems = lib.mapAttrs' 184 ( 185 dataset: attrs: 186 { 187 name = attrs.mount; 188 value = { 189 fsType = "zfs"; 190 device = "${dataset}"; 191 }; 192 } 193 ) 194 mountable; 195 }; 196 passAsFile = [ "filesystems" ]; 197 } '' 198 ( 199 echo "builtins.fromJSON '''" 200 jq . < "$filesystemsPath" 201 echo "'''" 202 ) > $out 203 204 nixpkgs-fmt $out 205 ''; 206 207 mergedConfig = 208 if configFile == null 209 then fileSystemsCfgFile 210 else 211 pkgs.runCommand "configuration.nix" 212 { 213 buildInputs = with pkgs; [ nixpkgs-fmt ]; 214 } 215 '' 216 ( 217 echo '{ imports = [' 218 printf "(%s)\n" "$(cat ${fileSystemsCfgFile})"; 219 printf "(%s)\n" "$(cat ${configFile})"; 220 echo ']; }' 221 ) > $out 222 223 nixpkgs-fmt $out 224 ''; 225 226 image = ( 227 pkgs.vmTools.override { 228 rootModules = 229 [ "zfs" "9p" "9pnet_virtio" "virtio_pci" "virtio_blk" ] ++ 230 (pkgs.lib.optional pkgs.stdenv.hostPlatform.isx86 "rtc_cmos"); 231 kernel = modulesTree; 232 } 233 ).runInLinuxVM ( 234 pkgs.runCommand name 235 { 236 inherit memSize; 237 QEMU_OPTS = "-drive file=$rootDiskImage,if=virtio,cache=unsafe,werror=report"; 238 preVM = '' 239 PATH=$PATH:${pkgs.qemu_kvm}/bin 240 mkdir $out 241 242 rootDiskImage=root.raw 243 qemu-img create -f raw $rootDiskImage ${toString (bootSize + rootSize)}M 244 ''; 245 246 postVM = '' 247 ${if formatOpt == "raw" then '' 248 mv $rootDiskImage $out/${rootFilename} 249 '' else '' 250 ${pkgs.qemu_kvm}/bin/qemu-img convert -f raw -O ${formatOpt} ${compress} $rootDiskImage $out/${rootFilename} 251 ''} 252 rootDiskImage=$out/${rootFilename} 253 set -x 254 ${postVM} 255 ''; 256 } '' 257 export PATH=${tools}:$PATH 258 set -x 259 260 cp -sv /dev/vda /dev/sda 261 cp -sv /dev/vda /dev/xvda 262 263 parted --script /dev/vda -- \ 264 mklabel gpt \ 265 mkpart no-fs 1MiB 2MiB \ 266 set 1 bios_grub on \ 267 align-check optimal 1 \ 268 mkpart primary fat32 2MiB ${toString bootSize}MiB \ 269 align-check optimal 2 \ 270 mkpart primary fat32 ${toString bootSize}MiB -1MiB \ 271 align-check optimal 3 \ 272 print 273 274 sfdisk --dump /dev/vda 275 276 277 zpool create \ 278 ${stringifyProperties " -o" rootPoolProperties} \ 279 ${stringifyProperties " -O" rootPoolFilesystemProperties} \ 280 ${rootPoolName} /dev/vda3 281 parted --script /dev/vda -- print 282 283 ${createDatasets} 284 ${mountDatasets} 285 286 mkdir -p /mnt/boot 287 mkfs.vfat -n ESP /dev/vda2 288 mount /dev/vda2 /mnt/boot 289 290 mount 291 292 # Install a configuration.nix 293 mkdir -p /mnt/etc/nixos 294 # `cat` so it is mutable on the fs 295 cat ${mergedConfig} > /mnt/etc/nixos/configuration.nix 296 297 export NIX_STATE_DIR=$TMPDIR/state 298 nix-store --load-db < ${closureInfo}/registration 299 300 nixos-install \ 301 --root /mnt \ 302 --no-root-passwd \ 303 --system ${config.system.build.toplevel} \ 304 --substituters "" \ 305 ${lib.optionalString includeChannel ''--channel ${channelSources}''} 306 307 df -h 308 309 umount /mnt/boot 310 ${unmountDatasets} 311 312 zpool export ${rootPoolName} 313 '' 314 ); 315in 316image