1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8
9 cfg = config.virtualbox;
10in
11{
12 imports = [
13 ./disk-size-option.nix
14 ../image/file-options.nix
15 (lib.mkRenamedOptionModuleWith {
16 sinceRelease = 2411;
17 from = [
18 "virtualbox"
19 "baseImageSize"
20 ];
21 to = [
22 "virtualisation"
23 "diskSize"
24 ];
25 })
26 (lib.mkRenamedOptionModuleWith {
27 sinceRelease = 2505;
28 from = [
29 "virtualisation"
30 "virtualbox"
31 "vmFileName"
32 ];
33 to = [
34 "image"
35 "fileName"
36 ];
37 })
38 ];
39
40 options = {
41 virtualbox = {
42 baseImageFreeSpace = lib.mkOption {
43 type = lib.types.int;
44 default = 30 * 1024;
45 description = ''
46 Free space in the VirtualBox base image in MiB.
47 '';
48 };
49 memorySize = lib.mkOption {
50 type = lib.types.int;
51 default = 1536;
52 description = ''
53 The amount of RAM the VirtualBox appliance can use in MiB.
54 '';
55 };
56 vmDerivationName = lib.mkOption {
57 type = lib.types.str;
58 default = "nixos-ova-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}";
59 description = ''
60 The name of the derivation for the VirtualBox appliance.
61 '';
62 };
63 vmName = lib.mkOption {
64 type = lib.types.str;
65 default = "${config.system.nixos.distroName} ${config.system.nixos.label} (${pkgs.stdenv.hostPlatform.system})";
66 description = ''
67 The name of the VirtualBox appliance.
68 '';
69 };
70 params = lib.mkOption {
71 type =
72 with lib.types;
73 attrsOf (oneOf [
74 str
75 int
76 bool
77 (listOf str)
78 ]);
79 example = {
80 audio = "alsa";
81 rtcuseutc = "on";
82 usb = "off";
83 };
84 description = ''
85 Parameters passed to the Virtualbox appliance.
86
87 Run `VBoxManage modifyvm --help` to see more options.
88 '';
89 };
90 exportParams = lib.mkOption {
91 type =
92 with lib.types;
93 listOf (oneOf [
94 str
95 int
96 bool
97 (listOf str)
98 ]);
99 example = [
100 "--vsys"
101 "0"
102 "--vendor"
103 "ACME Inc."
104 ];
105 default = [ ];
106 description = ''
107 Parameters passed to the Virtualbox export command.
108
109 Run `VBoxManage export --help` to see more options.
110 '';
111 };
112 extraDisk = lib.mkOption {
113 description = ''
114 Optional extra disk/hdd configuration.
115 The disk will be an 'ext4' partition on a separate file.
116 '';
117 default = null;
118 example = {
119 label = "storage";
120 mountPoint = "/home/demo/storage";
121 size = 100 * 1024;
122 };
123 type = lib.types.nullOr (
124 lib.types.submodule {
125 options = {
126 size = lib.mkOption {
127 type = lib.types.int;
128 description = "Size in MiB";
129 };
130 label = lib.mkOption {
131 type = lib.types.str;
132 default = "vm-extra-storage";
133 description = "Label for the disk partition";
134 };
135 mountPoint = lib.mkOption {
136 type = lib.types.str;
137 description = "Path where to mount this disk.";
138 };
139 };
140 }
141 );
142 };
143 postExportCommands = lib.mkOption {
144 type = lib.types.lines;
145 default = "";
146 example = ''
147 ${pkgs.cot}/bin/cot edit-hardware "$fn" \
148 -v vmx-14 \
149 --nics 2 \
150 --nic-types VMXNET3 \
151 --nic-names 'Nic name' \
152 --nic-networks 'Nic match' \
153 --network-descriptions 'Nic description' \
154 --scsi-subtypes VirtualSCSI
155 '';
156 description = ''
157 Extra commands to run after exporting the OVA to `$fn`.
158 '';
159 };
160 storageController = lib.mkOption {
161 type =
162 with lib.types;
163 attrsOf (oneOf [
164 str
165 int
166 bool
167 (listOf str)
168 ]);
169 example = {
170 name = "SCSI";
171 add = "scsi";
172 portcount = 16;
173 bootable = "on";
174 hostiocache = "on";
175 };
176 default = {
177 name = "SATA";
178 add = "sata";
179 portcount = 4;
180 bootable = "on";
181 hostiocache = "on";
182 };
183 description = ''
184 Parameters passed to the VirtualBox appliance. Must have at least
185 `name`.
186
187 Run `VBoxManage storagectl --help` to see more options.
188 '';
189 };
190 };
191 };
192
193 config = {
194 # Use a priority just below mkOptionDefault (1500) instead of lib.mkDefault
195 # to avoid breaking existing configs using that.
196 virtualisation.diskSize = lib.mkOverride 1490 (50 * 1024);
197
198 virtualbox.params = lib.mkMerge [
199 (lib.mapAttrs (name: lib.mkDefault) {
200 acpi = "on";
201 vram = 32;
202 nictype1 = "virtio";
203 nic1 = "nat";
204 audiocontroller = "ac97";
205 audio = "alsa";
206 audioout = "on";
207 graphicscontroller = "vmsvga";
208 rtcuseutc = "on";
209 usb = "on";
210 usbehci = "on";
211 mouse = "usbtablet";
212 })
213 (lib.mkIf (pkgs.stdenv.hostPlatform.system == "i686-linux") { pae = "on"; })
214 ];
215
216 system.nixos.tags = [ "virtualbox" ];
217 image.extension = "ova";
218 system.build.image = lib.mkDefault config.system.build.virtualBoxOVA;
219 system.build.virtualBoxOVA = import ../../lib/make-disk-image.nix {
220 name = cfg.vmDerivationName;
221 baseName = config.image.baseName;
222
223 inherit pkgs lib config;
224 partitionTableType = "legacy";
225 inherit (config.virtualisation) diskSize;
226 additionalSpace = "${toString cfg.baseImageFreeSpace}M";
227
228 postVM = ''
229 export HOME=$PWD
230 export PATH=${pkgs.virtualbox}/bin:$PATH
231
232 echo "converting image to VirtualBox format..."
233 VBoxManage convertfromraw $diskImage disk.vdi
234
235 ${lib.optionalString (cfg.extraDisk != null) ''
236 echo "creating extra disk: data-disk.raw"
237 dataDiskImage=data-disk.raw
238 truncate -s ${toString cfg.extraDisk.size}M $dataDiskImage
239
240 parted --script $dataDiskImage -- \
241 mklabel msdos \
242 mkpart primary ext4 1MiB -1
243 eval $(partx $dataDiskImage -o START,SECTORS --nr 1 --pairs)
244 mkfs.ext4 -F -L ${cfg.extraDisk.label} $dataDiskImage -E offset=$(sectorsToBytes $START) $(sectorsToKilobytes $SECTORS)K
245 echo "creating extra disk: data-disk.vdi"
246 VBoxManage convertfromraw $dataDiskImage data-disk.vdi
247 ''}
248
249 echo "creating VirtualBox VM..."
250 vmName="${cfg.vmName}";
251 VBoxManage createvm --name "$vmName" --register \
252 --ostype ${if pkgs.stdenv.hostPlatform.system == "x86_64-linux" then "Linux26_64" else "Linux26"}
253 VBoxManage modifyvm "$vmName" \
254 --memory ${toString cfg.memorySize} \
255 ${lib.cli.toGNUCommandLineShell { } cfg.params}
256 VBoxManage storagectl "$vmName" ${lib.cli.toGNUCommandLineShell { } cfg.storageController}
257 VBoxManage storageattach "$vmName" --storagectl ${cfg.storageController.name} --port 0 --device 0 --type hdd \
258 --medium disk.vdi
259 ${lib.optionalString (cfg.extraDisk != null) ''
260 VBoxManage storageattach "$vmName" --storagectl ${cfg.storageController.name} --port 1 --device 0 --type hdd \
261 --medium data-disk.vdi
262 ''}
263
264 echo "exporting VirtualBox VM..."
265 mkdir -p $out
266 fn="$out/${config.image.fileName}"
267 VBoxManage export "$vmName" --output "$fn" --options manifest ${lib.escapeShellArgs cfg.exportParams}
268 ${cfg.postExportCommands}
269
270 rm -v $diskImage
271
272 mkdir -p $out/nix-support
273 echo "file ova $fn" >> $out/nix-support/hydra-build-products
274 '';
275 };
276
277 fileSystems =
278 {
279 "/" = {
280 device = "/dev/disk/by-label/nixos";
281 autoResize = true;
282 fsType = "ext4";
283 };
284 }
285 // (lib.optionalAttrs (cfg.extraDisk != null) {
286 ${cfg.extraDisk.mountPoint} = {
287 device = "/dev/disk/by-label/" + cfg.extraDisk.label;
288 autoResize = true;
289 fsType = "ext4";
290 };
291 });
292
293 boot.growPartition = true;
294 boot.loader.grub.device = "/dev/sda";
295
296 swapDevices = [
297 {
298 device = "/var/swap";
299 size = 2048;
300 }
301 ];
302
303 virtualisation.virtualbox.guest.enable = true;
304
305 };
306}