]> git.immae.eu Git - perso/Immae/Config/Nix.git/blobdiff - systems/dilion/vms.nix
Squash changes containing private information
[perso/Immae/Config/Nix.git] / systems / dilion / vms.nix
diff --git a/systems/dilion/vms.nix b/systems/dilion/vms.nix
new file mode 100644 (file)
index 0000000..189e5ff
--- /dev/null
@@ -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<nbr>"; };
+          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" ''
+            <network>
+              <name>${name}</name>
+              <uuid>UUID</uuid>
+              <forward mode='nat' />
+              <bridge name='virbr${builtins.toString network.bridgeNumber}' />
+              <domain name='${name}' localOnly='yes'/>
+              <ip address='${network.ipRange}.1' netmask='255.255.255.0'>
+                <dhcp>
+                  <range start='${network.ipRange}.2' end='${network.ipRange}.254'/>
+                </dhcp>
+              </ip>
+            </network>
+          '';
+        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" ''
+            <pool type="${pool.type}">
+              <name>${name}</name>
+              <uuid>UUID</uuid>
+              ${pool.xml}
+              ${if pool.target != null then ''
+                <target>
+                  <path>${pool.target}</path>
+                </target>
+              '' else ""}
+            </pool>
+          '';
+        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"
+            ''
+              <domain type="kvm">
+                <name>${name}</name>
+                <uuid>UUID</uuid>
+                <memory unit="GiB">${builtins.toString guest.memory}</memory>
+                <vcpu>${builtins.toString guest.cpus}</vcpu>
+                <os>
+                  <type arch="x86_64">hvm</type>
+                </os>
+                <devices>
+                  <emulator>/run/current-system/sw/bin/qemu-system-x86_64</emulator>
+                  <disk type="volume">
+                    <source pool="${guest.pool}" volume="guest-${name}" />
+                    <target dev="vda" bus="virtio"/>
+                  </disk>
+                  ${guest.extraDevicesXML}
+                  <input type="keyboard" bus="usb"/>
+                  <graphics type="vnc" port="-1" autoport="yes"/>
+                  <interface type="network">
+                    <source network="${guest.network}" />
+                  </interface>
+                </devices>
+                <features>
+                  <acpi/>
+                </features>
+              </domain>
+            '';
+          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;
+    }
+  ];
+}