···
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
39
+
rootPoolName ? "tank"
41
+
, # zpool properties
42
+
rootPoolProperties ? {
45
+
, # pool-wide filesystem properties
46
+
rootPoolFilesystemProperties ? {
47
+
acltype = "posixacl";
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.
76
+
, name ? "nixos-disk-image"
78
+
, # Disk image format, one of qcow2, qcow2-compressed, vdi, vpc, raw.
81
+
, # Include a copy of Nixpkgs in the disk image
82
+
includeChannel ? true
85
+
formatOpt = if format == "qcow2-compressed" then "qcow2" else format;
87
+
compress = lib.optionalString (format == "qcow2-compressed") "-c";
89
+
filenameSuffix = "." + {
94
+
}.${formatOpt} or formatOpt;
95
+
bootFilename = "nixos.boot${filenameSuffix}";
96
+
rootFilename = "nixos.root${filenameSuffix}";
98
+
# FIXME: merge with channel.nix / make-channel.nix.
101
+
nixpkgs = lib.cleanSource pkgs.path;
103
+
pkgs.runCommand "nixos-${config.system.nixos.version}" {} ''
105
+
cp -prd ${nixpkgs.outPath} $out/nixos
106
+
chmod -R u+w $out/nixos
107
+
if [ ! -e $out/nixos/nixpkgs ]; then
108
+
ln -s . $out/nixos/nixpkgs
110
+
rm -rf $out/nixos/.git
111
+
echo -n ${config.system.nixos.versionSuffix} > $out/nixos/.version-suffix
114
+
closureInfo = pkgs.closureInfo {
115
+
rootPaths = [ config.system.build.toplevel ]
116
+
++ (lib.optional includeChannel channelSources);
119
+
modulesTree = pkgs.aggregateModules
120
+
(with config.boot.kernelPackages; [ kernel zfs ]);
122
+
tools = lib.makeBinPath (
124
+
config.system.build.nixos-enter
125
+
config.system.build.nixos-install
136
+
hasDefinedMount = disk: ((disk.mount or null) != null);
138
+
stringifyProperties = prefix: properties: lib.concatStringsSep " \\\n" (
141
+
property: value: "${prefix} ${lib.escapeShellArg property}=${lib.escapeShellArg value}"
146
+
featuresToProperties = features:
148
+
(builtins.map (feature: {
149
+
name = "feature@${feature}";
155
+
datasetlist = lib.mapAttrsToList lib.nameValuePair datasets;
156
+
sorted = lib.sort (left: right: (lib.stringLength left.name) < (lib.stringLength right.name)) datasetlist;
157
+
cmd = { name, value }:
159
+
properties = stringifyProperties "-o" (value.properties or {});
161
+
"zfs create -p ${properties} ${name}";
163
+
lib.concatMapStringsSep "\n" cmd sorted;
167
+
datasetlist = lib.mapAttrsToList lib.nameValuePair datasets;
168
+
mounts = lib.filter ({ value, ... }: hasDefinedMount value) datasetlist;
169
+
sorted = lib.sort (left: right: (lib.stringLength left.value.mount) < (lib.stringLength right.value.mount)) mounts;
170
+
cmd = { name, value }:
172
+
mkdir -p /mnt${lib.escapeShellArg value.mount}
173
+
mount -t zfs ${name} /mnt${lib.escapeShellArg value.mount}
176
+
lib.concatMapStringsSep "\n" cmd sorted;
180
+
datasetlist = lib.mapAttrsToList lib.nameValuePair datasets;
181
+
mounts = lib.filter ({ value, ... }: hasDefinedMount value) datasetlist;
182
+
sorted = lib.sort (left: right: (lib.stringLength left.value.mount) > (lib.stringLength right.value.mount)) mounts;
183
+
cmd = { name, value }:
185
+
umount /mnt${lib.escapeShellArg value.mount}
188
+
lib.concatMapStringsSep "\n" cmd sorted;
191
+
fileSystemsCfgFile =
193
+
mountable = lib.filterAttrs (_: value: hasDefinedMount value) datasets;
195
+
pkgs.runCommand "filesystem-config.nix" {
196
+
buildInputs = with pkgs; [ jq nixpkgs-fmt ];
197
+
filesystems = builtins.toJSON {
198
+
fileSystems = lib.mapAttrs'
202
+
name = attrs.mount;
205
+
device = "${dataset}";
211
+
passAsFile = [ "filesystems" ];
214
+
echo "builtins.fromJSON '''"
215
+
jq . < "$filesystemsPath"
223
+
if configFile == null
224
+
then fileSystemsCfgFile
226
+
pkgs.runCommand "configuration.nix" {
227
+
buildInputs = with pkgs; [ nixpkgs-fmt ];
231
+
echo '{ imports = ['
232
+
printf "(%s)\n" "$(cat ${fileSystemsCfgFile})";
233
+
printf "(%s)\n" "$(cat ${configFile})";
241
+
pkgs.vmTools.override {
243
+
[ "zfs" "9p" "9pnet_virtio" "virtio_pci" "virtio_blk" ] ++
244
+
(pkgs.lib.optional (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) "rtc_cmos");
245
+
kernel = modulesTree;
248
+
pkgs.runCommand name
250
+
QEMU_OPTS = "-drive file=$bootDiskImage,if=virtio,cache=unsafe,werror=report"
251
+
+ " -drive file=$rootDiskImage,if=virtio,cache=unsafe,werror=report";
253
+
PATH=$PATH:${pkgs.qemu_kvm}/bin
255
+
bootDiskImage=boot.raw
256
+
qemu-img create -f raw $bootDiskImage ${toString bootSize}M
258
+
rootDiskImage=root.raw
259
+
qemu-img create -f raw $rootDiskImage ${toString rootSize}M
263
+
${if formatOpt == "raw" then ''
264
+
mv $bootDiskImage $out/${bootFilename}
265
+
mv $rootDiskImage $out/${rootFilename}
267
+
${pkgs.qemu}/bin/qemu-img convert -f raw -O ${formatOpt} ${compress} $bootDiskImage $out/${bootFilename}
268
+
${pkgs.qemu}/bin/qemu-img convert -f raw -O ${formatOpt} ${compress} $rootDiskImage $out/${rootFilename}
270
+
bootDiskImage=$out/${bootFilename}
271
+
rootDiskImage=$out/${rootFilename}
276
+
export PATH=${tools}:$PATH
279
+
cp -sv /dev/vda /dev/sda
280
+
cp -sv /dev/vda /dev/xvda
282
+
parted --script /dev/vda -- \
284
+
mkpart no-fs 1MiB 2MiB \
285
+
set 1 bios_grub on \
286
+
align-check optimal 1 \
287
+
mkpart ESP fat32 2MiB -1MiB \
288
+
align-check optimal 2 \
291
+
sfdisk --dump /dev/vda
295
+
${stringifyProperties " -o" rootPoolProperties} \
296
+
${stringifyProperties " -O" rootPoolFilesystemProperties} \
297
+
${rootPoolName} /dev/vdb
298
+
parted --script /dev/vdb -- print
304
+
mkfs.vfat -n ESP /dev/vda2
305
+
mount /dev/vda2 /mnt/boot
309
+
# Install a configuration.nix
310
+
mkdir -p /mnt/etc/nixos
311
+
# `cat` so it is mutable on the fs
312
+
cat ${mergedConfig} > /mnt/etc/nixos/configuration.nix
314
+
export NIX_STATE_DIR=$TMPDIR/state
315
+
nix-store --load-db < ${closureInfo}/registration
320
+
--system ${config.system.build.toplevel} \
321
+
--substituters "" \
322
+
${lib.optionalString includeChannel ''--channel ${channelSources}''}
329
+
zpool export ${rootPoolName}