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