add module for declarative libvirt vm's
This commit is contained in:
parent
e7e03a3cd4
commit
faf851a55f
353
modules/libvirt-guests/default.nix
Normal file
353
modules/libvirt-guests/default.nix
Normal file
@ -0,0 +1,353 @@
|
||||
{ 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 != [ ];
|
||||
};
|
||||
vcpu = 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" ''
|
||||
<domain type="kvm">
|
||||
<name>${name}</name>
|
||||
<uuid>UUID</uuid>
|
||||
<memory unit="MiB">${toString guest.memory}</memory>
|
||||
${
|
||||
lib.optionalString guest.sharedMemory ''
|
||||
<memoryBacking>
|
||||
<source type="memfd"/>
|
||||
<access mode="shared"/>
|
||||
</memoryBacking>
|
||||
''
|
||||
}
|
||||
<vcpu placement="static">${toString guest.vcpu}</vcpu>
|
||||
<os>
|
||||
<type arch="x86_64" machine="pc-q35-7.2">hvm</type>
|
||||
${
|
||||
lib.optionalString guest.uefi ''
|
||||
<loader readonly="yes" type="pflash">/run/libvirt/nix-ovmf/OVMF_CODE.fd</loader>
|
||||
<nvram template="/run/libvirt/nix-ovmf/OVMF_CODE.fd">/var/lib/libvirt/qemu/nvram/${name}_VARS.fd</nvram>
|
||||
''
|
||||
}
|
||||
</os>
|
||||
<features>
|
||||
<acpi/>
|
||||
<apic/>
|
||||
<vmport state="off"/>
|
||||
</features>
|
||||
<cpu mode="host-passthrough" check="none" migratable="on"/>
|
||||
<clock offset="utc">
|
||||
<timer name="rtc" tickpolicy="catchup"/>
|
||||
<timer name="pit" tickpolicy="delay"/>
|
||||
<timer name="hpet" present="no"/>
|
||||
</clock>
|
||||
<pm>
|
||||
<suspend-to-mem enabled="no"/>
|
||||
<suspend-to-disk enabled="no"/>
|
||||
</pm>
|
||||
<devices>
|
||||
<emulator>/run/libvirt/nix-emulators/qemu-system-x86_64</emulator>
|
||||
${
|
||||
lib.concatStrings (map (disk: ''
|
||||
<disk type="file" device="disk">
|
||||
<driver name="qemu" type="${disk.type}"/>
|
||||
<source file="${disk.diskFile}"/>
|
||||
<target dev="vda" bus="${disk.bus}"/>
|
||||
</disk>
|
||||
'') guest.devices.disks)
|
||||
}
|
||||
${
|
||||
lib.concatStrings (map (mount: ''
|
||||
<filesystem type="mount" accessmode="passthrough">
|
||||
<driver type="virtiofs" queue="1024"/>
|
||||
<binary path="/run/current-system/sw/bin/virtiofsd" xattr="on">
|
||||
<cache mode="always"/>
|
||||
<lock posix="on" flock="on"/>
|
||||
</binary>
|
||||
<source dir="${mount.sourceDir}"/>
|
||||
<target dir="${mount.targetDir}"/>
|
||||
</filesystem>
|
||||
'') guest.devices.mounts)
|
||||
}
|
||||
${
|
||||
with guest.devices.network;
|
||||
if enable then
|
||||
if interfaceType == "network" then ''
|
||||
<interface type="network">
|
||||
${
|
||||
lib.optionalString (macAddress != null) ''
|
||||
<mac address="${macAddress}"/>
|
||||
''
|
||||
}
|
||||
<source network="${sourceDev}"/>
|
||||
<model type="${modelType}"/>
|
||||
</interface>
|
||||
'' else if interfaceType == "bridge" then ''
|
||||
<interface type="bridge">
|
||||
${lib.optionalString (macAddress != null) ''
|
||||
<mac address="${macAddress}"/>
|
||||
''}
|
||||
<source bridge="${sourceDev}"/>
|
||||
<model type="${modelType}"/>
|
||||
</interface>
|
||||
'' else if interfaceType == "macvlan" then ''
|
||||
<interface type="direct">
|
||||
${lib.optionalString (macAddress != null) ''
|
||||
<mac address="${macAddress}"/>
|
||||
''}
|
||||
<source dev="${sourceDev}" mode="bridge"/>
|
||||
<model type="${modelType}"/>
|
||||
</interface>
|
||||
'' else
|
||||
""
|
||||
else
|
||||
""
|
||||
}
|
||||
${
|
||||
lib.optionalString guest.devices.tablet ''
|
||||
<input type="tablet" bus="usb"/>
|
||||
''
|
||||
}
|
||||
${
|
||||
lib.optionalString guest.devices.serial ''
|
||||
<serial type="pty"/>
|
||||
''
|
||||
}
|
||||
${
|
||||
lib.optionalString guest.devices.qemuGuestAgent ''
|
||||
<channel type="unix">
|
||||
<target type="virtio" name="org.qemu.guest_agent.0"/>
|
||||
</channel>
|
||||
''
|
||||
}
|
||||
${
|
||||
lib.optionalString guest.devices.audio.enable ''
|
||||
<audio id="1" type="${guest.devices.audio.type}"/>
|
||||
<sound model="ich9"/>
|
||||
''
|
||||
}
|
||||
${
|
||||
if guest.devices.graphics.enable then
|
||||
if guest.devices.graphics.type == "spice" then ''
|
||||
<graphics type="spice" autoport="yes">
|
||||
<listen type="address"/>
|
||||
<image compression="off"/>
|
||||
</graphics>
|
||||
'' else
|
||||
""
|
||||
else
|
||||
""
|
||||
}
|
||||
${
|
||||
lib.optionalString guest.devices.video.enable ''
|
||||
<video>
|
||||
<model type="${guest.devices.video.type}" heads="1"/>
|
||||
</video>
|
||||
''
|
||||
}
|
||||
<channel type="spicevmc">
|
||||
<target type="virtio" name="com.redhat.spice.0"/>
|
||||
</channel>
|
||||
<input type="mouse" bus="ps2"/>
|
||||
<input type="keyboard" bus="ps2"/>
|
||||
<redirdev bus='usb' type='spicevmc'/>
|
||||
<memballoon model="virtio"/>
|
||||
<rng model="virtio">
|
||||
<backend model="random">/dev/urandom</backend>
|
||||
</rng>
|
||||
</devices>
|
||||
</domain>
|
||||
'';
|
||||
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;
|
||||
};
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user