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