{ config, pkgs, lib, ... }: with lib; let cfg = config.virtualisation.libvirt.guests; diskOptions.options = { diskFile = mkOption { type = types.str; default = "/var/lib/libvirt/images/guest-${name}.qcow2"; }; # TODO bus = mkOption { type = types.enum [ "virtio" "ide" "scsi" "sata" ]; default = "virtio"; }; type = mkOption { type = types.enum [ "raw" "qcow2" ]; default = "qcow2"; }; }; mountOptions.options = { sourceDir = mkOption { type = types.str; default = ""; }; targetDir = mkOption { type = types.str; default = ""; }; # TODO type = mkOption { type = types.enum [ "virtiofs" "9p" ]; default = "virtiofs"; }; }; guestsOptions = { name, ... }: { options = rec { # TODO guestOsType = mkOption { type = enum [ "linux" "windows" ]; default = "linux"; }; uefi = mkOption { type = types.bool; default = false; }; memory = mkOption { type = types.int; default = 1024; }; sharedMemory = mkOption { type = types.bool; # TODO: not needed if using 9p mount default = devices.mounts != [ ]; }; cpu = { sockets = mkOption { type = types.int; default = 1; }; cores = mkOption { type = types.int; default = 1; }; threads = mkOption { type = types.int; default = 1; }; }; devices = { disks = mkOption { type = with types; listOf (submodule diskOptions); }; mounts = mkOption { type = with types; listOf (submodule mountOptions); }; tablet = mkOption { type = types.bool; default = true; }; serial = mkOption { type = types.bool; default = true; }; qemuGuestAgent = mkOption { type = types.bool; default = true; }; audio = { enable = mkOption { type = types.bool; default = true; }; type = mkOption { # TODO type = types.enum [ "none" "alsa" "coreaudio" "dbus" "jack" "oss" "pulseaudio" "sdl" "spice" "file" ]; default = "spice"; }; }; graphics = { enable = mkOption { type = types.bool; # TODO: must be true if video == true? default = true; }; type = mkOption { # TODO type = types.enum [ "sdl" "vnc" "spice" "rdp" "desktop" "egl-headless" ]; default = "spice"; }; }; video = { enable = mkOption { type = types.bool; default = true; }; type = mkOption { # TODO type = types.enum [ "vga" "cirrus" "vmvga" "xen" "vbox" "qxl" "virtio" "gop" "bochs" "ramfb" "none" ]; default = "virtio"; }; }; network = { enable = mkOption { type = types.bool; default = true; }; interfaceType = mkOption { # TODO type = types.enum [ "network" "macvlan" "bridge" ]; default = "network"; }; modelType = mkOption { type = types.enum [ "virtio" "e1000" ]; default = "virtio"; }; macAddress = mkOption { type = with types; nullOr str; default = null; }; active = mkOption { type = types.bool; default = true; }; sourceDev = mkOption { type = types.str; default = "default"; }; }; }; timeout = mkOption { type = types.int; default = 10; }; }; }; in { options.virtualisation.libvirt.guests = mkOption { default = { }; type = types.attrsOf (types.submodule guestsOptions); }; config = { systemd.services = lib.mapAttrs' (name: guest: lib.nameValuePair "libvirtd-guest-${name}" { after = [ "libvirtd.service" ]; requires = [ "libvirtd.service" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = "yes"; }; script = let xml = pkgs.writeText "libvirt-guest-${name}.xml" '' ${name} UUID ${toString guest.memory} ${ lib.optionalString guest.sharedMemory '' '' } ${ with guest.cpu; toString (sockets * cores * threads) } hvm ${ lib.optionalString guest.uefi '' /run/libvirt/nix-ovmf/OVMF_CODE.fd /var/lib/libvirt/qemu/nvram/${name}_VARS.fd '' } ${ lib.optionalString (guest.guestOsType == "windows") '' '' } ${ with guest.cpu; '' '' } ${ lib.optionalString (guest.guestOsType == "windows") '' '' } /run/libvirt/nix-emulators/qemu-system-x86_64 ${ lib.concatStrings (map (disk: '' '') guest.devices.disks) } ${ lib.concatStrings (map (mount: '' '') guest.devices.mounts) } ${ with guest.devices.network; if enable then if interfaceType == "network" then '' ${ lib.optionalString (macAddress != null) '' '' } '' else if interfaceType == "bridge" then '' ${lib.optionalString (macAddress != null) '' ''} '' else if interfaceType == "macvlan" then '' ${lib.optionalString (macAddress != null) '' ''} '' else "" else "" } ${ lib.optionalString guest.devices.tablet '' '' } ${ lib.optionalString guest.devices.serial '' '' } ${ lib.optionalString guest.devices.qemuGuestAgent '' '' } ${ lib.optionalString guest.devices.audio.enable '' ''; in '' uuid="$(${pkgs.libvirt}/bin/virsh domuuid '${name}' || true)" ${pkgs.libvirt}/bin/virsh define <(sed "s/UUID/$uuid/" '${xml}') ${pkgs.libvirt}/bin/virsh start '${name}' ''; preStop = '' ${pkgs.libvirt}/bin/virsh shutdown '${name}' let "timeout = $(date +%s) + ${toString guest.timeout}" while [ "$(${pkgs.libvirt}/bin/virsh list --name | grep --count '^${name}$')" -gt 0 ]; do if [ "$(date +%s)" -ge "$timeout" ]; then ${pkgs.libvirt}/bin/virsh destroy '${name}' else sleep 0.5 fi done ''; }) cfg; }; }