nixfiles/lib/qemu.nix

199 lines
5.5 KiB
Nix
Raw Permalink Normal View History

{ pkgs, pkgs-stable, ... }@inputs:
2024-01-08 07:49:51 +03:00
let
mkDiskFlags = disks: if (builtins.length disks == 0) then [ ] else
([
"-object iothread,id=thread-scsi"
"-device virtio-scsi-pci,bus=pcie.0,num_queues=8,iothread=thread-scsi,id=scsi"
] ++ map
(
disk:
let
name = disk.name;
driver = disk.driver or "qcow2";
path = disk.path;
2024-06-06 13:10:13 +03:00
readonly = disk.readonly or false;
2024-01-08 07:49:51 +03:00
in
builtins.concatStringsSep " " [
2024-06-06 13:10:13 +03:00
"-blockdev driver=${driver},file.driver=file,file.filename=${path},file.aio=io_uring,discard=unmap,detect-zeroes=unmap,read-only=${if readonly then "on" else "off"},cache.direct=on,node-name=${name}"
2024-01-08 07:49:51 +03:00
"-device scsi-hd,drive=${name},bus=scsi.0,rotation_rate=1,physical_block_size=512,logical_block_size=512,id=scsi-${name}"
]
)
disks);
mkUsbFlags = usbs: if (builtins.length usbs == 0) then [ ] else
([
"-device qemu-xhci,id=xhci"
] ++ pkgs.lib.imap0
(
idx: usb: "-device ${usb},bus=xhci.0,port=${toString (idx + 1)},id=usb${toString idx}"
)
usbs);
tapStartCommands = tap: bridge: ''
${tapStopCommands tap}
${pkgs.iproute2}/bin/ip tuntap add dev ${tap} mode tap
${pkgs.iproute2}/bin/ip link set ${tap} up promisc on
${pkgs.bridge-utils}/sbin/brctl addif ${bridge} ${tap}
${pkgs.iproute2}/bin/ip link set dev ${tap} master ${bridge}
'';
tapStopCommands = tap: ''
if (${pkgs.iproute2}/bin/ip tuntap show | grep "^${tap}:"); then
${pkgs.iproute2}/bin/ip tuntap del ${tap} mode tap
fi
'';
mkQemuFlags =
{ efi ? true
, cores ? "4"
, memory ? "4G"
, disks ? [ ]
, usbs ? [ ]
, extraFlags ? [ ]
, OVMF ? pkgs-stable.OVMF.override {
2024-01-08 07:49:51 +03:00
secureBoot = true;
}
, enableTpm ? false
, vnc ? null
, network ? true
, cpufeatures ? "+topoext,+invtsc,host-cache-info=on,l3-cache=on,x2apic=off,+kvm_pv_eoi,+kvm_pv_unhalt"
, hvflags ? (
builtins.concatStringsSep "," [
"hv-relaxed"
"hv-vapic"
"hv-spinlocks=0x1fff"
"hv-vpindex"
"hv-runtime"
"hv-crash"
"hv-time"
"hv-synic"
"hv-stimer"
"hv-tlbflush"
"hv-ipi"
"hv-reset"
"hv-frequencies"
"hv-stimer-direct"
"hv-avic"
"hv-no-nonarch-coresharing=on"
]
)
, tap ? null
, macAddress ? "01:23:45:67:89:ab"
, display ? true
}: [
"-nodefaults"
"-no-user-config"
"-enable-kvm"
"-cpu host,check,enforce,migratable=no,kvm=on,${cpufeatures},${hvflags}"
"-smp ${cores}"
"-m ${memory}"
"-M q35,hpet=off,mem-merge=off"
"-device qemu-xhci"
"-global kvm-pit.lost_tick_policy=discard"
] ++ pkgs.lib.optionals display [
"-device qxl-vga,xres=1920,yres=1080,max_outputs=1"
] ++ pkgs.lib.optionals efi [
"-bios ${OVMF.fd}/FV/OVMF.fd"
] ++ pkgs.lib.optionals enableTpm [
"-chardev socket,id=chrtpm,path=tpm.sock"
"-tpmdev emulator,id=tpm0,chardev=chrtpm"
"-device tpm-tis,tpmdev=tpm0"
] ++ (if (vnc != null) then [
"-vnc ${vnc}"
] else [
"-display none"
]) ++ pkgs.lib.optionals (tap != null) [
"-netdev tap,id=net0,ifname=${tap},script=no,downscript=no"
"-device virtio-net-pci,netdev=net0,mac=${macAddress}"
] ++ (mkDiskFlags disks) ++ (mkUsbFlags usbs) ++ extraFlags;
2024-06-06 13:10:13 +03:00
mkCloudInitImage = {
user ? {},
meta ? {},
network ? null,
}: let
toYAML = builtins.toJSON;
metaYaml = pkgs.writeText "cloud-init-meta.yaml" (toYAML (meta // {
# thx https://gist.github.com/Informatic/0b6b24374b54d09c77b9d25595cdbd47
dsmode = "local";
}));
userYaml = pkgs.writeText "cloud-init-user.yaml" ''
#cloud-config
${toYAML user}
'';
networkYaml = pkgs.writeText "cloud-init-network.yaml" (toYAML {
inherit network;
});
args = []
++ pkgs.lib.optionals (network != null) [
"--network-config"
"${networkYaml}"
];
argsText = builtins.concatStringsSep " " args;
in pkgs.stdenvNoCC.mkDerivation {
name = "cloud-init-seed.img";
buildInputs = [ pkgs.cloud-utils ];
dontUnpack = true;
buildPhase = ''
cloud-localds ${argsText} $out ${userYaml} ${metaYaml}
'';
};
2024-01-08 07:49:51 +03:00
in
{
mkSystemdService =
{ name
, qemu ? "${pkgs.qemu}/bin/qemu-system-x86_64"
, tapName ? "tap-${name}"
, qemuOptions ? { }
, bridgeName ? "br0"
, beforeStart ? ""
, afterEnd ? ""
,
}:
let
sockPath = "/run/qemu-${name}.mon.sock";
qemuParams = mkQemuFlags (qemuOptions // {
tap = tapName;
extraFlags =
(qemuOptions.extraFlags or [ ]) ++
[ "-monitor unix:${sockPath},server,nowait" ];
});
in
{
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig.PrivateTmp = true;
script = ''
set -euxo pipefail
${if (tapName != null) then (tapStartCommands tapName bridgeName) else ""}
${beforeStart}
${qemu} ${builtins.concatStringsSep " " qemuParams}
${afterEnd}
${if (tapName != null) then (tapStopCommands tapName) else ""}
'';
preStop = ''
echo 'system_powerdown' | ${pkgs.socat}/bin/socat - UNIX-CONNECT:${sockPath}
sleep 10
'';
postStop = ''
${if (tapName != null) then (tapStopCommands tapName) else ""}
'';
};
2024-06-06 13:10:13 +03:00
inherit mkCloudInitImage;
mkCloudInitDisk = params: {
name = "init";
driver = "raw";
path = mkCloudInitImage params;
readonly = true;
};
2024-01-08 07:49:51 +03:00
}