1 # Note: This is a private API, internal to NixOS. Its interface is subject
2 # to change without notice.
4 # The result of this builder is two disk images:
6 # * `boot` - a small disk formatted with FAT to be used for /boot. FAT is
7 # chosen to support EFI.
8 # * `root` - a larger disk with a zpool taking the entire disk.
10 # This two-disk approach is taken to satisfy ZFS's requirements for
13 # # Why doesn't autoexpand work with ZFS in a partition?
15 # When ZFS owns the whole disk doesn’t really use a partition: it has
16 # a marker partition at the start and a marker partition at the end of
19 # If ZFS is constrained to a partition, ZFS leaves expanding the partition
20 # up to the user. Obviously, the user may not choose to do so.
22 # Once the user expands the partition, calling zpool online -e expands the
23 # vdev to use the whole partition. It doesn’t happen automatically
24 # presumably because zed doesn’t get an event saying it’s partition grew,
25 # whereas it can and does get an event saying the whole disk it is on is
29 , # The NixOS configuration to be installed onto the disk image.
32 , # size of the FAT boot disk, in megabytes.
35 , # The size of the root disk, in megabytes.
38 , # The name of the ZFS pool
42 rootPoolProperties ? {
45 , # pool-wide filesystem properties
46 rootPoolFilesystemProperties ? {
50 mountpoint = "legacy";
54 , # datasets, with per-attribute options:
55 # mount: (optional) mount point in the VM
56 # properties: (optional) ZFS properties on the dataset, like filesystemProperties
58 # 1. datasets will be created from shorter to longer names as a simple topo-sort
59 # 2. you should define a root's dataset's mount for `/`
62 , # The files and directories to be placed in the target file system.
63 # This is a list of attribute sets {source, target} where `source'
64 # is the file system object (regular file or directory) to be
65 # grafted in the file system at path `target'.
68 , # The initial NixOS configuration file to be copied to
69 # /etc/nixos/configuration.nix. This configuration will be embedded
70 # inside a configuration which includes the described ZFS fileSystems.
73 , # Shell code executed after the VM has finished.
79 , name ? "nixos-disk-image"
81 , # Disk image format, one of qcow2, qcow2-compressed, vdi, vpc, raw.
84 , # Include a copy of Nixpkgs in the disk image
88 formatOpt = if format == "qcow2-compressed" then "qcow2" else format;
90 compress = lib.optionalString (format == "qcow2-compressed") "-c";
92 filenameSuffix = "." + {
97 }.${formatOpt} or formatOpt;
98 bootFilename = "nixos.boot${filenameSuffix}";
99 rootFilename = "nixos.root${filenameSuffix}";
101 # FIXME: merge with channel.nix / make-channel.nix.
104 nixpkgs = lib.cleanSource pkgs.path;
106 pkgs.runCommand "nixos-${config.system.nixos.version}" {} ''
108 cp -prd ${nixpkgs.outPath} $out/nixos
109 chmod -R u+w $out/nixos
110 if [ ! -e $out/nixos/nixpkgs ]; then
111 ln -s . $out/nixos/nixpkgs
113 rm -rf $out/nixos/.git
114 echo -n ${config.system.nixos.versionSuffix} > $out/nixos/.version-suffix
117 closureInfo = pkgs.closureInfo {
118 rootPaths = [ config.system.build.toplevel ]
119 ++ (lib.optional includeChannel channelSources);
122 modulesTree = pkgs.aggregateModules
123 (with config.boot.kernelPackages; [ kernel zfs ]);
125 tools = lib.makeBinPath (
127 config.system.build.nixos-enter
128 config.system.build.nixos-install
139 hasDefinedMount = disk: ((disk.mount or null) != null);
141 stringifyProperties = prefix: properties: lib.concatStringsSep " \\\n" (
144 property: value: "${prefix} ${lib.escapeShellArg property}=${lib.escapeShellArg value}"
151 datasetlist = lib.mapAttrsToList lib.nameValuePair datasets;
152 sorted = lib.sort (left: right: (lib.stringLength left.name) < (lib.stringLength right.name)) datasetlist;
153 cmd = { name, value }:
155 properties = stringifyProperties "-o" (value.properties or {});
157 "zfs create -p ${properties} ${name}";
159 lib.concatMapStringsSep "\n" cmd sorted;
163 datasetlist = lib.mapAttrsToList lib.nameValuePair datasets;
164 mounts = lib.filter ({ value, ... }: hasDefinedMount value) datasetlist;
165 sorted = lib.sort (left: right: (lib.stringLength left.value.mount) < (lib.stringLength right.value.mount)) mounts;
166 cmd = { name, value }:
168 mkdir -p /mnt${lib.escapeShellArg value.mount}
169 mount -t zfs ${name} /mnt${lib.escapeShellArg value.mount}
172 lib.concatMapStringsSep "\n" cmd sorted;
176 datasetlist = lib.mapAttrsToList lib.nameValuePair datasets;
177 mounts = lib.filter ({ value, ... }: hasDefinedMount value) datasetlist;
178 sorted = lib.sort (left: right: (lib.stringLength left.value.mount) > (lib.stringLength right.value.mount)) mounts;
179 cmd = { name, value }:
181 umount /mnt${lib.escapeShellArg value.mount}
184 lib.concatMapStringsSep "\n" cmd sorted;
189 mountable = lib.filterAttrs (_: value: hasDefinedMount value) datasets;
191 pkgs.runCommand "filesystem-config.nix" {
192 buildInputs = with pkgs; [ jq nixpkgs-fmt ];
193 filesystems = builtins.toJSON {
194 fileSystems = lib.mapAttrs'
201 device = "${dataset}";
207 passAsFile = [ "filesystems" ];
210 echo "builtins.fromJSON '''"
211 jq . < "$filesystemsPath"
219 if configFile == null
220 then fileSystemsCfgFile
222 pkgs.runCommand "configuration.nix" {
223 buildInputs = with pkgs; [ nixpkgs-fmt ];
228 printf "(%s)\n" "$(cat ${fileSystemsCfgFile})";
229 printf "(%s)\n" "$(cat ${configFile})";
237 pkgs.vmTools.override {
239 [ "zfs" "9p" "9pnet_virtio" "virtio_pci" "virtio_blk" ] ++
240 (pkgs.lib.optional pkgs.stdenv.hostPlatform.isx86 "rtc_cmos");
241 kernel = modulesTree;
246 QEMU_OPTS = "-drive file=$bootDiskImage,if=virtio,cache=unsafe,werror=report"
247 + " -drive file=$rootDiskImage,if=virtio,cache=unsafe,werror=report";
250 PATH=$PATH:${pkgs.qemu_kvm}/bin
252 bootDiskImage=boot.raw
253 qemu-img create -f raw $bootDiskImage ${toString bootSize}M
255 rootDiskImage=root.raw
256 qemu-img create -f raw $rootDiskImage ${toString rootSize}M
260 ${if formatOpt == "raw" then ''
261 mv $bootDiskImage $out/${bootFilename}
262 mv $rootDiskImage $out/${rootFilename}
264 ${pkgs.qemu_kvm}/bin/qemu-img convert -f raw -O ${formatOpt} ${compress} $bootDiskImage $out/${bootFilename}
265 ${pkgs.qemu_kvm}/bin/qemu-img convert -f raw -O ${formatOpt} ${compress} $rootDiskImage $out/${rootFilename}
267 bootDiskImage=$out/${bootFilename}
268 rootDiskImage=$out/${rootFilename}
273 export PATH=${tools}:$PATH
276 cp -sv /dev/vda /dev/sda
277 cp -sv /dev/vda /dev/xvda
279 parted --script /dev/vda -- \
281 mkpart no-fs 1MiB 2MiB \
283 align-check optimal 1 \
284 mkpart ESP fat32 2MiB -1MiB \
285 align-check optimal 2 \
288 sfdisk --dump /dev/vda
292 ${stringifyProperties " -o" rootPoolProperties} \
293 ${stringifyProperties " -O" rootPoolFilesystemProperties} \
294 ${rootPoolName} /dev/vdb
295 parted --script /dev/vdb -- print
301 mkfs.vfat -n ESP /dev/vda2
302 mount /dev/vda2 /mnt/boot
306 # Install a configuration.nix
307 mkdir -p /mnt/etc/nixos
308 # `cat` so it is mutable on the fs
309 cat ${mergedConfig} > /mnt/etc/nixos/configuration.nix
311 export NIX_STATE_DIR=$TMPDIR/state
312 nix-store --load-db < ${closureInfo}/registration
317 --system ${config.system.build.toplevel} \
319 ${lib.optionalString includeChannel ''--channel ${channelSources}''}
326 zpool export ${rootPoolName}