about summary refs log tree commit diff
path: root/nix/modules/libvirtnix/domain.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nix/modules/libvirtnix/domain.nix')
-rw-r--r--nix/modules/libvirtnix/domain.nix1106
1 files changed, 1033 insertions, 73 deletions
diff --git a/nix/modules/libvirtnix/domain.nix b/nix/modules/libvirtnix/domain.nix
index 22cd891..d1a5b9c 100644
--- a/nix/modules/libvirtnix/domain.nix
+++ b/nix/modules/libvirtnix/domain.nix
@@ -12,11 +12,363 @@ let
       type = lib.types.str;
       default = "${default}";
     };
+
+  # shorthand for an option with an enum, no default value
+  enumOpt =
+    options:
+    lib.mkOption {
+      type = lib.types.nullOr (lib.types.enum options);
+      default = null;
+    };
 in
 {
   options = {
     services.emile.libvirtnix =
       let
+
+        address = lib.mkOption {
+          default = null;
+          type = lib.types.nullOr (
+            lib.types.submodule {
+              options = {
+                type = enumOpt [ "pci" ];
+                domain = lib.mkOption {
+                  type = lib.types.str;
+                  example = "0x0000";
+                };
+                bus = lib.mkOption {
+                  type = lib.types.str;
+                  example = "0x4";
+                };
+                slot = lib.mkOption {
+                  type = lib.types.str;
+                  example = "0x00";
+                };
+                function = lib.mkOption {
+                  type = lib.types.str;
+                  example = "0x00";
+                };
+                multifunction = lib.mkOption {
+                  type = lib.types.enum [
+                    "on"
+                    "off"
+                  ];
+                  example = "on";
+                  default = "off";
+                };
+              };
+            }
+          );
+        };
+
+        interface = lib.mkOption {
+          type = lib.types.submodule {
+            options = {
+              type = enumOpt [ "bridge" ];
+              mac = lib.mkOption {
+                type = lib.types.submodule {
+                  options = {
+                    address = lib.types.mkOption {
+                      type = lib.types.str;
+                    };
+                  };
+                };
+              };
+              source = lib.mkOption {
+                type = lib.types.submodule {
+                  options = {
+                    network = lib.types.mkOption {
+                      type = lib.types.str;
+                    };
+                    portid = lib.types.mkOption {
+                      type = lib.types.str;
+                    };
+                    bridge = lib.types.mkOption {
+                      type = lib.types.str;
+                    };
+                  };
+                };
+              };
+              target = lib.mkOption {
+                type = lib.types.submodule {
+                  options = {
+                    dev = lib.types.mkOption {
+                      type = lib.types.str;
+                    };
+                  };
+                };
+              };
+              model = lib.mkOption {
+                type = lib.types.submodule {
+                  options = {
+                    type = lib.types.mkOption {
+                      type = enumOpt [ "virtio" ];
+                    };
+                  };
+                };
+              };
+              alias = lib.mkOption {
+                type = lib.types.submodule {
+                  options = {
+                    dev = lib.types.mkOption {
+                      name = lib.types.str;
+                    };
+                  };
+                };
+              };
+              inherit address;
+            };
+          };
+        };
+
+
+        controller = lib.types.submodule {
+          options = {
+            type = enumOpt [
+              "usb"
+              "pci"
+              "sata"
+              "virtio-serial"
+              "scsi"
+            ];
+            index = lib.mkOption { type = lib.types.int; };
+            model = lib.mkOption {
+              type = lib.types.nullOr lib.types.str;
+              default = null;
+            };
+            ports = lib.mkOption {
+              type = lib.types.nullOr lib.types.int;
+              default = null;
+            };
+            alias = lib.mkOption {
+              type = lib.types.submodule {
+                options = {
+                  name = lib.mkOption { type = lib.types.str; };
+                };
+              };
+            };
+            target = lib.mkOption {
+              type = lib.types.nullOr (
+                lib.types.submodule {
+                  options = {
+                    chassis = lib.mkOption { type = lib.types.int; };
+                    port = lib.mkOption { type = lib.types.str; };
+                  };
+                }
+              );
+              default = null;
+            };
+            inherit address;
+          };
+        };
+
+        emulator = lib.types.submodule {
+          options = {
+            value = lib.mkOption { type = lib.types.str; };
+          };
+        };
+
+        disk = lib.types.submodule {
+          options = {
+            type = enumOpt [ "block" ];
+            device = enumOpt [ "disk" ];
+            driver = lib.mkOption {
+              type = lib.types.submodule {
+                options = {
+                  name = lib.mkOption { type = lib.types.str; };
+                  type = enumOpt [ "raw" ];
+                  cache = enumOpt [ "none" ];
+                  io = enumOpt [ "native" ];
+                  discard = enumOpt [ "unmap" ];
+                };
+              };
+            };
+            source = lib.mkOption {
+              type = lib.types.submodule {
+                options = {
+                  dev = lib.mkOption { type = lib.types.str; };
+                  index = lib.mkOption { type = lib.types.int; };
+                };
+              };
+            };
+            backingStore = lib.mkOption { type = lib.types.bool; };
+            target = lib.mkOption {
+              type = lib.types.submodule {
+                options = {
+                  dev = lib.mkOption { type = lib.types.str; };
+                  bus = enumOpt [ "virtio" ];
+                };
+              };
+            };
+            alias = lib.mkOption {
+              type = lib.types.submodule {
+                options = {
+                  name = lib.mkOption { type = lib.types.str; };
+                };
+              };
+            };
+            inherit address;
+          };
+        };
+
+        devices = lib.mkOption {
+          type = lib.types.submodule {
+            options = {
+              emulators = lib.mkOption {
+                type = lib.types.listOf emulator;
+              };
+              disks = lib.mkOption {
+                type = lib.types.listOf disk;
+              };
+              controllers = lib.mkOption {
+                type = lib.types.listOf controller;
+              };
+              interfaces = lib.mkOption {
+                type = lib.types.listOf interface;
+              };
+            };
+          };
+        };
+
+        pm = lib.mkOption {
+          type = lib.types.submodule {
+            options = {
+              suspend-to-mem = lib.mkOption {
+                type = lib.types.nullOr (
+                  lib.types.enum [
+                    "yes"
+                    "no"
+                  ]
+                );
+                default = null;
+                example = "yes";
+              };
+              suspend-to-disk = lib.mkOption {
+                type = lib.types.nullOr (
+                  lib.types.enum [
+                    "yes"
+                    "no"
+                  ]
+                );
+                default = null;
+                example = "yes";
+              };
+            };
+          };
+        };
+
+        on_handle = lib.mkOption {
+          type = lib.types.enum [
+            "destroy"
+            "restart"
+            "preserve"
+            "rename-restart"
+          ];
+        };
+
+        on_poweroff = on_handle;
+        on_reboot = on_handle;
+        on_crash = on_handle;
+
+        clock = lib.mkOption {
+          type = lib.types.submodule {
+            options = {
+              offset = lib.mkOption {
+                type = lib.types.str;
+                example = "utc";
+                default = "utc";
+              };
+              timer = lib.mkOption {
+                type = lib.types.listOf (
+                  lib.types.submodule {
+                    options = {
+                      name = lib.mkOption {
+                        type = lib.types.enum [
+                          # "platform" (currently unsupported)
+                          "hpet" # xen, qemu, lxc
+                          "kvmclock" # qemu
+                          "pit" # qemu
+                          "rtc" # qemu, lxc
+                          "tsc" # xen, qemu - since 3.2.0
+
+                          # The hypervclock timer adds support for the reference time counter and the reference page for iTSC feature for guests running the Microsoft Windows operating system.
+                          "hypervclock" # qemu - since 1.2.2
+
+                          "armvtimer" # qemu - since 6.1.0
+                        ];
+                        default = "";
+                        example = "";
+                      };
+                      track = lib.mkOption {
+                        # TODO(emile): Only valid for name="rtc" or name="platform".
+                        type = lib.types.enum [
+                          "boot"
+                          "guest"
+                          "wall"
+                          "realtime"
+                        ];
+                        default = "";
+                        example = "";
+                      };
+                      tickpolicy = lib.mkOption {
+                        type = lib.types.nullOr (
+                          lib.types.enum [
+                            "catchup"
+                            "delay"
+                            "merge"
+                            "discard"
+                            "catchup"
+                          ]
+                        );
+                        default = null;
+                        example = "";
+                      };
+                      present = lib.mkOption {
+                        type = lib.types.nullOr (
+                          lib.types.enum [
+                            "yes"
+                            "no"
+                          ]
+                        );
+                        default = null;
+                        example = "";
+                      };
+                    };
+                  }
+                );
+                example = [
+                  {
+                    name = "rtc";
+                    tickpolicy = "catchup";
+                  }
+                  {
+                    name = "pit";
+                    tickpolicy = "delay";
+                  }
+                  {
+                    name = "hpet";
+                    present = "no";
+                  }
+                ];
+                default = [
+                  {
+                    name = "rtc";
+                    tickpolicy = "catchup";
+                  }
+                  {
+                    name = "pit";
+                    tickpolicy = "delay";
+                  }
+                  {
+                    name = "hpet";
+                    present = "no";
+                  }
+                ];
+              };
+            };
+          };
+        };
+
         cpu = lib.mkOption {
           type = lib.types.submodule {
             options = {
@@ -59,7 +411,7 @@ in
             };
           };
         };
-      
+
         os_boot = lib.mkOption {
           type = lib.types.submodule {
             options = {
@@ -71,7 +423,7 @@ in
             };
           };
         };
-      
+
         os_nvram = lib.mkOption {
           type = lib.types.submodule {
             options = {
@@ -83,7 +435,7 @@ in
             };
           };
         };
-      
+
         os_loader = lib.mkOption {
           type = lib.types.submodule {
             options = {
@@ -105,7 +457,7 @@ in
             };
           };
         };
-      
+
         os_type = lib.mkOption {
           type = lib.types.submodule {
             options = {
@@ -233,13 +585,18 @@ in
               os
               features
               cpu
+              clock
+              on_poweroff
+              on_reboot
+              on_crash
+              pm
+              devices
               ;
           };
         };
       in
       {
         enable = lib.mkEnableOption "Enable r2wars-web";
-      
 
         # Temporary output we write the domain to, in order to inspect the correctness of
         # the generated xml
@@ -258,93 +615,690 @@ in
       let
         vm = config.services.emile.libvirtnix.vm;
 
-        cpu =
-          vm_name:
+        # try this in a nix repl
+        # nix-repl> mkTag = import ./xml.nix { lib = (import <nixpkgs> {}).lib; }
+        # nix-repl> :p let func = (x: mkTag {name=x.variant;}); case = {"emulator" = func; "disk" = func;}; in map (x: case."${x.variant}" x) [ {variant = "emulator"; value = "asd";} {variant = "disk"; type = "block"; } ]
+
+        emulator =
+          x:
           mkTag {
-            name = "cpu";
-            args = let
-              mode = vm.${vm_name}.cpu.mode;
-              check = vm.${vm_name}.cpu.check;
-              migratable= vm.${vm_name}.cpu.migratable;
-            in [
-              (if (mode != null)
-               then {key = "mode"; val = mode;}
-               else "")
-              (if (check != null)
-               then {key = "check"; val = check;}
-               else "")
-              (if (migratable != null)
-               then {key = "migratable"; val = migratable;}
-               else "")
+            name = "emulator";
+            value = x.value;
+          };
+
+        interface =
+          x:
+          mkTag {
+            name = "interface";
+            args = [
+              (
+                if x.type != null then
+                  {
+                    key = "type";
+                    val = x.type;
+                  }
+                else
+                  ""
+              )
+            ];
+            children = [
+              # (
+              #   if x.mac != null then
+              #     (mkTag {
+              #       name = "mac";
+              #       args = [
+              #         {
+              #           key = "address";
+              #           val = x.mac.address;
+              #         }
+              #       ];
+              #       closing = false;
+              #     })
+              #   else
+              #     ""
+              # )
+              #(
+              #  if x.source != null then
+              #    (mkTag {
+              #      name = "source";
+              #      args = [
+              #        {
+              #          key = "network";
+              #          val = x.source.network;
+              #        }
+              #        {
+              #          key = "portid";
+              #          val = x.source.portid;
+              #        }
+              #        {
+              #          key = "bridge";
+              #          val = x.source.bridge;
+              #        }
+              #      ];
+              #      closing = false;
+              #    })
+              #  else
+              #    ""
+              #)
+              #(
+              #  if x.target != null then
+              #    (mkTag {
+              #      name = "target";
+              #      args = [
+              #        {
+              #          key = "dev";
+              #          val = x.target.dev;
+              #        }
+              #      ];
+              #    })
+              #  else
+              #    ""
+              #)
+              #(
+              #  if x.model != null then
+              #    (mkTag {
+              #      name = "model";
+              #      args = [
+              #        {
+              #          key = "type";
+              #          val = x.model.type;
+              #        }
+              #      ];
+              #    })
+              #  else
+              #    ""
+              #)
+              #(
+              #  if x.alias!= null then
+              #    (mkTag {
+              #      name = "alias";
+              #      args = [
+              #        {
+              #          key = "name";
+              #          val = x.alias.name;
+              #        }
+              #      ];
+              #    })
+              #  else
+              #    ""
+              #)
+              #(
+              #  if x.address != null then
+              #    mkTag {
+              #      name = "address";
+              #      args = [
+              #        (
+              #          if x.address.type != null then
+              #            {
+              #              key = "type";
+              #              val = x.address.type;
+              #            }
+              #          else
+              #            ""
+              #        )
+              #        {
+              #          key = "domain";
+              #          val = x.address.domain;
+              #        }
+              #        {
+              #          key = "bus";
+              #          val = x.address.bus;
+              #        }
+              #        {
+              #          key = "slot";
+              #          val = x.address.slot;
+              #        }
+              #        {
+              #          key = "function";
+              #          val = x.address.function;
+              #        }
+              #      ];
+              #      closing = false;
+              #    }
+              #  else
+              #    ""
+              #)
             ];
-            closing = false;
           };
-        
-        features =
-          vm_name:
+
+        controller =
+          x:
           mkTag {
-            name = "features";
-            children = let
-              acpi = vm_name: mkTag {
-                name = "acpi";
-                closing = false;
-              };
-              apic = vm_name: mkTag {
-                name = "apic";
-                closing = false;
-              };
-              vmport = vm_name: mkTag {
-                name = "vmport";
+            name = "controller";
+            args = [
+              (
+                if x.type != null then
+                  {
+                    key = "type";
+                    val = x.type;
+                  }
+                else
+                  ""
+              )
+              (
+                if x.index != null then
+                  {
+                    key = "index";
+                    val = "${toString x.index}";
+                  }
+                else
+                  ""
+              )
+              (
+                if x.model != null then
+                  {
+                    key = "model";
+                    val = x.model;
+                  }
+                else
+                  ""
+              )
+              (
+                if x.ports != null then
+                  {
+                    key = "ports";
+                    val = "${toString x.ports}";
+                  }
+                else
+                  ""
+              )
+            ];
+            children = [
+              # TODO(emile): figure out if the arg in controller is really the same as the
+              #              one given in model. The configs I've got let it seem so
+              # <controller type="pci" index="1" model="pcie-root-port">
+              #   <model name="pcie-root-port"/>
+              (
+                if x.model != null then
+                  (mkTag {
+                    name = "model";
+                    args = [
+                      {
+                        key = "name";
+                        val = x.model;
+                      }
+                    ];
+                    closing = false;
+                  })
+                else
+                  ""
+              )
+              (
+                if x.target != null then
+                  (mkTag {
+                    name = "target";
+                    args = [
+                      {
+                        key = "chassis";
+                        val = "${toString x.target.chassis}";
+                      }
+                      {
+                        key = "port";
+                        val = x.target.port;
+                      }
+                    ];
+                  })
+                else
+                  ""
+              )
+              (mkTag {
+                name = "alias";
                 args = [
-                  { key = "state"; val = vm.${vm_name}.features.vmport.state; }
+                  (
+                    if x.alias.name != null then
+                      {
+                        key = "name";
+                        val = x.alias.name;
+                      }
+                    else
+                      ""
+                  )
                 ];
                 closing = false;
-              };
-            in [
-              (if (vm.${vm_name}.features.acpi != false) then (acpi vm_name) else "")
-              (if (vm.${vm_name}.features.apic != false) then (apic vm_name) else "")
-              (if (vm.${vm_name}.features.vmport != null) then (vmport vm_name) else "")
+              })
+
+              (
+                if x.address != null then
+                  mkTag {
+                    name = "address";
+                    args = [
+                      (
+                        if x.address.type != null then
+                          {
+                            key = "type";
+                            val = x.address.type;
+                          }
+                        else
+                          ""
+                      )
+                      {
+                        key = "domain";
+                        val = x.address.domain;
+                      }
+                      {
+                        key = "bus";
+                        val = x.address.bus;
+                      }
+                      {
+                        key = "slot";
+                        val = x.address.slot;
+                      }
+                      {
+                        key = "function";
+                        val = x.address.function;
+                      }
+                    ];
+                    closing = false;
+                  }
+                else
+                  ""
+              )
             ];
           };
 
-        os =
-          vm_name:
+        disk =
+          x:
           mkTag {
-            name = "os";
-            children = let
-              os_type = vm_name: mkTag {
-                name = "type";
+            name = "disk";
+            args = [
+              {
+                key = "type";
+                val = x.type;
+              }
+              {
+                key = "device";
+                val = x.device;
+              }
+            ];
+            children = [
+              (mkTag {
+                name = "driver";
                 args = [
-                  { key = "arch"; val = vm.${vm_name}.os.type.arch; }
-                  { key = "machine"; val = vm.${vm_name}.os.type.machine; }
+                  (
+                    if x.driver.name != null then
+                      {
+                        key = "name";
+                        val = x.driver.name;
+                      }
+                    else
+                      ""
+                  )
+                  (
+                    if x.driver.type != null then
+                      {
+                        key = "type";
+                        val = x.driver.type;
+                      }
+                    else
+                      ""
+                  )
+                  (
+                    if x.driver.cache != null then
+                      {
+                        key = "cache";
+                        val = x.driver.cache;
+                      }
+                    else
+                      ""
+                  )
+                  (
+                    if x.driver.io != null then
+                      {
+                        key = "io";
+                        val = x.driver.io;
+                      }
+                    else
+                      ""
+                  )
+                  (
+                    if x.driver.discard != null then
+                      {
+                        key = "discard";
+                        val = x.driver.discard;
+                      }
+                    else
+                      ""
+                  )
                 ];
-                value = vm.${vm_name}.os.type.value;
-              };
-              os_loader = vm_name: mkTag {
-                name = "loader";
+                closing = false;
+              })
+              (mkTag {
+                name = "source";
                 args = [
-                  { key = "readonly"; val = vm.${vm_name}.os.loader.readonly; }
-                  { key = "pflash"; val = vm.${vm_name}.os.loader.type; }
+                  {
+                    key = "dev";
+                    val = x.source.dev;
+                  }
+                  {
+                    key = "index";
+                    val = "${toString x.source.index}";
+                  }
                 ];
-                value = vm.${vm_name}.os.loader.value;
-              };
-              os_nvram = vm_name: mkTag {
-                name = "nvram";
-                value = vm.${vm_name}.os.nvram.value;
-              };
-              os_boot = vm_name: mkTag {
-                name = "type";
+                closing = false;
+              })
+              (
+                if x.backingStore == true then
+                  mkTag {
+                    name = "backingStore";
+                    closing = false;
+                  }
+                else
+                  ""
+              )
+              (mkTag {
+                name = "target";
                 args = [
-                  { key = "dev"; val = vm.${vm_name}.os.boot.dev; }
+                  {
+                    key = "dev";
+                    val = x.target.dev;
+                  }
+                  {
+                    key = "bus";
+                    val = x.target.bus;
+                  }
                 ];
                 closing = false;
-              };
-            in [
-              (os_type vm_name)
-              (os_loader vm_name)
-              (os_nvram vm_name)
-              (os_boot vm_name)
+              })
+              (mkTag {
+                name = "alias";
+                args = [
+                  {
+                    key = "name";
+                    val = x.alias.name;
+                  }
+                ];
+                closing = false;
+              })
+              (mkTag {
+                name = "address";
+                args = [
+                  {
+                    key = "type";
+                    val = x.address.type;
+                  }
+                  {
+                    key = "domain";
+                    val = x.address.domain;
+                  }
+                  {
+                    key = "bus";
+                    val = x.address.bus;
+                  }
+                  {
+                    key = "slot";
+                    val = x.address.slot;
+                  }
+                  {
+                    key = "function";
+                    val = x.address.function;
+                  }
+                ];
+                closing = false;
+              })
             ];
+            value = x.type;
+          };
+
+        devices =
+          vm_name:
+          mkTag {
+            name = "devices";
+            children =
+              map (x: emulator x) vm.${vm_name}.devices.emulators
+              ++ map (x: disk x) vm.${vm_name}.devices.disks
+              ++ map (x: controller x) vm.${vm_name}.devices.controllers
+              ++ map (x: interface x) vm.${vm_name}.devices.interfaces;
+          };
+
+        pm =
+          vm_name:
+          mkTag {
+            name = "pm";
+            children =
+              let
+                suspend-to-mem = mkTag {
+                  name = "suspend-to-mem";
+                  args = [
+                    {
+                      key = "enabled";
+                      val = vm.${vm_name}.pm.suspend-to-mem;
+                    }
+                  ];
+                  closing = false;
+                };
+
+                suspend-to-disk = mkTag {
+                  name = "suspend-to-disk";
+                  args = [
+                    {
+                      key = "enabled";
+                      val = vm.${vm_name}.pm.suspend-to-disk;
+                    }
+                  ];
+                  closing = false;
+                };
+              in
+              [
+                suspend-to-mem
+                suspend-to-disk
+              ];
+          };
+
+        on_handler =
+          { vm_name, tag }:
+          mkTag {
+            name = "${tag}";
+            value = vm.${vm_name}.${tag};
+          };
+
+        on_poweroff =
+          vm_name:
+          on_handler {
+            inherit vm_name;
+            tag = "on_poweroff";
+          };
+        on_reboot =
+          vm_name:
+          on_handler {
+            inherit vm_name;
+            tag = "on_reboot";
+          };
+        on_crash =
+          vm_name:
+          on_handler {
+            inherit vm_name;
+            tag = "on_crash";
+          };
+
+        clock =
+          vm_name:
+          mkTag {
+            name = "clock";
+            args = [
+              {
+                key = "offset";
+                val = vm.${vm_name}.clock.offset;
+              }
+            ];
+            children = map (
+              x:
+              mkTag {
+                name = "timer";
+                args =
+                  let
+                    tickpolicy =
+                      if x.tickpolicy != null then
+                        {
+                          key = "tickpolicy";
+                          val = x.tickpolicy;
+                        }
+                      else
+                        { };
+
+                    present =
+                      if x.present != null then
+                        {
+                          key = "present";
+                          val = x.present;
+                        }
+                      else
+                        { };
+                  in
+                  [
+                    {
+                      key = "name";
+                      val = x.name;
+                    }
+                    tickpolicy
+                    present
+                  ];
+              }
+            ) vm.${vm_name}.clock.timer;
+          };
+
+        cpu =
+          vm_name:
+          mkTag {
+            name = "cpu";
+            args =
+              let
+                mode = vm.${vm_name}.cpu.mode;
+                check = vm.${vm_name}.cpu.check;
+                migratable = vm.${vm_name}.cpu.migratable;
+              in
+              [
+                (
+                  if (mode != null) then
+                    {
+                      key = "mode";
+                      val = mode;
+                    }
+                  else
+                    ""
+                )
+                (
+                  if (check != null) then
+                    {
+                      key = "check";
+                      val = check;
+                    }
+                  else
+                    ""
+                )
+                (
+                  if (migratable != null) then
+                    {
+                      key = "migratable";
+                      val = migratable;
+                    }
+                  else
+                    ""
+                )
+              ];
+            closing = false;
+          };
+
+        features =
+          vm_name:
+          mkTag {
+            name = "features";
+            children =
+              let
+                acpi =
+                  vm_name:
+                  mkTag {
+                    name = "acpi";
+                    closing = false;
+                  };
+                apic =
+                  vm_name:
+                  mkTag {
+                    name = "apic";
+                    closing = false;
+                  };
+                vmport =
+                  vm_name:
+                  mkTag {
+                    name = "vmport";
+                    args = [
+                      {
+                        key = "state";
+                        val = vm.${vm_name}.features.vmport.state;
+                      }
+                    ];
+                    closing = false;
+                  };
+              in
+              [
+                (if (vm.${vm_name}.features.acpi != false) then (acpi vm_name) else "")
+                (if (vm.${vm_name}.features.apic != false) then (apic vm_name) else "")
+                (if (vm.${vm_name}.features.vmport != null) then (vmport vm_name) else "")
+              ];
+          };
+
+        os =
+          vm_name:
+          mkTag {
+            name = "os";
+            children =
+              let
+                os_type =
+                  vm_name:
+                  mkTag {
+                    name = "type";
+                    args = [
+                      {
+                        key = "arch";
+                        val = vm.${vm_name}.os.type.arch;
+                      }
+                      {
+                        key = "machine";
+                        val = vm.${vm_name}.os.type.machine;
+                      }
+                    ];
+                    value = vm.${vm_name}.os.type.value;
+                  };
+                os_loader =
+                  vm_name:
+                  mkTag {
+                    name = "loader";
+                    args = [
+                      {
+                        key = "readonly";
+                        val = vm.${vm_name}.os.loader.readonly;
+                      }
+                      {
+                        key = "pflash";
+                        val = vm.${vm_name}.os.loader.type;
+                      }
+                    ];
+                    value = vm.${vm_name}.os.loader.value;
+                  };
+                os_nvram =
+                  vm_name:
+                  mkTag {
+                    name = "nvram";
+                    value = vm.${vm_name}.os.nvram.value;
+                  };
+                os_boot =
+                  vm_name:
+                  mkTag {
+                    name = "type";
+                    args = [
+                      {
+                        key = "dev";
+                        val = vm.${vm_name}.os.boot.dev;
+                      }
+                    ];
+                    closing = false;
+                  };
+              in
+              [
+                (os_type vm_name)
+                (os_loader vm_name)
+                (os_nvram vm_name)
+                (os_boot vm_name)
+              ];
           };
 
         resource =
@@ -473,6 +1427,12 @@ in
               (os vm_name)
               (features vm_name)
               (cpu vm_name)
+              (clock vm_name)
+              (on_poweroff vm_name)
+              (on_reboot vm_name)
+              (on_crash vm_name)
+              (pm vm_name)
+              (devices vm_name)
             ];
           };
       in