nixos: Add initial QEMU-based VM module
This commit is contained in:
parent
5290c78b06
commit
39f8e65f74
@ -37,6 +37,12 @@
|
||||
networking.bridge = "virtual";
|
||||
};
|
||||
};
|
||||
vms = {
|
||||
instances.test = {
|
||||
networks.virtual = {};
|
||||
vga = "none";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
fileSystems = {
|
||||
@ -82,6 +88,10 @@
|
||||
};
|
||||
|
||||
#systemd.services.systemd-networkd.environment.SYSTEMD_LOG_LEVEL = "debug";
|
||||
virtualisation = {
|
||||
cores = 8;
|
||||
memorySize = 8192;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -10,5 +10,6 @@
|
||||
deploy-rs = ./deploy-rs.nix;
|
||||
secrets = ./secrets.nix;
|
||||
containers = ./containers.nix;
|
||||
vms = ./vms.nix;
|
||||
};
|
||||
}
|
||||
|
@ -78,6 +78,8 @@ in
|
||||
diskImage = dummyOption;
|
||||
forwardPorts = dummyOption;
|
||||
sharedDirectories = dummyOption;
|
||||
cores = dummyOption;
|
||||
memorySize = dummyOption;
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -31,7 +31,7 @@ in
|
||||
name = mkDefault' "dev";
|
||||
isNormalUser = true;
|
||||
uid = mkDefault 1000;
|
||||
extraGroups = mkDefault [ "wheel" ];
|
||||
extraGroups = mkDefault [ "wheel" "kvm" ];
|
||||
password = mkDefault "hunter2"; # TODO: secrets...
|
||||
shell =
|
||||
let shell = cfg.homeConfig.my.shell;
|
||||
|
119
nixos/modules/vms.nix
Normal file
119
nixos/modules/vms.nix
Normal file
@ -0,0 +1,119 @@
|
||||
{ lib, pkgs, config, ... }:
|
||||
let
|
||||
inherit (lib) optional optionals optionalString flatten concatStringsSep mapAttrsToList mapAttrs' mkIf mkDefault;
|
||||
inherit (lib.my) mkOpt' mkBoolOpt';
|
||||
|
||||
flattenQEMUOpts = attrs:
|
||||
concatStringsSep
|
||||
","
|
||||
(mapAttrsToList
|
||||
(k: v: if (v != null) then "${k}=${toString v}" else k)
|
||||
attrs);
|
||||
qemuOpts = with lib.types; coercedTo (attrsOf unspecified) flattenQEMUOpts str;
|
||||
extraQEMUOpts = o: optionalString (o != "") ",${o}";
|
||||
|
||||
cfg = config.my.vms;
|
||||
|
||||
netOpts = with lib.types; { name, ... }: {
|
||||
options = {
|
||||
bridge = mkOpt' str name "Network bridge to connect to.";
|
||||
model = mkOpt' str "virtio-net" "Device type for network interface.";
|
||||
extraOptions = mkOpt' qemuOpts { } "Extra QEMU options to set for the NIC.";
|
||||
};
|
||||
};
|
||||
|
||||
vmOpts = with lib.types; { name, ... }: {
|
||||
options = {
|
||||
qemuBin = mkOpt' path "${pkgs.qemu_kvm}/bin/qemu-kvm" "Path to QEMU executable.";
|
||||
qemuFlags = mkOpt' (listOf str) [ ] "Additional flags to pass to QEMU.";
|
||||
autoStart = mkBoolOpt' true "Whether to start the VM automatically at boot.";
|
||||
|
||||
machine = mkOpt' str "q35" "QEMU machine type.";
|
||||
enableKVM = mkBoolOpt' true "Whether to enable KVM.";
|
||||
enableUEFI = mkBoolOpt' true "Whether to enable UEFI.";
|
||||
cpu = mkOpt' str "host" "QEMU CPU model.";
|
||||
smp = {
|
||||
cpus = mkOpt' ints.unsigned 1 "Number of CPU cores.";
|
||||
threads = mkOpt' ints.unsigned 1 "Number of threads per core.";
|
||||
};
|
||||
memory = mkOpt' ints.unsigned 1024 "Amount of RAM (mebibytes).";
|
||||
vga = mkOpt' str "qxl" "VGA card type.";
|
||||
spice.enable = mkBoolOpt' true "Whether to enable SPICE.";
|
||||
networks = mkOpt' (attrsOf (submodule netOpts)) { } "Networks to attach VM to.";
|
||||
};
|
||||
};
|
||||
|
||||
mkQemuCommand = n: i:
|
||||
let
|
||||
flags =
|
||||
i.qemuFlags ++
|
||||
[
|
||||
"name ${n}"
|
||||
"machine ${i.machine}"
|
||||
"cpu ${i.cpu}"
|
||||
"smp cpus=${toString i.smp.cpus},threads=${toString i.smp.threads}"
|
||||
"m ${toString i.memory}"
|
||||
"nographic"
|
||||
"vga ${i.vga}"
|
||||
"chardev socket,id=monitor,path=/run/vms/${n}/monitor.sock,server=on,wait=off"
|
||||
"mon chardev=monitor"
|
||||
"chardev socket,id=tty,path=/run/vms/${n}/tty.sock,server=on,wait=off"
|
||||
"device isa-serial,chardev=tty"
|
||||
] ++
|
||||
(optional i.enableKVM "enable-kvm") ++
|
||||
(optional i.spice.enable "spice unix=on,addr=/run/vms/${n}/spice.sock,disable-ticketing=on") ++
|
||||
(flatten (mapAttrsToList (nn: c: [
|
||||
"netdev bridge,id=${nn},br=${c.bridge}"
|
||||
("device ${c.model},netdev=${nn}" + (extraQEMUOpts c.extraOptions))
|
||||
]) i.networks)) ++
|
||||
(optionals i.enableUEFI [
|
||||
"drive if=pflash,format=raw,unit=0,readonly=on,file=${cfg.ovmfPackage.fd}/FV/OVMF_CODE.fd"
|
||||
"drive if=pflash,format=raw,unit=1,file=/var/lib/vms/${n}/ovmf_vars.bin"
|
||||
]);
|
||||
args = map (v: "-${v}") flags;
|
||||
in
|
||||
concatStringsSep " " ([ i.qemuBin ] ++ args);
|
||||
in
|
||||
{
|
||||
options.my.vms = with lib.types; {
|
||||
instances = mkOpt' (attrsOf (submodule vmOpts)) { } "VM instances.";
|
||||
ovmfPackage = mkOpt' package pkgs.OVMF "OVMF package.";
|
||||
};
|
||||
|
||||
config = mkIf (cfg.instances != { }) {
|
||||
# qemu-bridge-helper will fail otherwise
|
||||
environment.etc."qemu/bridge.conf".text = "allow all";
|
||||
systemd = {
|
||||
services = mapAttrs' (n: i: {
|
||||
name = "vm@${n}";
|
||||
value = {
|
||||
description = "Virtual machine '${n}'";
|
||||
serviceConfig = {
|
||||
ExecStart = mkQemuCommand n i;
|
||||
RuntimeDirectory = "vms/${n}";
|
||||
StateDirectory = "vms/${n}";
|
||||
};
|
||||
|
||||
preStart =
|
||||
''
|
||||
if [ ! -e "$STATE_DIRECTORY"/ovmf_vars.bin ]; then
|
||||
cp "${cfg.ovmfPackage.fd}"/FV/OVMF_VARS.fd "$STATE_DIRECTORY"/ovmf_vars.bin
|
||||
fi
|
||||
'';
|
||||
postStart =
|
||||
''
|
||||
socks=(monitor tty spice)
|
||||
for s in ''${socks[@]}; do
|
||||
path="$RUNTIME_DIRECTORY"/''${s}.sock
|
||||
until [ -e "$path" ]; do sleep 0.1; done
|
||||
chgrp kvm "$path"
|
||||
chmod 770 "$path"
|
||||
done
|
||||
'';
|
||||
restartIfChanged = mkDefault false;
|
||||
wantedBy = optional i.autoStart "machines.target";
|
||||
};
|
||||
}) cfg.instances;
|
||||
};
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user