1{ pkgs
2, lib
3
4, # The NixOS configuration to be installed onto the disk image.
5 config
6
7, # The size of the disk, in megabytes.
8 diskSize
9
10 # The files and directories to be placed in the target file system.
11 # This is a list of attribute sets {source, target} where `source'
12 # is the file system object (regular file or directory) to be
13 # grafted in the file system at path `target'.
14, contents ? []
15
16, # Whether the disk should be partitioned (with a single partition
17 # containing the root filesystem) or contain the root filesystem
18 # directly.
19 partitioned ? true
20
21 # Whether to invoke switch-to-configuration boot during image creation
22, installBootLoader ? true
23
24, # The root file system type.
25 fsType ? "ext4"
26
27, # The initial NixOS configuration file to be copied to
28 # /etc/nixos/configuration.nix.
29 configFile ? null
30
31, # Shell code executed after the VM has finished.
32 postVM ? ""
33
34, name ? "nixos-disk-image"
35
36, format ? "raw"
37}:
38
39with lib;
40
41let
42 extensions = {
43 qcow2 = "qcow2";
44 vpc = "vhd";
45 raw = "img";
46 };
47
48 nixpkgs = lib.cleanSource pkgs.path;
49
50 channelSources = pkgs.runCommand "nixos-${config.system.nixosVersion}" {} ''
51 mkdir -p $out
52 cp -prd ${nixpkgs} $out/nixos
53 chmod -R u+w $out/nixos
54 if [ ! -e $out/nixos/nixpkgs ]; then
55 ln -s . $out/nixos/nixpkgs
56 fi
57 rm -rf $out/nixos/.git
58 echo -n ${config.system.nixosVersionSuffix} > $out/nixos/.version-suffix
59 '';
60
61 metaClosure = pkgs.writeText "meta" ''
62 ${config.system.build.toplevel}
63 ${config.nix.package.out}
64 ${channelSources}
65 '';
66
67 prepareImageInputs = with pkgs; [ rsync utillinux parted e2fsprogs lkl fakeroot config.system.build.nixos-prepare-root ] ++ stdenv.initialPath;
68
69 # I'm preserving the line below because I'm going to search for it across nixpkgs to consolidate
70 # image building logic. The comment right below this now appears in 4 different places in nixpkgs :)
71 # !!! should use XML.
72 sources = map (x: x.source) contents;
73 targets = map (x: x.target) contents;
74
75 prepareImage = ''
76 export PATH=${pkgs.lib.makeSearchPathOutput "bin" "bin" prepareImageInputs}
77
78 mkdir $out
79 diskImage=nixos.raw
80 truncate -s ${toString diskSize}M $diskImage
81
82 ${if partitioned then ''
83 parted $diskImage -- mklabel msdos mkpart primary ext4 1M -1s
84 offset=$((2048*512))
85 '' else ''
86 offset=0
87 ''}
88
89 mkfs.${fsType} -F -L nixos -E offset=$offset $diskImage
90
91 root="$PWD/root"
92 mkdir -p $root
93
94 # Copy arbitrary other files into the image
95 # Semi-shamelessly copied from make-etc.sh. I (@copumpkin) shall factor this stuff out as part of
96 # https://github.com/NixOS/nixpkgs/issues/23052.
97 set -f
98 sources_=(${concatStringsSep " " sources})
99 targets_=(${concatStringsSep " " targets})
100 set +f
101
102 for ((i = 0; i < ''${#targets_[@]}; i++)); do
103 source="''${sources_[$i]}"
104 target="''${targets_[$i]}"
105
106 if [[ "$source" =~ '*' ]]; then
107 # If the source name contains '*', perform globbing.
108 mkdir -p $root/$target
109 for fn in $source; do
110 rsync -a --no-o --no-g "$fn" $root/$target/
111 done
112 else
113 mkdir -p $root/$(dirname $target)
114 if ! [ -e $root/$target ]; then
115 rsync -a --no-o --no-g $source $root/$target
116 else
117 echo "duplicate entry $target -> $source"
118 exit 1
119 fi
120 fi
121 done
122
123 # TODO: Nix really likes to chown things it creates to its current user...
124 fakeroot nixos-prepare-root $root ${channelSources} ${config.system.build.toplevel} closure
125
126 echo "copying staging root to image..."
127 cptofs ${pkgs.lib.optionalString partitioned "-P 1"} -t ${fsType} -i $diskImage $root/* /
128 '';
129in pkgs.vmTools.runInLinuxVM (
130 pkgs.runCommand name
131 { preVM = prepareImage;
132 buildInputs = with pkgs; [ utillinux e2fsprogs ];
133 exportReferencesGraph = [ "closure" metaClosure ];
134 postVM = ''
135 ${if format == "raw" then ''
136 mv $diskImage $out/nixos.img
137 diskImage=$out/nixos.img
138 '' else ''
139 ${pkgs.qemu}/bin/qemu-img convert -f raw -O ${format} $diskImage $out/nixos.${extensions.${format}}
140 diskImage=$out/nixos.${extensions.${format}}
141 ''}
142 ${postVM}
143 '';
144 memSize = 1024;
145 }
146 ''
147 ${if partitioned then ''
148 . /sys/class/block/vda1/uevent
149 mknod /dev/vda1 b $MAJOR $MINOR
150 rootDisk=/dev/vda1
151 '' else ''
152 rootDisk=/dev/vda
153 ''}
154
155 # Some tools assume these exist
156 ln -s vda /dev/xvda
157 ln -s vda /dev/sda
158
159 mountPoint=/mnt
160 mkdir $mountPoint
161 mount $rootDisk $mountPoint
162
163 # Install a configuration.nix
164 mkdir -p /mnt/etc/nixos
165 ${optionalString (configFile != null) ''
166 cp ${configFile} /mnt/etc/nixos/configuration.nix
167 ''}
168
169 mount --rbind /dev $mountPoint/dev
170 mount --rbind /proc $mountPoint/proc
171 mount --rbind /sys $mountPoint/sys
172
173 # Set up core system link, GRUB, etc.
174 NIXOS_INSTALL_BOOTLOADER=1 chroot $mountPoint /nix/var/nix/profiles/system/bin/switch-to-configuration boot
175
176 # TODO: figure out if I should activate, but for now I won't
177 # chroot $mountPoint /nix/var/nix/profiles/system/activate
178
179 # The above scripts will generate a random machine-id and we don't want to bake a single ID into all our images
180 rm -f $mountPoint/etc/machine-id
181
182 umount -R /mnt
183
184 # Make sure resize2fs works. Note that resize2fs has stricter criteria for resizing than a normal
185 # mount, so the `-c 0` and `-i 0` don't affect it. Setting it to `now` doesn't produce deterministic
186 # output, of course, but we can fix that when/if we start making images deterministic.
187 ${optionalString (fsType == "ext4") ''
188 tune2fs -T now -c 0 -i 0 $rootDisk
189 ''}
190 ''
191)