{ config, pkgs, lib, ... }: with lib; { options.proxmox = { qemuConf = { # essential configs boot = mkOption { type = types.str; default = ""; example = "order=scsi0;net0"; description = '' Default boot device. PVE will try all devices in its default order if this value is empty. ''; }; scsihw = mkOption { type = types.str; default = "virtio-scsi-single"; example = "lsi"; description = '' SCSI controller type. Must be one of the supported values given in ''; }; virtio0 = mkOption { type = types.str; default = "local-lvm:vm-9999-disk-0"; example = "ceph:vm-123-disk-0"; description = '' Configuration for the default virtio disk. It can be used as a cue for PVE to autodetect the target storage. This parameter is required by PVE even if it isn't used. ''; }; ostype = mkOption { type = types.str; default = "l26"; description = '' Guest OS type ''; }; cores = mkOption { type = types.ints.positive; default = 1; description = '' Guest core count ''; }; memory = mkOption { type = types.ints.positive; default = 1024; description = '' Guest memory in MB ''; }; bios = mkOption { type = types.enum [ "seabios" "ovmf" ]; default = "seabios"; description = '' Select BIOS implementation (seabios = Legacy BIOS, ovmf = UEFI). ''; }; # optional configs name = mkOption { type = types.str; default = "nixos-${config.system.nixos.label}"; description = '' VM name ''; }; additionalSpace = mkOption { type = types.str; default = "512M"; example = "2048M"; description = '' additional disk space to be added to the image if diskSize "auto" is used. ''; }; bootSize = mkOption { type = types.str; default = "256M"; example = "512M"; description = '' Size of the boot partition. Is only used if partitionTableType is either "efi" or "hybrid". ''; }; diskSize = mkOption { type = types.str; default = "auto"; example = "20480"; description = '' The size of the disk, in megabytes. if "auto" size is calculated based on the contents copied to it and additionalSpace is taken into account. ''; }; net0 = mkOption { type = types.commas; default = "virtio=00:00:00:00:00:00,bridge=vmbr0,firewall=1"; description = '' Configuration for the default interface. When restoring from VMA, check the "unique" box to ensure device mac is randomized. ''; }; serial0 = mkOption { type = types.str; default = "socket"; example = "/dev/ttyS0"; description = '' Create a serial device inside the VM (n is 0 to 3), and pass through a host serial device (i.e. /dev/ttyS0), or create a unix socket on the host side (use qm terminal to open a terminal connection). ''; }; agent = mkOption { type = types.bool; apply = x: if x then "1" else "0"; default = true; description = '' Expect guest to have qemu agent running ''; }; }; qemuExtraConf = mkOption { type = with types; attrsOf (oneOf [ str int ]); default = {}; example = literalExpression '' { cpu = "host"; onboot = 1; } ''; description = '' Additional options appended to qemu-server.conf ''; }; partitionTableType = mkOption { type = types.enum [ "efi" "hybrid" "legacy" "legacy+gpt" ]; description = '' Partition table type to use. See make-disk-image.nix partitionTableType for details. Defaults to 'legacy' for 'proxmox.qemuConf.bios="seabios"' (default), other bios values defaults to 'efi'. Use 'hybrid' to build grub-based hybrid bios+efi images. ''; default = if config.proxmox.qemuConf.bios == "seabios" then "legacy" else "efi"; defaultText = lib.literalExpression ''if config.proxmox.qemuConf.bios == "seabios" then "legacy" else "efi"''; example = "hybrid"; }; filenameSuffix = mkOption { type = types.str; default = config.proxmox.qemuConf.name; example = "999-nixos_template"; description = '' Filename of the image will be vzdump-qemu-''${filenameSuffix}.vma.zstd. This will also determine the default name of the VM on restoring the VMA. Start this value with a number if you want the VMA to be detected as a backup of any specific VMID. ''; }; cloudInit = { enable = mkOption { type = types.bool; default = true; description = '' Whether the VM should accept cloud init configurations from PVE. ''; }; defaultStorage = mkOption { default = "local-lvm"; example = "tank"; type = types.str; description = '' Default storage name for cloud init drive. ''; }; device = mkOption { default = "ide2"; example = "scsi0"; type = types.str; description = '' Bus/device to which the cloud init drive is attached. ''; }; }; }; config = let cfg = config.proxmox; cfgLine = name: value: '' ${name}: ${builtins.toString value} ''; virtio0Storage = builtins.head (builtins.split ":" cfg.qemuConf.virtio0); cfgFile = fileName: properties: pkgs.writeTextDir fileName '' # generated by NixOS ${lib.concatStrings (lib.mapAttrsToList cfgLine properties)} #qmdump#map:virtio0:drive-virtio0:${virtio0Storage}:raw: ''; inherit (cfg) partitionTableType; supportEfi = partitionTableType == "efi" || partitionTableType == "hybrid"; supportBios = partitionTableType == "legacy" || partitionTableType == "hybrid" || partitionTableType == "legacy+gpt"; hasBootPartition = partitionTableType == "efi" || partitionTableType == "hybrid"; hasNoFsPartition = partitionTableType == "hybrid" || partitionTableType == "legacy+gpt"; in { assertions = [ { assertion = config.boot.loader.systemd-boot.enable -> config.proxmox.qemuConf.bios == "ovmf"; message = "systemd-boot requires 'ovmf' bios"; } { assertion = partitionTableType == "efi" -> config.proxmox.qemuConf.bios == "ovmf"; message = "'efi' disk partitioning requires 'ovmf' bios"; } { assertion = partitionTableType == "legacy" -> config.proxmox.qemuConf.bios == "seabios"; message = "'legacy' disk partitioning requires 'seabios' bios"; } { assertion = partitionTableType == "legacy+gpt" -> config.proxmox.qemuConf.bios == "seabios"; message = "'legacy+gpt' disk partitioning requires 'seabios' bios"; } ]; system.build.VMA = import ../../lib/make-disk-image.nix { name = "proxmox-${cfg.filenameSuffix}"; inherit (cfg) partitionTableType; postVM = let # Build qemu with PVE's patch that adds support for the VMA format vma = (pkgs.qemu_kvm.override { alsaSupport = false; pulseSupport = false; sdlSupport = false; jackSupport = false; gtkSupport = false; vncSupport = false; smartcardSupport = false; spiceSupport = false; ncursesSupport = false; libiscsiSupport = false; tpmSupport = false; numaSupport = false; seccompSupport = false; guestAgentSupport = false; }).overrideAttrs ( super: rec { # Check https://github.com/proxmox/pve-qemu/tree/master for the version # of qemu and patch to use version = "9.0.0"; src = pkgs.fetchurl { url = "https://download.qemu.org/qemu-${version}.tar.xz"; hash = "sha256-MnCKxmww2MiSYz6paMdxwcdtWX1w3erSGg0izPOG2mk="; }; patches = [ # Proxmox' VMA tool is published as a particular patch upon QEMU "${pkgs.fetchFromGitHub { owner = "proxmox"; repo = "pve-qemu"; rev = "14afbdd55f04d250bd679ca1ad55d3f47cd9d4c8"; hash = "sha256-lSJQA5SHIHfxJvMLIID2drv2H43crTPMNIlIT37w9Nc="; }}/debian/patches/pve/0027-PVE-Backup-add-vma-backup-format-code.patch" ]; buildInputs = super.buildInputs ++ [ pkgs.libuuid ]; nativeBuildInputs = super.nativeBuildInputs ++ [ pkgs.perl ]; }); in '' ${vma}/bin/vma create "vzdump-qemu-${cfg.filenameSuffix}.vma" \ -c ${cfgFile "qemu-server.conf" (cfg.qemuConf // cfg.qemuExtraConf)}/qemu-server.conf drive-virtio0=$diskImage rm $diskImage ${pkgs.zstd}/bin/zstd "vzdump-qemu-${cfg.filenameSuffix}.vma" mv "vzdump-qemu-${cfg.filenameSuffix}.vma.zst" $out/ mkdir -p $out/nix-support echo "file vma $out/vzdump-qemu-${cfg.filenameSuffix}.vma.zst" > $out/nix-support/hydra-build-products ''; inherit (cfg.qemuConf) additionalSpace diskSize bootSize; format = "raw"; inherit config lib pkgs; }; boot = { growPartition = true; kernelParams = [ "console=ttyS0" ]; loader.grub = { device = lib.mkDefault (if (hasNoFsPartition || supportBios) then # Even if there is a separate no-fs partition ("/dev/disk/by-partlabel/no-fs" i.e. "/dev/vda2"), # which will be used the bootloader, do not set it as loader.grub.device. # GRUB installation fails, unless the whole disk is selected. "/dev/vda" else "nodev"); efiSupport = lib.mkDefault supportEfi; efiInstallAsRemovable = lib.mkDefault supportEfi; }; loader.timeout = 0; initrd.availableKernelModules = [ "uas" "virtio_blk" "virtio_pci" ]; }; fileSystems."/" = { device = "/dev/disk/by-label/nixos"; autoResize = true; fsType = "ext4"; }; fileSystems."/boot" = lib.mkIf hasBootPartition { device = "/dev/disk/by-label/ESP"; fsType = "vfat"; }; networking = mkIf cfg.cloudInit.enable { hostName = mkForce ""; useDHCP = false; }; services = { cloud-init = mkIf cfg.cloudInit.enable { enable = true; network.enable = true; }; sshd.enable = mkDefault true; qemuGuest.enable = true; }; proxmox.qemuExtraConf.${cfg.cloudInit.device} = "${cfg.cloudInit.defaultStorage}:vm-9999-cloudinit,media=cdrom"; }; }