1{
2 config,
3 pkgs,
4 lib,
5 ...
6}:
7
8with lib;
9{
10 imports = [
11 ./disk-size-option.nix
12 ../image/file-options.nix
13 (lib.mkRenamedOptionModuleWith {
14 sinceRelease = 2411;
15 from = [
16 "proxmox"
17 "qemuConf"
18 "diskSize"
19 ];
20 to = [
21 "virtualisation"
22 "diskSize"
23 ];
24 })
25 ];
26
27 options.proxmox = {
28 qemuConf = {
29 # essential configs
30 boot = mkOption {
31 type = types.str;
32 default = "";
33 example = "order=scsi0;net0";
34 description = ''
35 Default boot device. PVE will try all devices in its default order if this value is empty.
36 '';
37 };
38 scsihw = mkOption {
39 type = types.str;
40 default = "virtio-scsi-single";
41 example = "lsi";
42 description = ''
43 SCSI controller type. Must be one of the supported values given in
44 <https://pve.proxmox.com/wiki/Qemu/KVM_Virtual_Machines>
45 '';
46 };
47 virtio0 = mkOption {
48 type = types.str;
49 default = "local-lvm:vm-9999-disk-0";
50 example = "ceph:vm-123-disk-0";
51 description = ''
52 Configuration for the default virtio disk. It can be used as a cue for PVE to autodetect the target storage.
53 This parameter is required by PVE even if it isn't used.
54 '';
55 };
56 ostype = mkOption {
57 type = types.str;
58 default = "l26";
59 description = ''
60 Guest OS type
61 '';
62 };
63 cores = mkOption {
64 type = types.ints.positive;
65 default = 1;
66 description = ''
67 Guest core count
68 '';
69 };
70 memory = mkOption {
71 type = types.ints.positive;
72 default = 1024;
73 description = ''
74 Guest memory in MB
75 '';
76 };
77 bios = mkOption {
78 type = types.enum [
79 "seabios"
80 "ovmf"
81 ];
82 default = "seabios";
83 description = ''
84 Select BIOS implementation (seabios = Legacy BIOS, ovmf = UEFI).
85 '';
86 };
87
88 # optional configs
89 name = mkOption {
90 type = types.str;
91 default = "nixos-${config.system.nixos.label}";
92 description = ''
93 VM name
94 '';
95 };
96 additionalSpace = mkOption {
97 type = types.str;
98 default = "512M";
99 example = "2048M";
100 description = ''
101 additional disk space to be added to the image if diskSize "auto"
102 is used.
103 '';
104 };
105 bootSize = mkOption {
106 type = types.str;
107 default = "256M";
108 example = "512M";
109 description = ''
110 Size of the boot partition. Is only used if partitionTableType is
111 either "efi" or "hybrid".
112 '';
113 };
114 net0 = mkOption {
115 type = types.commas;
116 default = "virtio=00:00:00:00:00:00,bridge=vmbr0,firewall=1";
117 description = ''
118 Configuration for the default interface. When restoring from VMA, check the
119 "unique" box to ensure device mac is randomized.
120 '';
121 };
122 serial0 = mkOption {
123 type = types.str;
124 default = "socket";
125 example = "/dev/ttyS0";
126 description = ''
127 Create a serial device inside the VM (n is 0 to 3), and pass through a host serial device (i.e. /dev/ttyS0),
128 or create a unix socket on the host side (use qm terminal to open a terminal connection).
129 '';
130 };
131 agent = mkOption {
132 type = types.bool;
133 apply = x: if x then "1" else "0";
134 default = true;
135 description = ''
136 Expect guest to have qemu agent running
137 '';
138 };
139 };
140 qemuExtraConf = mkOption {
141 type =
142 with types;
143 attrsOf (oneOf [
144 str
145 int
146 ]);
147 default = { };
148 example = literalExpression ''
149 {
150 cpu = "host";
151 onboot = 1;
152 }
153 '';
154 description = ''
155 Additional options appended to qemu-server.conf
156 '';
157 };
158 partitionTableType = mkOption {
159 type = types.enum [
160 "efi"
161 "hybrid"
162 "legacy"
163 "legacy+gpt"
164 ];
165 description = ''
166 Partition table type to use. See make-disk-image.nix partitionTableType for details.
167 Defaults to 'legacy' for 'proxmox.qemuConf.bios="seabios"' (default), other bios values defaults to 'efi'.
168 Use 'hybrid' to build grub-based hybrid bios+efi images.
169 '';
170 default = if config.proxmox.qemuConf.bios == "seabios" then "legacy" else "efi";
171 defaultText = lib.literalExpression ''if config.proxmox.qemuConf.bios == "seabios" then "legacy" else "efi"'';
172 example = "hybrid";
173 };
174 filenameSuffix = mkOption {
175 type = types.str;
176 default = config.proxmox.qemuConf.name;
177 example = "999-nixos_template";
178 description = ''
179 Filename of the image will be vzdump-qemu-''${filenameSuffix}.vma.zstd.
180 This will also determine the default name of the VM on restoring the VMA.
181 Start this value with a number if you want the VMA to be detected as a backup of
182 any specific VMID.
183 '';
184 };
185 cloudInit = {
186 enable = mkOption {
187 type = types.bool;
188 default = true;
189 description = ''
190 Whether the VM should accept cloud init configurations from PVE.
191 '';
192 };
193 defaultStorage = mkOption {
194 default = "local-lvm";
195 example = "tank";
196 type = types.str;
197 description = ''
198 Default storage name for cloud init drive.
199 '';
200 };
201 device = mkOption {
202 default = "ide2";
203 example = "scsi0";
204 type = types.str;
205 description = ''
206 Bus/device to which the cloud init drive is attached.
207 '';
208 };
209 };
210 };
211
212 config =
213 let
214 cfg = config.proxmox;
215 cfgLine = name: value: ''
216 ${name}: ${builtins.toString value}
217 '';
218 virtio0Storage = builtins.head (builtins.split ":" cfg.qemuConf.virtio0);
219 cfgFile =
220 fileName: properties:
221 pkgs.writeTextDir fileName ''
222 # generated by NixOS
223 ${lib.concatStrings (lib.mapAttrsToList cfgLine properties)}
224 #qmdump#map:virtio0:drive-virtio0:${virtio0Storage}:raw:
225 '';
226 inherit (cfg) partitionTableType;
227 supportEfi = partitionTableType == "efi" || partitionTableType == "hybrid";
228 supportBios =
229 partitionTableType == "legacy"
230 || partitionTableType == "hybrid"
231 || partitionTableType == "legacy+gpt";
232 hasBootPartition = partitionTableType == "efi" || partitionTableType == "hybrid";
233 hasNoFsPartition = partitionTableType == "hybrid" || partitionTableType == "legacy+gpt";
234 in
235 {
236 assertions = [
237 {
238 assertion = config.boot.loader.systemd-boot.enable -> config.proxmox.qemuConf.bios == "ovmf";
239 message = "systemd-boot requires 'ovmf' bios";
240 }
241 {
242 assertion = partitionTableType == "efi" -> config.proxmox.qemuConf.bios == "ovmf";
243 message = "'efi' disk partitioning requires 'ovmf' bios";
244 }
245 {
246 assertion = partitionTableType == "legacy" -> config.proxmox.qemuConf.bios == "seabios";
247 message = "'legacy' disk partitioning requires 'seabios' bios";
248 }
249 {
250 assertion = partitionTableType == "legacy+gpt" -> config.proxmox.qemuConf.bios == "seabios";
251 message = "'legacy+gpt' disk partitioning requires 'seabios' bios";
252 }
253 ];
254 image.baseName = lib.mkDefault "vzdump-qemu-${cfg.filenameSuffix}";
255 image.extension = "vma.zst";
256 system.build.image = config.system.build.VMA;
257 system.build.VMA = import ../../lib/make-disk-image.nix {
258 name = "proxmox-${cfg.filenameSuffix}";
259 baseName = config.image.baseName;
260 inherit (cfg) partitionTableType;
261 postVM =
262 let
263 # Build qemu with PVE's patch that adds support for the VMA format
264 vma =
265 (pkgs.qemu_kvm.override {
266 alsaSupport = false;
267 pulseSupport = false;
268 sdlSupport = false;
269 jackSupport = false;
270 gtkSupport = false;
271 vncSupport = false;
272 smartcardSupport = false;
273 spiceSupport = false;
274 ncursesSupport = false;
275 libiscsiSupport = false;
276 tpmSupport = false;
277 numaSupport = false;
278 seccompSupport = false;
279 guestAgentSupport = false;
280 }).overrideAttrs
281 (super: rec {
282 # Check https://github.com/proxmox/pve-qemu/tree/master for the version
283 # of qemu and patch to use
284 version = "9.0.0";
285 src = pkgs.fetchurl {
286 url = "https://download.qemu.org/qemu-${version}.tar.xz";
287 hash = "sha256-MnCKxmww2MiSYz6paMdxwcdtWX1w3erSGg0izPOG2mk=";
288 };
289 patches = [
290 # Proxmox' VMA tool is published as a particular patch upon QEMU
291 "${
292 pkgs.fetchFromGitHub {
293 owner = "proxmox";
294 repo = "pve-qemu";
295 rev = "14afbdd55f04d250bd679ca1ad55d3f47cd9d4c8";
296 hash = "sha256-lSJQA5SHIHfxJvMLIID2drv2H43crTPMNIlIT37w9Nc=";
297 }
298 }/debian/patches/pve/0027-PVE-Backup-add-vma-backup-format-code.patch"
299 ];
300
301 buildInputs = super.buildInputs ++ [ pkgs.libuuid ];
302 nativeBuildInputs = super.nativeBuildInputs ++ [ pkgs.perl ];
303
304 });
305 in
306 ''
307 ${vma}/bin/vma create "${config.image.baseName}.vma" \
308 -c ${
309 cfgFile "qemu-server.conf" (cfg.qemuConf // cfg.qemuExtraConf)
310 }/qemu-server.conf drive-virtio0=$diskImage
311 rm $diskImage
312 ${pkgs.zstd}/bin/zstd "${config.image.baseName}.vma"
313 mv "${config.image.fileName}" $out/
314
315 mkdir -p $out/nix-support
316 echo "file vma $out/${config.image.fileName}" > $out/nix-support/hydra-build-products
317 '';
318 inherit (cfg.qemuConf) additionalSpace bootSize;
319 inherit (config.virtualisation) diskSize;
320 format = "raw";
321 inherit config lib pkgs;
322 };
323
324 boot = {
325 growPartition = true;
326 kernelParams = [ "console=ttyS0" ];
327 loader.grub = {
328 device = lib.mkDefault (
329 if (hasNoFsPartition || supportBios) then
330 # Even if there is a separate no-fs partition ("/dev/disk/by-partlabel/no-fs" i.e. "/dev/vda2"),
331 # which will be used the bootloader, do not set it as loader.grub.device.
332 # GRUB installation fails, unless the whole disk is selected.
333 "/dev/vda"
334 else
335 "nodev"
336 );
337 efiSupport = lib.mkDefault supportEfi;
338 efiInstallAsRemovable = lib.mkDefault supportEfi;
339 };
340
341 loader.timeout = 0;
342 initrd.availableKernelModules = [
343 "uas"
344 "virtio_blk"
345 "virtio_pci"
346 ];
347 };
348
349 fileSystems."/" = {
350 device = "/dev/disk/by-label/nixos";
351 autoResize = true;
352 fsType = "ext4";
353 };
354 fileSystems."/boot" = lib.mkIf hasBootPartition {
355 device = "/dev/disk/by-label/ESP";
356 fsType = "vfat";
357 };
358
359 networking = mkIf cfg.cloudInit.enable {
360 hostName = mkForce "";
361 useDHCP = false;
362 };
363
364 services = {
365 cloud-init = mkIf cfg.cloudInit.enable {
366 enable = true;
367 network.enable = true;
368 };
369 sshd.enable = mkDefault true;
370 qemuGuest.enable = true;
371 };
372
373 proxmox.qemuExtraConf.${cfg.cloudInit.device} =
374 "${cfg.cloudInit.defaultStorage}:vm-9999-cloudinit,media=cdrom";
375 };
376}