X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=systems%2Fdilion%2Fvms.nix;h=189e5ffab1a58d6d7ae96f2e7d94925628272722;hb=1a64deeb894dc95e2645a75771732c6cc53a79ad;hpb=fa25ffd4583cc362075cd5e1b4130f33306103f0;p=perso%2FImmae%2FConfig%2FNix.git diff --git a/systems/dilion/vms.nix b/systems/dilion/vms.nix new file mode 100644 index 0000000..189e5ff --- /dev/null +++ b/systems/dilion/vms.nix @@ -0,0 +1,200 @@ +# inspired from https://nixos.wiki/wiki/Virtualization_in_NixOS +{ config, pkgs, lib, pkgs-no-overlay, ... }@args: +let + toImage = f: "${import ./vms/base_image.nix f (args // { myEnv = config.myEnv; })}/nixos.qcow2"; +in +{ + options = { + myServices.vms.libvirt-guests = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule { + options = { + network = lib.mkOption { type = lib.types.str; description = "network to attach the guest to"; }; + pool = lib.mkOption { type = lib.types.str; description = "pool to attach the guest to"; }; + cpus = lib.mkOption { type = lib.types.int; default = 1; description = "number of cpus to assign"; }; + memory = lib.mkOption { type = lib.types.int; description = "memory in GiB to assign"; }; + diskSize = lib.mkOption { type = lib.types.int; description = "disk size in GiB"; }; + destroyVolumeOnExit = lib.mkOption { type = lib.types.bool; description = "Whether to destroy the volume on exit"; default = false; }; + extraDevicesXML = lib.mkOption { type = lib.types.lines; description = "Extra device configuration"; default = ""; }; + preStart = lib.mkOption { type = lib.types.lines; default = ""; description = "Script to run as prestart"; }; + }; + }); + default = {}; + description = "Libvirt guests to start"; + }; + myServices.vms.libvirt-networks = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule { + options = { + bridgeNumber = lib.mkOption { type = lib.types.int; description = "bridge interface to create virbr"; }; + ipRange = lib.mkOption { type = lib.types.str; example = "192.168.100"; description = "ip4 prefix to use"; }; + }; + }); + description = "Libvirt networks to configure"; + default = {}; + }; + myServices.vms.libvirt-pools = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule { + options = { + type = lib.mkOption { type = lib.types.enum [ "dir" "zfs" ]; description = "Pool type"; }; + target = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; description = "where to find images"; }; + preStart = lib.mkOption { type = lib.types.lines; default = ""; description = "Script to run as prestart"; }; + xml = lib.mkOption { type = lib.types.lines; default = ""; description = "Additional configuration"; }; + }; + }); + }; + myServices.vms.libvirt-images = lib.mkOption { + type = lib.types.attrsOf lib.types.path; + default = {}; + description = "Attrs of images to create in /etc/libvirtd/base-images"; + }; + }; + config = lib.mkMerge [ + # Define images + { + environment.etc = lib.mapAttrs' + (n: v: lib.nameValuePair "libvirtd/base-images/${n}.qcow2" { source = toImage v; }) + config.myServices.vms.libvirt-images; + } + + # Define networks + { + systemd.services = lib.mapAttrs' (name: network: lib.nameValuePair "libvirtd-network-${name}" { + after = [ "libvirtd.service" ]; + requires = [ "libvirtd.service" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = "yes"; + }; + path = [ config.boot.zfs.package ]; + script = let + xml = pkgs.writeText "libvirt-network-${name}.xml" '' + + ${name} + UUID + + + + + + + + + + ''; + in '' + uuid="$(${pkgs.libvirt}/bin/virsh net-uuid '${name}' || true)" + ${pkgs.libvirt}/bin/virsh net-define <(sed "s/UUID/$uuid/" '${xml}') + ${pkgs.libvirt}/bin/virsh net-start '${name}' + ''; + preStop = '' + ${pkgs.libvirt}/bin/virsh net-destroy '${name}' + ''; + }) config.myServices.vms.libvirt-networks; + } + + # Define pools + { + systemd.services = lib.mapAttrs' (name: pool: lib.nameValuePair "libvirtd-pool-${name}" { + after = [ "libvirtd.service" ]; + requires = [ "libvirtd.service" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = "yes"; + }; + path = [ config.boot.zfs.package ]; + script = let + xml = pkgs.writeText "libvirt-pool-${name}.xml" '' + + ${name} + UUID + ${pool.xml} + ${if pool.target != null then '' + + ${pool.target} + + '' else ""} + + ''; + in pool.preStart + '' + uuid="$(${pkgs.libvirt}/bin/virsh pool-uuid '${name}' || true)" + ${pkgs.libvirt}/bin/virsh pool-define <(sed "s/UUID/$uuid/" '${xml}') + ${pkgs.libvirt}/bin/virsh pool-start '${name}' || true + ''; + }) config.myServices.vms.libvirt-pools; + } + + # Define guests + { + systemd.services = lib.mapAttrs' (name: guest: lib.nameValuePair "libvirtd-guest-${name}" { + after = [ "libvirtd.service" "libvirtd-pool-${guest.pool}.service" "libvirtd-network-${guest.network}.service" ]; + requires = [ "libvirtd.service" "libvirtd-pool-${guest.pool}.service" "libvirtd-network-${guest.network}.service" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = "yes"; + }; + path = [ config.boot.zfs.package ]; + script = + let + xml = pkgs.writeText "libvirt-guest-${name}.xml" + '' + + ${name} + UUID + ${builtins.toString guest.memory} + ${builtins.toString guest.cpus} + + hvm + + + /run/current-system/sw/bin/qemu-system-x86_64 + + + + + ${guest.extraDevicesXML} + + + + + + + + + + + ''; + in + guest.preStart + '' + if ! ${pkgs.libvirt}/bin/virsh vol-key 'guest-${name}' --pool ${guest.pool} &> /dev/null; then + ${pkgs.libvirt}/bin/virsh vol-create-as --pool ${guest.pool} --name 'guest-${name}' --capacity '${builtins.toString guest.diskSize}GiB' + volume_path=$(${pkgs.libvirt}/bin/virsh vol-path --pool ${guest.pool} --vol 'guest-${name}') + ${pkgs-no-overlay.qemu}/bin/qemu-img convert /etc/libvirtd/base-images/nixos.qcow2 $volume_path + fi + 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) + 10" + while [ "$(${pkgs.libvirt}/bin/virsh list --name | grep --count '^${name}$')" -gt 0 ]; do + if [ "$(date +%s)" -ge "$timeout" ]; then + # Meh, we warned it... + ${pkgs.libvirt}/bin/virsh destroy '${name}' + else + # The machine is still running, let's give it some time to shut down + sleep 0.5 + fi + done + '' + lib.optionalString guest.destroyVolumeOnExit '' + if ${pkgs.libvirt}/bin/virsh vol-key 'guest-${name}' --pool ${guest.pool} &> /dev/null; then + ${pkgs.libvirt}/bin/virsh vol-wipe --pool ${guest.pool} --vol 'guest-${name}' || true + ${pkgs.libvirt}/bin/virsh vol-delete --pool ${guest.pool} --vol 'guest-${name}' + fi + ''; + }) config.myServices.vms.libvirt-guests; + } + ]; +}