about summary refs log tree commit diff
path: root/nixos/modules
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules')
-rw-r--r--nixos/modules/installer/cd-dvd/iso-image.nix2
-rw-r--r--nixos/modules/installer/netboot/netboot.nix2
-rw-r--r--nixos/modules/installer/tools/nix-fallback-paths.nix11
-rw-r--r--nixos/modules/module-list.nix2
-rw-r--r--nixos/modules/programs/cdemu.nix2
-rw-r--r--nixos/modules/programs/pulseview.nix28
-rw-r--r--nixos/modules/programs/yubikey-touch-detector.nix48
-rw-r--r--nixos/modules/services/desktop-managers/plasma6.nix4
-rw-r--r--nixos/modules/services/matrix/hookshot.nix127
-rw-r--r--nixos/modules/services/misc/mediatomb.nix3
-rw-r--r--nixos/modules/services/networking/wstunnel.nix18
-rw-r--r--nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix42
-rw-r--r--nixos/modules/virtualisation/amazon-init.nix5
-rwxr-xr-xnixos/modules/virtualisation/xen-boot-builder.sh165
-rw-r--r--nixos/modules/virtualisation/xen-dom0.nix1188
15 files changed, 1245 insertions, 402 deletions
diff --git a/nixos/modules/installer/cd-dvd/iso-image.nix b/nixos/modules/installer/cd-dvd/iso-image.nix
index a0bca949c6a2..541bc5b0189f 100644
--- a/nixos/modules/installer/cd-dvd/iso-image.nix
+++ b/nixos/modules/installer/cd-dvd/iso-image.nix
@@ -722,7 +722,7 @@ in
     "/nix/.ro-store" = lib.mkImageMediaOverride
       { fsType = "squashfs";
         device = "/iso/nix-store.squashfs";
-        options = [ "loop" "threads=multi" ];
+        options = [ "loop" ] ++ lib.optional (config.boot.kernelPackages.kernel.kernelAtLeast "6.2") "threads=multi";
         neededForBoot = true;
       };
 
diff --git a/nixos/modules/installer/netboot/netboot.nix b/nixos/modules/installer/netboot/netboot.nix
index 0e4f456dce49..167c2b521893 100644
--- a/nixos/modules/installer/netboot/netboot.nix
+++ b/nixos/modules/installer/netboot/netboot.nix
@@ -47,7 +47,7 @@ with lib;
     fileSystems."/nix/.ro-store" = mkImageMediaOverride
       { fsType = "squashfs";
         device = "../nix-store.squashfs";
-        options = [ "loop" "threads=multi" ];
+        options = [ "loop" ] ++ lib.optional (config.boot.kernelPackages.kernel.kernelAtLeast "6.2") "threads=multi";
         neededForBoot = true;
       };
 
diff --git a/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixos/modules/installer/tools/nix-fallback-paths.nix
index f9ea7eb39597..10b2b5220e75 100644
--- a/nixos/modules/installer/tools/nix-fallback-paths.nix
+++ b/nixos/modules/installer/tools/nix-fallback-paths.nix
@@ -1,7 +1,8 @@
 {
-  x86_64-linux = "/nix/store/f409bhlpp0xkzvdz95qr2yvfjfi8r9jc-nix-2.18.5";
-  i686-linux = "/nix/store/ra39jzrxq3bcpf55aahwv5037akvylf5-nix-2.18.5";
-  aarch64-linux = "/nix/store/xiw8a4jbnw18svgdb04hyqzg5bsjspqf-nix-2.18.5";
-  x86_64-darwin = "/nix/store/k2gzx7i90x3h2c8g6xdi1jkwbl6ic895-nix-2.18.5";
-  aarch64-darwin = "/nix/store/rqwymbndaqxma6p8s5brcl9k32n5xx54-nix-2.18.5";
+  x86_64-linux = "/nix/store/fmfy9zigxns8f1wfb4v2arf1jmfdjpjc-nix-2.24.6";
+  i686-linux = "/nix/store/bl95c09pcihf2fdqpzjyjk4bdq0bsizm-nix-2.24.6";
+  aarch64-linux = "/nix/store/2sbzgmvas19iq3nhg8xbnd8k0khahk34-nix-2.24.6";
+  riscv64-linux = "/nix/store/7yy1x9sx83wm77mjawd953d6a6wb669q-nix-riscv64-unknown-linux-gnu-2.24.6";
+  x86_64-darwin = "/nix/store/vs6bbxkwxqr828q8rj1xlbmsbnx1ry6z-nix-2.24.6";
+  aarch64-darwin = "/nix/store/d88r5b1qv1fvz2j9qndz8sr31mqgz45x-nix-2.24.6";
 }
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 006cdeedcaf6..5068a04c8d34 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -265,6 +265,7 @@
   ./programs/pqos-wrapper.nix
   ./programs/projecteur.nix
   ./programs/proxychains.nix
+  ./programs/pulseview.nix
   ./programs/qdmr.nix
   ./programs/qgroundcontrol.nix
   ./programs/qt5ct.nix
@@ -698,6 +699,7 @@
   ./services/matrix/conduit.nix
   ./services/matrix/dendrite.nix
   ./services/matrix/hebbot.nix
+  ./services/matrix/hookshot.nix
   ./services/matrix/maubot.nix
   ./services/matrix/mautrix-facebook.nix
   ./services/matrix/mautrix-meta.nix
diff --git a/nixos/modules/programs/cdemu.nix b/nixos/modules/programs/cdemu.nix
index 1aac28af1d2c..cdfab3ee9f39 100644
--- a/nixos/modules/programs/cdemu.nix
+++ b/nixos/modules/programs/cdemu.nix
@@ -60,7 +60,7 @@ in {
     systemd.user.services.cdemu-daemon.serviceConfig = {
       Type = "dbus";
       BusName = "net.sf.cdemu.CDEmuDaemon";
-      ExecStart = "${pkgs.cdemu-daemon}/bin/cdemu-daemon --config-file \"%h/.config/cdemu-daemon\"";
+      ExecStart = "${lib.getExe pkgs.cdemu-daemon} --config-file \"%h/.config/cdemu-daemon\"";
       Restart = "no";
     };
 
diff --git a/nixos/modules/programs/pulseview.nix b/nixos/modules/programs/pulseview.nix
new file mode 100644
index 000000000000..f8db5a467d53
--- /dev/null
+++ b/nixos/modules/programs/pulseview.nix
@@ -0,0 +1,28 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.programs.pulseview;
+in
+{
+  options.programs.pulseview = {
+    enable = lib.mkEnableOption "pulseview, a sigrok GUI";
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [
+      pkgs.pulseview
+    ];
+
+    services.udev = {
+      packages = [
+        # Pulseview needs some udev rules provided by libsigrok to access devices
+        pkgs.libsigrok
+      ];
+    };
+  };
+}
diff --git a/nixos/modules/programs/yubikey-touch-detector.nix b/nixos/modules/programs/yubikey-touch-detector.nix
index 9a0d107f73c9..42beabbc4e94 100644
--- a/nixos/modules/programs/yubikey-touch-detector.nix
+++ b/nixos/modules/programs/yubikey-touch-detector.nix
@@ -1,9 +1,44 @@
-{ config, lib, pkgs, ... }:
-let cfg = config.programs.yubikey-touch-detector;
-in {
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+let
+  inherit (lib) types;
+  cfg = config.programs.yubikey-touch-detector;
+in
+{
   options = {
     programs.yubikey-touch-detector = {
+
       enable = lib.mkEnableOption "yubikey-touch-detector";
+
+      libnotify = lib.mkOption {
+        # This used to be true previously and using libnotify would be a sane default.
+        default = true;
+        type = types.bool;
+        description = ''
+          If set to true, yubikey-touch-detctor will send notifications using libnotify
+        '';
+      };
+
+      unixSocket = lib.mkOption {
+        default = true;
+        type = types.bool;
+        description = ''
+          If set to true, yubikey-touch-detector will send notifications to a unix socket
+        '';
+      };
+
+      verbose = lib.mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Enables verbose logging
+        '';
+      };
+
     };
   };
 
@@ -12,6 +47,13 @@ in {
 
     systemd.user.services.yubikey-touch-detector = {
       path = [ pkgs.gnupg ];
+
+      environment = {
+        YUBIKEY_TOUCH_DETECTOR_LIBNOTIFY = builtins.toString cfg.libnotify;
+        YUBIKEY_TOUCH_DETECTOR_NOSOCKET = builtins.toString (!cfg.unixSocket);
+        YUBIKEY_TOUCH_DETECTOR_VERBOSE = builtins.toString cfg.verbose;
+      };
+
       wantedBy = [ "graphical-session.target" ];
     };
     systemd.user.sockets.yubikey-touch-detector = {
diff --git a/nixos/modules/services/desktop-managers/plasma6.nix b/nixos/modules/services/desktop-managers/plasma6.nix
index c165c0b47eed..2df28c80e8a1 100644
--- a/nixos/modules/services/desktop-managers/plasma6.nix
+++ b/nixos/modules/services/desktop-managers/plasma6.nix
@@ -147,6 +147,10 @@ in {
         spectacle
         ffmpegthumbs
         krdp
+      ] ++ lib.optionals config.services.flatpak.enable [
+        # Since PackageKit Nix support is not there yet,
+        # only install discover if flatpak is enabled.
+        discover
       ];
     in
       requiredPackages
diff --git a/nixos/modules/services/matrix/hookshot.nix b/nixos/modules/services/matrix/hookshot.nix
new file mode 100644
index 000000000000..d6329e91459e
--- /dev/null
+++ b/nixos/modules/services/matrix/hookshot.nix
@@ -0,0 +1,127 @@
+{
+  config,
+  pkgs,
+  lib,
+  ...
+}:
+let
+  cfg = config.services.matrix-hookshot;
+  settingsFormat = pkgs.formats.yaml { };
+  configFile = settingsFormat.generate "matrix-hookshot-config.yml" cfg.settings;
+in
+{
+  options = {
+    services.matrix-hookshot = {
+      enable = lib.mkEnableOption "matrix-hookshot, a bridge between Matrix and project management services";
+
+      package = lib.mkPackageOption pkgs "matrix-hookshot" { };
+
+      registrationFile = lib.mkOption {
+        type = lib.types.path;
+        description = ''
+          Appservice registration file.
+          As it contains secret tokens, you may not want to add this to the publicly readable Nix store.
+        '';
+        example = lib.literalExpression ''
+          pkgs.writeText "matrix-hookshot-registration" \'\'
+            id: matrix-hookshot
+            as_token: aaaaaaaaaa
+            hs_token: aaaaaaaaaa
+            namespaces:
+              rooms: []
+              users:
+                - regex: "@_webhooks_.*:foobar"
+                  exclusive: true
+
+            sender_localpart: hookshot
+            url: "http://localhost:9993"
+            rate_limited: false
+            \'\'
+        '';
+      };
+
+      settings = lib.mkOption {
+        description = ''
+          {file}`config.yml` configuration as a Nix attribute set.
+
+          For details please see the [documentation](https://matrix-org.github.io/matrix-hookshot/latest/setup/sample-configuration.html).
+        '';
+        example = {
+          bridge = {
+            domain = "example.com";
+            url = "http://localhost:8008";
+            mediaUrl = "https://example.com";
+            port = 9993;
+            bindAddress = "127.0.0.1";
+          };
+          listeners = [
+            {
+              port = 9000;
+              bindAddress = "0.0.0.0";
+              resources = [ "webhooks" ];
+            }
+            {
+              port = 9001;
+              bindAddress = "localhost";
+              resources = [
+                "metrics"
+                "provisioning"
+              ];
+            }
+          ];
+        };
+        default = { };
+        type = lib.types.submodule {
+          freeformType = settingsFormat.type;
+          options = {
+            passFile = lib.mkOption {
+              type = lib.types.path;
+              default = "/var/lib/matrix-hookshot/passkey.pem";
+              description = ''
+                A passkey used to encrypt tokens stored inside the bridge.
+                File will be generated if not found.
+              '';
+            };
+          };
+        };
+      };
+
+      serviceDependencies = lib.mkOption {
+        type = with lib.types; listOf str;
+        default = lib.optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit;
+        defaultText = lib.literalExpression ''
+          lib.optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit
+        '';
+        description = ''
+          List of Systemd services to require and wait for when starting the application service,
+          such as the Matrix homeserver if it's running on the same host.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.matrix-hookshot = {
+      description = "a bridge between Matrix and multiple project management services";
+
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
+      after = [ "network-online.target" ] ++ cfg.serviceDependencies;
+
+      preStart = ''
+        if [ ! -f '${cfg.settings.passFile}' ]; then
+          mkdir -p $(dirname '${cfg.settings.passFile}')
+          ${pkgs.openssl}/bin/openssl genpkey -out '${cfg.settings.passFile}' -outform PEM -algorithm RSA -pkeyopt rsa_keygen_bits:4096
+        fi
+      '';
+
+      serviceConfig = {
+        Type = "simple";
+        Restart = "always";
+        ExecStart = "${cfg.package}/bin/matrix-hookshot ${configFile} ${cfg.registrationFile}";
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ flandweber ];
+}
diff --git a/nixos/modules/services/misc/mediatomb.nix b/nixos/modules/services/misc/mediatomb.nix
index ce4ecbeb7279..ae9d1fa31be2 100644
--- a/nixos/modules/services/misc/mediatomb.nix
+++ b/nixos/modules/services/misc/mediatomb.nix
@@ -36,7 +36,6 @@ let
         <transcode mimetype="video/x-flv" using="vlcmpeg" />
         <transcode mimetype="application/ogg" using="vlcmpeg" />
         <transcode mimetype="audio/ogg" using="ogg2mp3" />
-        <transcode mimetype="audio/x-flac" using="oggflac2raw"/>
       </mimetype-profile-mappings>
       <profiles>
         <profile name="ogg2mp3" enabled="no" type="external">
@@ -52,7 +51,7 @@ let
           <accept-url>yes</accept-url>
           <first-resource>yes</first-resource>
           <accept-ogg-theora>yes</accept-ogg-theora>
-          <agent command="${libsForQt5.vlc}/bin/vlc"
+          <agent command="${lib.getExe vlc}"
             arguments="-I dummy %in --sout #transcode{venc=ffmpeg,vcodec=mp2v,vb=4096,fps=25,aenc=ffmpeg,acodec=mpga,ab=192,samplerate=44100,channels=2}:standard{access=file,mux=ps,dst=%out} vlc:quit" />
           <buffer size="14400000" chunk-size="512000" fill-size="120000" />
         </profile>
diff --git a/nixos/modules/services/networking/wstunnel.nix b/nixos/modules/services/networking/wstunnel.nix
index c05e00809357..bf56858a190f 100644
--- a/nixos/modules/services/networking/wstunnel.nix
+++ b/nixos/modules/services/networking/wstunnel.nix
@@ -318,10 +318,21 @@ let
               lib.cli.toGNUCommandLineShell { } (
                 lib.recursiveUpdate {
                   restrict-to = map hostPortToString restrictTo;
-                  tls-certificate =
-                    if useACMEHost != null then "${certConfig.directory}/fullchain.pem" else "${tlsCertificate}";
-                  tls-private-key = if useACMEHost != null then "${certConfig.directory}/key.pem" else "${tlsKey}";
                   websocket-ping-frequency-sec = websocketPingInterval;
+                  tls-certificate =
+                    if !enableHTTPS then
+                      null
+                    else if useACMEHost != null then
+                      "${certConfig.directory}/fullchain.pem"
+                    else
+                      "${tlsCertificate}";
+                  tls-private-key =
+                    if !enableHTTPS then
+                      null
+                    else if useACMEHost != null then
+                      "${certConfig.directory}/key.pem"
+                    else
+                      "${tlsKey}";
                 } extraArgs
               )
             } \
@@ -475,6 +486,7 @@ in
 
   meta.maintainers = with lib.maintainers; [
     alyaeanyx
+    raylas
     rvdp
     neverbehave
   ];
diff --git a/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix b/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
index 2b75707ad99e..797f0d81f14c 100644
--- a/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
+++ b/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
@@ -54,6 +54,30 @@ in
         '';
       };
 
+      mirroredBoots = mkOption {
+        default = [ { path = "/boot"; } ];
+        example = [
+          { path = "/boot1"; }
+          { path = "/boot2"; }
+        ];
+        description = ''
+          Mirror the boot configuration to multiple paths.
+        '';
+
+        type = with types; listOf (submodule {
+          options = {
+            path = mkOption {
+              example = "/boot1";
+              type = types.str;
+              description = ''
+                The path to the boot directory where the extlinux-compatible
+                configuration files will be written.
+              '';
+            };
+          };
+        });
+      };
+
       populateCmd = mkOption {
         type = types.str;
         readOnly = true;
@@ -72,11 +96,27 @@ in
     builderArgs = "-g ${toString cfg.configurationLimit} -t ${timeoutStr}"
       + lib.optionalString (dtCfg.name != null) " -n ${dtCfg.name}"
       + lib.optionalString (!cfg.useGenerationDeviceTree) " -r";
+    installBootLoader = pkgs.writeScript "install-extlinux-conf.sh" (''
+      #!${pkgs.runtimeShell}
+      set -e
+    '' + flip concatMapStrings cfg.mirroredBoots (args: ''
+      ${builder} ${builderArgs} -d '${args.path}' -c "$@"
+    ''));
   in
     mkIf cfg.enable {
-      system.build.installBootLoader = "${builder} ${builderArgs} -c";
+      system.build.installBootLoader = installBootLoader;
       system.boot.loader.id = "generic-extlinux-compatible";
 
       boot.loader.generic-extlinux-compatible.populateCmd = "${populateBuilder} ${builderArgs}";
+
+      assertions = [
+        {
+          assertion = cfg.mirroredBoots != [ ];
+          message = ''
+            You must not remove all elements from option 'boot.loader.generic-extlinux-compatible.mirroredBoots',
+            otherwise the system will not be bootable.
+          '';
+        }
+      ];
     };
 }
diff --git a/nixos/modules/virtualisation/amazon-init.nix b/nixos/modules/virtualisation/amazon-init.nix
index 612f6c5bc765..1a65686af37c 100644
--- a/nixos/modules/virtualisation/amazon-init.nix
+++ b/nixos/modules/virtualisation/amazon-init.nix
@@ -75,6 +75,11 @@ in {
       after = [ "multi-user.target" ];
       requires = [ "network-online.target" ];
 
+      path = [
+        "/run/wrappers"
+        "/run/current-system/sw"
+      ];
+
       restartIfChanged = false;
       unitConfig.X-StopOnRemoval = false;
 
diff --git a/nixos/modules/virtualisation/xen-boot-builder.sh b/nixos/modules/virtualisation/xen-boot-builder.sh
new file mode 100755
index 000000000000..13e1a4e20243
--- /dev/null
+++ b/nixos/modules/virtualisation/xen-boot-builder.sh
@@ -0,0 +1,165 @@
+# This script is called by ./xen-dom0.nix to create the Xen boot entries.
+# shellcheck shell=bash
+
+# Handle input argument and exit if the flag is invalid. See virtualisation.xen.efi.bootBuilderVerbosity below.
+[[ $# -ne 1 ]] && echo -e "\e[1;31merror:\e[0m xenBootBuilder must be called with exactly one verbosity argument. See the \e[1;34mvirtualisation.xen.efi.bootBuilderVerbosity\e[0m option." && exit 1
+case "$1" in
+    "quiet") true ;;
+    "default" | "info") echo -n "Installing Xen Hypervisor boot entries..." ;;
+    "debug") echo -e "\e[1;34mxenBootBuilder:\e[0m called with the '$1' flag" ;;
+    *)
+        echo -e "\e[1;31merror:\e[0m xenBootBuilder was called with an invalid argument. See the \e[1;34mvirtualisation.xen.efi.bootBuilderVerbosity\e[0m option."
+        exit 2
+        ;;
+esac
+
+# Get the current Xen generations and store them in an array. This will be used
+# for displaying the diff later, if xenBootBuilder was called with `info`.
+# We also delete the current Xen entries here, as they'll be rebuilt later if
+# the corresponding NixOS generation still exists.
+mapfile -t preGenerations < <(find "$efiMountPoint"/loader/entries -type f -name 'xen-*.conf' | sort -V | sed 's_/loader/entries/nixos_/loader/entries/xen_g;s_^.*/xen_xen_g;s_.conf$__g')
+if [ "$1" = "debug" ]; then
+    if ((${#preGenerations[@]} == 0)); then
+        echo -e "\e[1;34mxenBootBuilder:\e[0m no previous Xen entries."
+    else
+        echo -e "\e[1;34mxenBootBuilder:\e[0m deleting the following stale xen entries:" && for debugGen in "${preGenerations[@]}"; do echo "                - $debugGen"; done
+    fi
+fi
+
+# Cleanup all Xen entries.
+rm -f "$efiMountPoint"/{loader/entries/xen-*.conf,efi/nixos/xen-*.efi}
+
+# Main array for storing which generations exist in $efiMountPoint after
+# systemd-boot-builder.py builds the main entries.
+mapfile -t gens < <(find "$efiMountPoint"/loader/entries -type f -name 'nixos-*.conf' | sort -V)
+[ "$1" = "debug" ] && echo -e "\e[1;34mxenBootBuilder:\e[0m found the following NixOS boot entries:" && for debugGen in "${gens[@]}"; do echo "                - $debugGen"; done
+
+# This is the main loop that installs the Xen entries.
+for gen in "${gens[@]}"; do
+
+    # We discover the path to Bootspec through the init attribute in the entries,
+    # as it is equivalent to $toplevel/init.
+    bootspecFile="$(sed -nr 's/^options init=(.*)\/init.*$/\1/p' "$gen")/boot.json"
+    [ "$1" = "debug" ] && echo -e "\e[1;34mxenBootBuilder:\e[0m processing bootspec file $bootspecFile"
+
+    # We do nothing if the Bootspec for the current $gen does not contain the Xen
+    # extension, which is added as a configuration attribute below.
+    if grep -sq '"org.xenproject.bootspec.v1"' "$bootspecFile"; then
+        [ "$1" = "debug" ] && echo -e "                \e[1;32msuccess:\e[0m found Xen entries in $gen."
+
+        # TODO: Support DeviceTree booting. Xen has some special handling for DeviceTree
+        # attributes, which will need to be translated in a boot script similar to this
+        # one. Having a DeviceTree entry is rare, and it is not always required for a
+        # successful boot, so we don't fail here, only warn with `debug`.
+        if grep -sq '"devicetree"' "$bootspecFile"; then
+            echo -e "\n\e[1;33mwarning:\e[0m $gen has a \e[1;34morg.nixos.systemd-boot.devicetree\e[0m Bootspec entry. Xen currently does not support DeviceTree, so this value will be ignored in the Xen boot entries, which may cause them to \e[1;31mfail to boot\e[0m."
+        else
+            [ "$1" = "debug" ] && echo -e "\e[1;34mxenBootBuilder:\e[0m no DeviceTree entries found in $gen."
+        fi
+
+        # Prepare required attributes for `xen.cfg/xen.conf`. It inherits the name of
+        # the corresponding nixos generation, substituting `nixos` with `xen`:
+        # `xen-$profile-generation-$number-specialisation-$specialisation.{cfg,conf}`
+        xenGen=$(echo "$gen" | sed 's_/loader/entries/nixos_/loader/entries/xen_g;s_^.*/xen_xen_g;s_.conf$__g')
+        bootParams=$(jq -re '."org.xenproject.bootspec.v1".xenParams | join(" ")' "$bootspecFile")
+        kernel=$(jq -re '."org.nixos.bootspec.v1".kernel | sub("^/nix/store/"; "") | sub("/bzImage"; "-bzImage.efi")' "$bootspecFile")
+        kernelParams=$(jq -re '."org.nixos.bootspec.v1".kernelParams | join(" ")' "$bootspecFile")
+        initrd=$(jq -re '."org.nixos.bootspec.v1".initrd | sub("^/nix/store/"; "") | sub("/initrd"; "-initrd.efi")' "$bootspecFile")
+        init=$(jq -re '."org.nixos.bootspec.v1".init' "$bootspecFile")
+        title=$(sed -nr 's/^title (.*)$/\1/p' "$gen")
+        version=$(sed -nr 's/^version (.*)$/\1/p' "$gen")
+        machineID=$(sed -nr 's/^machine-id (.*)$/\1/p' "$gen")
+        sortKey=$(sed -nr 's/^sort-key (.*)$/\1/p' "$gen")
+
+        # Write `xen.cfg` to a temporary location prior to UKI creation.
+        tmpCfg=$(mktemp)
+        [ "$1" = "debug" ] && echo -ne "\e[1;34mxenBootBuilder:\e[0m writing $xenGen.cfg to temporary file..."
+        cat > "$tmpCfg" << EOF
+[global]
+default=xen
+
+[xen]
+options=$bootParams
+kernel=$kernel init=$init $kernelParams
+ramdisk=$initrd
+EOF
+        [ "$1" = "debug" ] && echo -e "done."
+
+        # Create Xen UKI for $generation. Most of this is lifted from
+        # https://xenbits.xenproject.org/docs/unstable/misc/efi.html.
+        [ "$1" = "debug" ] && echo -e "\e[1;34mxenBootBuilder:\e[0m making Xen UKI..."
+        xenEfi=$(jq -re '."org.xenproject.bootspec.v1".xen' "$bootspecFile")
+        padding=$(objdump --header --section=".pad" "$xenEfi" | awk '/\.pad/ { printf("0x%016x\n", strtonum("0x"$3) + strtonum("0x"$4))};')
+        [ "$1" = "debug" ] && echo "               - padding: $padding"
+        objcopy \
+            --add-section .config="$tmpCfg" \
+            --change-section-vma .config="$padding" \
+            "$xenEfi" \
+            "$efiMountPoint"/EFI/nixos/"$xenGen".efi
+        [ "$1" = "debug" ] && echo -e "               - \e[1;32msuccessfully built\e[0m $xenGen.efi"
+        rm -f "$tmpCfg"
+
+        # Write `xen.conf`.
+        [ "$1" = "debug" ] && echo -ne "\e[1;34mxenBootBuilder:\e[0m writing $xenGen.conf to EFI System Partition..."
+        cat > "$efiMountPoint"/loader/entries/"$xenGen".conf << EOF
+title $title (with Xen Hypervisor)
+version $version
+efi /EFI/nixos/$xenGen.efi
+machine-id $machineID
+sort-key $sortKey
+EOF
+        [ "$1" = "debug" ] && echo -e "done."
+
+    # Sometimes, garbage collection weirdness causes a generation to still exist in
+    # the loader entries, but its Bootspec file was deleted. We consider such a
+    # generation to be invalid, but we don't write extra code to handle this
+    # situation, as supressing grep's error messages above is quite enough, and the
+    # error message below is still technically correct, as no Xen can be found in
+    # something that does not exist.
+    else
+        [ "$1" = "debug" ] && echo -e "                \e[1;33mwarning:\e[0m \e[1;31mno Xen found\e[0m in $gen."
+    fi
+done
+
+# Counterpart to the preGenerations array above. We use it to diff the
+# generations created/deleted when callled with the `info` argument.
+mapfile -t postGenerations < <(find "$efiMountPoint"/loader/entries -type f -name 'xen-*.conf' | sort -V | sed 's_/loader/entries/nixos_/loader/entries/xen_g;s_^.*/xen_xen_g;s_.conf$__g')
+
+# In the event the script does nothing, guide the user to debug, as it'll only
+# ever run when Xen is enabled, and it makes no sense to enable Xen and not have
+# any hypervisor boot entries.
+if ((${#postGenerations[@]} == 0)); then
+    case "$1" in
+        "default" | "info") echo "none found." && echo -e "If you believe this is an error, set the \e[1;34mvirtualisation.xen.efi.bootBuilderVerbosity\e[0m option to \e[1;34m\"debug\"\e[0m and rebuild to print debug logs." ;;
+        "debug") echo -e "\e[1;34mxenBootBuilder:\e[0m wrote \e[1;31mno generations\e[0m. Most likely, there were no generations with a valid \e[1;34morg.xenproject.bootspec.v1\e[0m entry." ;;
+    esac
+
+# If the script is successful, change the default boot, say "done.", write a
+# diff, or print the total files written, depending on the argument this script
+# was called with. We use some dumb dependencies here, like `diff` or `bat` for
+# colourisation, but they're only included  with the `info` argument.
+#
+# It's also fine to change the default here, as this runs after the
+# `systemd-boot-builder.py` script, which overwrites the file, and this script
+# does not run after an user disables the Xen module.
+else
+    sed --in-place 's/^default nixos-/default xen-/g' "$efiMountPoint"/loader/loader.conf
+    case "$1" in
+        "default" | "info") echo "done." ;;
+        "debug") echo -e "\e[1;34mxenBootBuilder:\e[0m \e[1;32msuccessfully wrote\e[0m the following generations:" && for debugGen in "${postGenerations[@]}"; do echo "                - $debugGen"; done ;;
+    esac
+    if [ "$1" = "info" ]; then
+        if [[ ${#preGenerations[@]} == "${#postGenerations[@]}" ]]; then
+            echo -e "\e[1;33mNo Change:\e[0m Xen Hypervisor boot entries were refreshed, but their contents are identical."
+        else
+            echo -e "\e[1;32mSuccess:\e[0m Changed the following boot entries:"
+            # We briefly unset errexit and pipefail here, as GNU diff has no option to not fail when files differ.
+            set +o errexit
+            set +o pipefail
+            diff <(echo "${preGenerations[*]}" | tr ' ' '\n') <(echo "${postGenerations[*]}" | tr ' ' '\n') -U 0 | grep --invert-match --extended-regexp '^(@@|---|\+\+\+).*' | sed '1{/^-$/d}' | bat --language diff --theme ansi --paging=never --plain
+            true
+            set -o errexit
+            set -o pipefail
+        fi
+    fi
+fi
diff --git a/nixos/modules/virtualisation/xen-dom0.nix b/nixos/modules/virtualisation/xen-dom0.nix
index 2fb8c6cd4566..db0971065900 100644
--- a/nixos/modules/virtualisation/xen-dom0.nix
+++ b/nixos/modules/virtualisation/xen-dom0.nix
@@ -1,452 +1,870 @@
 # Xen hypervisor (Dom0) support.
 
-{ config, lib, pkgs, ... }:
-
-with lib;
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
 
 let
   cfg = config.virtualisation.xen;
+
+  xenBootBuilder = pkgs.writeShellApplication {
+    name = "xenBootBuilder";
+    runtimeInputs =
+      (with pkgs; [
+        binutils
+        coreutils
+        findutils
+        gawk
+        gnugrep
+        gnused
+        jq
+      ])
+      ++ lib.lists.optionals (cfg.efi.bootBuilderVerbosity == "info") (
+        with pkgs;
+        [
+          bat
+          diffutils
+        ]
+      );
+    runtimeEnv = {
+      efiMountPoint = config.boot.loader.efi.efiSysMountPoint;
+    };
+    text = builtins.readFile ./xen-boot-builder.sh;
+  };
 in
 
 {
-  imports = [
-    (mkRemovedOptionModule [ "virtualisation" "xen" "qemu" ] "You don't need this option anymore, it will work without it.")
-    (mkRenamedOptionModule [ "virtualisation" "xen" "qemu-package" ] [ "virtualisation" "xen" "package-qemu" ])
+  imports = with lib.modules; [
+    (mkRemovedOptionModule
+      [
+        "virtualisation"
+        "xen"
+        "bridge"
+        "name"
+      ]
+      "The Xen Network Bridge options are currently unavailable. Please set up your own bridge manually."
+    )
+    (mkRemovedOptionModule
+      [
+        "virtualisation"
+        "xen"
+        "bridge"
+        "address"
+      ]
+      "The Xen Network Bridge options are currently unavailable. Please set up your own bridge manually."
+    )
+    (mkRemovedOptionModule
+      [
+        "virtualisation"
+        "xen"
+        "bridge"
+        "prefixLength"
+      ]
+      "The Xen Network Bridge options are currently unavailable. Please set up your own bridge manually."
+    )
+    (mkRemovedOptionModule
+      [
+        "virtualisation"
+        "xen"
+        "bridge"
+        "forwardDns"
+      ]
+      "The Xen Network Bridge options are currently unavailable. Please set up your own bridge manually."
+    )
+    (mkRenamedOptionModule
+      [
+        "virtualisation"
+        "xen"
+        "qemu-package"
+      ]
+      [
+        "virtualisation"
+        "xen"
+        "qemu"
+        "package"
+      ]
+    )
+    (mkRenamedOptionModule
+      [
+        "virtualisation"
+        "xen"
+        "package-qemu"
+      ]
+      [
+        "virtualisation"
+        "xen"
+        "qemu"
+        "package"
+      ]
+    )
+    (mkRenamedOptionModule
+      [
+        "virtualisation"
+        "xen"
+        "stored"
+      ]
+      [
+        "virtualisation"
+        "xen"
+        "store"
+        "path"
+      ]
+    )
   ];
 
-  ###### interface
+  ## Interface ##
 
-  options = {
+  options.virtualisation.xen = {
 
-    virtualisation.xen.enable =
-      mkOption {
-        default = false;
-        type = types.bool;
-        description = ''
-            Setting this option enables the Xen hypervisor, a
-            virtualisation technology that allows multiple virtual
-            machines, known as *domains*, to run
-            concurrently on the physical machine.  NixOS runs as the
-            privileged *Domain 0*.  This option
-            requires a reboot to take effect.
-          '';
-      };
+    enable = lib.options.mkEnableOption "the Xen Hypervisor, a virtualisation technology defined as a *type-1 hypervisor*, which allows multiple virtual machines, known as *domains*, to run concurrently on the physical machine. NixOS runs as the privileged *Domain 0*. This option requires a reboot into a Xen kernel to take effect";
 
-    virtualisation.xen.package = mkOption {
-      type = types.package;
-      defaultText = literalExpression "pkgs.xen";
-      example = literalExpression "pkgs.xen-light";
-      description = ''
-        The package used for Xen binary.
-      '';
-      relatedPackages = [ "xen" "xen-light" ];
+    debug = lib.options.mkEnableOption "Xen debug features for Domain 0. This option enables some hidden debugging tests and features, and should not be used in production";
+
+    trace = lib.options.mkOption {
+      type = lib.types.bool;
+      default = cfg.debug;
+      defaultText = lib.options.literalExpression "false";
+      example = true;
+      description = "Whether to enable Xen debug tracing and logging for Domain 0.";
     };
 
-    virtualisation.xen.package-qemu = mkOption {
-      type = types.package;
-      defaultText = literalExpression "pkgs.xen";
-      example = literalExpression "pkgs.qemu_xen-light";
+    package = lib.options.mkOption {
+      type = lib.types.package;
+      default = pkgs.xen;
+      defaultText = lib.options.literalExpression "pkgs.xen";
+      example = lib.options.literalExpression "pkgs.xen-slim";
       description = ''
-        The package with qemu binaries for dom0 qemu and xendomains.
+        The package used for Xen Hypervisor.
       '';
-      relatedPackages = [ "xen"
-                          { name = "qemu_xen-light"; comment = "For use with pkgs.xen-light."; }
-                        ];
+      relatedPackages = [
+        "xen"
+        "xen-slim"
+      ];
     };
 
-    virtualisation.xen.bootParams =
-      mkOption {
-        default = [];
-        type = types.listOf types.str;
-        description =
-          ''
-            Parameters passed to the Xen hypervisor at boot time.
-          '';
+    qemu = {
+      package = lib.options.mkOption {
+        type = lib.types.package;
+        default = pkgs.xen;
+        defaultText = lib.options.literalExpression "pkgs.xen";
+        example = lib.options.literalExpression "pkgs.qemu_xen";
+        description = ''
+          The package with QEMU binaries that runs in Domain 0
+          and virtualises the unprivileged domains.
+        '';
+        relatedPackages = [
+          "xen"
+          {
+            name = "qemu_xen";
+            comment = "For use with `pkgs.xen-slim`.";
+          }
+        ];
       };
-
-    virtualisation.xen.domain0MemorySize =
-      mkOption {
-        default = 0;
-        example = 512;
-        type = types.addCheck types.int (n: n >= 0);
-        description =
-          ''
-            Amount of memory (in MiB) allocated to Domain 0 on boot.
-            If set to 0, all memory is assigned to Domain 0.
-          '';
+      pidFile = lib.options.mkOption {
+        type = lib.types.path;
+        default = "/run/xen/qemu-dom0.pid";
+        example = "/var/run/xen/qemu-dom0.pid";
+        description = "Path to the QEMU PID file.";
       };
+    };
 
-    virtualisation.xen.bridge = {
-        name = mkOption {
-          default = "xenbr0";
-          type = types.str;
-          description = ''
-              Name of bridge the Xen domUs connect to.
-            '';
-        };
+    bootParams = lib.options.mkOption {
+      default = [ ];
+      example = ''
+        [
+          "iommu=force:true,qinval:true,debug:true"
+          "noreboot=true"
+          "vga=ask"
+        ]
+      '';
+      type = lib.types.listOf lib.types.str;
+      description = ''
+        Xen Command Line parameters passed to Domain 0 at boot time.
+        Note: these are different from `boot.kernelParams`. See
+        the [Xen documentation](https://xenbits.xenproject.org/docs/unstable/misc/xen-command-line.html) for more information.
+      '';
+    };
 
-        address = mkOption {
-          type = types.str;
-          default = "172.16.0.1";
-          description = ''
-            IPv4 address of the bridge.
-          '';
-        };
+    efi = {
+      bootBuilderVerbosity = lib.options.mkOption {
+        type = lib.types.enum [
+          "default"
+          "info"
+          "debug"
+          "quiet"
+        ];
+        default = "default";
+        example = "info";
+        description = ''
+          The EFI boot entry builder script should be called with exactly one of the following arguments in order to specify its verbosity:
 
-        prefixLength = mkOption {
-          type = types.addCheck types.int (n: n >= 0 && n <= 32);
-          default = 16;
-          description = ''
-            Subnet mask of the bridge interface, specified as the number of
-            bits in the prefix (`24`).
-            A DHCP server will provide IP addresses for the whole, remaining
-            subnet.
-          '';
-        };
+          - `quiet` supresses all messages.
 
-        forwardDns = mkOption {
-          type = types.bool;
-          default = false;
-          description = ''
-            If set to `true`, the DNS queries from the
-            hosts connected to the bridge will be forwarded to the DNS
-            servers specified in /etc/resolv.conf .
-            '';
-        };
+          - `default` adds a simple "Installing Xen Hypervisor boot entries...done." message to the script.
 
-      };
+          - `info` is the same as `default`, but it also prints a diff with information on which generations were altered.
+            - This option adds two extra dependencies to the script: `diffutils` and `bat`.
 
-    virtualisation.xen.stored =
-      mkOption {
-        type = types.path;
-        description =
-          ''
-            Xen Store daemon to use. Defaults to oxenstored of the xen package.
-          '';
+          - `debug` prints information messages for every single step of the script.
+
+          This option does not alter the actual functionality of the script, just the number of messages printed when rebuilding the system.
+        '';
       };
 
-    virtualisation.xen.domains = {
-        extraConfig = mkOption {
-          type = types.lines;
-          default = "";
-          description =
-            ''
-              Options defined here will override the defaults for xendomains.
-              The default options can be seen in the file included from
-              /etc/default/xendomains.
-            '';
-          };
+      path = lib.options.mkOption {
+        type = lib.types.path;
+        default = "${cfg.package.boot}/${cfg.package.efi}";
+        defaultText = lib.options.literalExpression "\${config.virtualisation.xen.package.boot}/\${config.virtualisation.xen.package.efi}";
+        example = lib.options.literalExpression "\${config.virtualisation.xen.package}/boot/efi/efi/nixos/xen-\${config.virtualisation.xen.package.version}.efi";
+        description = ''
+          Path to xen.efi. `pkgs.xen` is patched to install the xen.efi file
+          on `$boot/boot/xen.efi`, but an unpatched Xen build may install it
+          somewhere else, such as `$out/boot/efi/efi/nixos/xen.efi`. Unless
+          you're building your own Xen derivation, you should leave this
+          option as the default value.
+        '';
       };
+    };
 
-    virtualisation.xen.trace = mkEnableOption "Xen tracing";
+    dom0Resources = {
+      maxVCPUs = lib.options.mkOption {
+        default = 0;
+        example = 4;
+        type = lib.types.ints.unsigned;
+        description = ''
+          Amount of virtual CPU cores allocated to Domain 0 on boot.
+          If set to 0, all cores are assigned to Domain 0, and
+          unprivileged domains will compete with Domain 0 for CPU time.
+        '';
+      };
 
-  };
+      memory = lib.options.mkOption {
+        default = 0;
+        example = 512;
+        type = lib.types.ints.unsigned;
+        description = ''
+          Amount of memory (in MiB) allocated to Domain 0 on boot.
+          If set to 0, all memory is assigned to Domain 0, and
+          unprivileged domains will compete with Domain 0 for free RAM.
+        '';
+      };
 
+      maxMemory = lib.options.mkOption {
+        default = cfg.dom0Resources.memory;
+        defaultText = lib.options.literalExpression "config.virtualisation.xen.dom0Resources.memory";
+        example = 1024;
+        type = lib.types.ints.unsigned;
+        description = ''
+          Maximum amount of memory (in MiB) that Domain 0 can
+          dynamically allocate to itself. Does nothing if set
+          to the same amount as virtualisation.xen.memory, or
+          if that option is set to 0.
+        '';
+      };
+    };
 
-  ###### implementation
+    domains = {
+      extraConfig = lib.options.mkOption {
+        type = lib.types.lines;
+        default = "";
+        example = ''
+          XENDOMAINS_SAVE=/persist/xen/save
+          XENDOMAINS_RESTORE=false
+          XENDOMAINS_CREATE_USLEEP=10000000
+        '';
+        description = ''
+          Options defined here will override the defaults for xendomains.
+          The default options can be seen in the file included from
+          /etc/default/xendomains.
+        '';
+      };
+    };
 
-  config = mkIf cfg.enable {
-    assertions = [ {
-      assertion = pkgs.stdenv.isx86_64;
-      message = "Xen currently not supported on ${pkgs.stdenv.hostPlatform.system}";
-    } {
-      assertion = config.boot.loader.grub.enable && (config.boot.loader.grub.efiSupport == false);
-      message = "Xen currently does not support EFI boot";
-    } ];
+    store = {
+      path = lib.options.mkOption {
+        type = lib.types.path;
+        default = "${cfg.package}/bin/oxenstored";
+        defaultText = lib.options.literalExpression "\${config.virtualisation.xen.package}/bin/oxenstored";
+        example = lib.options.literalExpression "\${config.virtualisation.xen.package}/bin/xenstored";
+        description = ''
+          Path to the Xen Store Daemon. This option is useful to
+          switch between the legacy C-based Xen Store Daemon, and
+          the newer OCaml-based Xen Store Daemon, `oxenstored`.
+        '';
+      };
+      type = lib.options.mkOption {
+        type = lib.types.enum [
+          "c"
+          "ocaml"
+        ];
+        default = if (lib.strings.hasSuffix "oxenstored" cfg.store.path) then "ocaml" else "c";
+        internal = true;
+        readOnly = true;
+        description = "Helper internal option that determines the type of the Xen Store Daemon based on cfg.store.path.";
+      };
+      settings = lib.options.mkOption {
+        default = { };
+        example = {
+          enableMerge = false;
+          quota.maxWatchEvents = 2048;
+          quota.enable = true;
+          conflict.maxHistorySeconds = 0.12;
+          conflict.burstLimit = 15.0;
+          xenstored.log.file = "/dev/null";
+          xenstored.log.level = "info";
+        };
+        description = ''
+          The OCaml-based Xen Store Daemon configuration. This
+          option does nothing with the C-based `xenstored`.
+        '';
+        type = lib.types.submodule {
+          options = {
+            pidFile = lib.options.mkOption {
+              default = "/run/xen/xenstored.pid";
+              example = "/var/run/xen/xenstored.pid";
+              type = lib.types.path;
+              description = "Path to the Xen Store Daemon PID file.";
+            };
+            testEAGAIN = lib.options.mkOption {
+              default = cfg.debug;
+              defaultText = lib.options.literalExpression "config.virtualisation.xen.debug";
+              example = true;
+              type = lib.types.bool;
+              visible = false;
+              description = "Randomly fail a transaction with EAGAIN. This option is used for debugging purposes only.";
+            };
+            enableMerge = lib.options.mkOption {
+              default = true;
+              example = false;
+              type = lib.types.bool;
+              description = "Whether to enable transaction merge support.";
+            };
+            conflict = {
+              burstLimit = lib.options.mkOption {
+                default = 5.0;
+                example = 15.0;
+                type = lib.types.addCheck (
+                  lib.types.float
+                  // {
+                    name = "nonnegativeFloat";
+                    description = "nonnegative floating point number, meaning >=0";
+                    descriptionClass = "nonRestrictiveClause";
+                  }
+                ) (n: n >= 0);
+                description = ''
+                  Limits applied to domains whose writes cause other domains' transaction
+                  commits to fail. Must include decimal point.
+
+                  The burst limit is the number of conflicts a domain can cause to
+                  fail in a short period; this value is used for both the initial and
+                  the maximum value of each domain's conflict-credit, which falls by
+                  one point for each conflict caused, and when it reaches zero the
+                  domain's requests are ignored.
+                '';
+              };
+              maxHistorySeconds = lib.options.mkOption {
+                default = 5.0e-2;
+                example = 1.0;
+                type = lib.types.addCheck (
+                  lib.types.float // { description = "nonnegative floating point number, meaning >=0"; }
+                ) (n: n >= 0);
+                description = ''
+                  Limits applied to domains whose writes cause other domains' transaction
+                  commits to fail. Must include decimal point.
+
+                  The conflict-credit is replenished over time:
+                  one point is issued after each conflict.maxHistorySeconds, so this
+                  is the minimum pause-time during which a domain will be ignored.
+                '';
+              };
+              rateLimitIsAggregate = lib.options.mkOption {
+                default = true;
+                example = false;
+                type = lib.types.bool;
+                description = ''
+                  If the conflict.rateLimitIsAggregate option is `true`, then after each
+                  tick one point of conflict-credit is given to just one domain: the
+                  one at the front of the queue. If `false`, then after each tick each
+                  domain gets a point of conflict-credit.
+
+                  In environments where it is known that every transaction will
+                  involve a set of nodes that is writable by at most one other domain,
+                  then it is safe to set this aggregate limit flag to `false` for better
+                  performance. (This can be determined by considering the layout of
+                  the xenstore tree and permissions, together with the content of the
+                  transactions that require protection.)
+
+                  A transaction which involves a set of nodes which can be modified by
+                  multiple other domains can suffer conflicts caused by any of those
+                  domains, so the flag must be set to `true`.
+                '';
+              };
+            };
+            perms = {
+              enable = lib.options.mkOption {
+                default = true;
+                example = false;
+                type = lib.types.bool;
+                description = "Whether to enable the node permission system.";
+              };
+              enableWatch = lib.options.mkOption {
+                default = true;
+                example = false;
+                type = lib.types.bool;
+                description = ''
+                  Whether to enable the watch permission system.
+
+                  When this is set to `true`, unprivileged guests can only get watch events
+                  for xenstore entries that they would've been able to read.
+
+                  When this is set to `false`, unprivileged guests may get watch events
+                  for xenstore entries that they cannot read. The watch event contains
+                  only the entry name, not the value.
+                  This restores behaviour prior to [XSA-115](https://xenbits.xenproject.org/xsa/advisory-115.html).
+                '';
+              };
+            };
+            quota = {
+              enable = lib.options.mkOption {
+                default = true;
+                example = false;
+                type = lib.types.bool;
+                description = "Whether to enable the quota system.";
+              };
+              maxEntity = lib.options.mkOption {
+                default = 1000;
+                example = 1024;
+                type = lib.types.ints.positive;
+                description = "Entity limit for transactions.";
+              };
+              maxSize = lib.options.mkOption {
+                default = 2048;
+                example = 4096;
+                type = lib.types.ints.positive;
+                description = "Size limit for transactions.";
+              };
+              maxWatch = lib.options.mkOption {
+                default = 100;
+                example = 256;
+                type = lib.types.ints.positive;
+                description = "Maximum number of watches by the Xenstore Watchdog.";
+              };
+              transaction = lib.options.mkOption {
+                default = 10;
+                example = 50;
+                type = lib.types.ints.positive;
+                description = "Maximum number of transactions.";
+              };
+              maxRequests = lib.options.mkOption {
+                default = 1024;
+                example = 1024;
+                type = lib.types.ints.positive;
+                description = "Maximum number of requests per transaction.";
+              };
+              maxPath = lib.options.mkOption {
+                default = 1024;
+                example = 1024;
+                type = lib.types.ints.positive;
+                description = "Path limit for the quota system.";
+              };
+              maxOutstanding = lib.options.mkOption {
+                default = 1024;
+                example = 1024;
+                type = lib.types.ints.positive;
+                description = "Maximum outstanding requests, i.e. in-flight requests / domain.";
+              };
+              maxWatchEvents = lib.options.mkOption {
+                default = 1024;
+                example = 2048;
+                type = lib.types.ints.positive;
+                description = "Maximum number of outstanding watch events per watch.";
+              };
+            };
+            persistent = lib.options.mkOption {
+              default = false;
+              example = true;
+              type = lib.types.bool;
+              description = "Whether to activate the filed base backend.";
+            };
+            xenstored = {
+              log = {
+                file = lib.options.mkOption {
+                  default = "/var/log/xen/xenstored.log";
+                  example = "/dev/null";
+                  type = lib.types.path;
+                  description = "Path to the Xen Store log file.";
+                };
+                level = lib.options.mkOption {
+                  default = if cfg.trace then "debug" else null;
+                  defaultText = lib.options.literalExpression "if (config.virtualisation.xen.trace == true) then \"debug\" else null";
+                  example = "error";
+                  type = lib.types.nullOr (
+                    lib.types.enum [
+                      "debug"
+                      "info"
+                      "warn"
+                      "error"
+                    ]
+                  );
+                  description = "Logging level for the Xen Store.";
+                };
+                # The hidden options below have no upstream documentation whatsoever.
+                # The nb* options appear to alter the log rotation behaviour, and
+                # the specialOps option appears to affect the Xenbus logging logic.
+                nbFiles = lib.options.mkOption {
+                  default = 10;
+                  example = 16;
+                  type = lib.types.int;
+                  visible = false;
+                  description = "Set `xenstored-log-nb-files`.";
+                };
+              };
+              accessLog = {
+                file = lib.options.mkOption {
+                  default = "/var/log/xen/xenstored-access.log";
+                  example = "/var/log/security/xenstored-access.log";
+                  type = lib.types.path;
+                  description = "Path to the Xen Store access log file.";
+                };
+                nbLines = lib.options.mkOption {
+                  default = 13215;
+                  example = 16384;
+                  type = lib.types.int;
+                  visible = false;
+                  description = "Set `access-log-nb-lines`.";
+                };
+                nbChars = lib.options.mkOption {
+                  default = 180;
+                  example = 256;
+                  type = lib.types.int;
+                  visible = false;
+                  description = "Set `acesss-log-nb-chars`.";
+                };
+                specialOps = lib.options.mkOption {
+                  default = false;
+                  example = true;
+                  type = lib.types.bool;
+                  visible = false;
+                  description = "Set `access-log-special-ops`.";
+                };
+              };
+              xenfs = {
+                kva = lib.options.mkOption {
+                  default = "/proc/xen/xsd_kva";
+                  example = cfg.store.settings.xenstored.xenfs.kva;
+                  type = lib.types.path;
+                  visible = false;
+                  description = ''
+                    Path to the Xen Store Daemon KVA location inside the XenFS pseudo-filesystem.
+                    While it is possible to alter this value, some drivers may be hardcoded to follow the default paths.
+                  '';
+                };
+                port = lib.options.mkOption {
+                  default = "/proc/xen/xsd_port";
+                  example = cfg.store.settings.xenstored.xenfs.port;
+                  type = lib.types.path;
+                  visible = false;
+                  description = ''
+                    Path to the Xen Store Daemon userspace port inside the XenFS pseudo-filesystem.
+                    While it is possible to alter this value, some drivers may be hardcoded to follow the default paths.
+                  '';
+                };
+              };
+            };
+            ringScanInterval = lib.options.mkOption {
+              default = 20;
+              example = 30;
+              type = lib.types.addCheck (
+                lib.types.int
+                // {
+                  name = "nonzeroInt";
+                  description = "nonzero signed integer, meaning !=0";
+                  descriptionClass = "nonRestrictiveClause";
+                }
+              ) (n: n != 0);
+              description = ''
+                Perodic scanning for all the rings as a safenet for lazy clients.
+                Define the interval in seconds; set to a negative integer to disable.
+              '';
+            };
+          };
+        };
+      };
+    };
+  };
 
-    virtualisation.xen.package = mkDefault pkgs.xen;
-    virtualisation.xen.package-qemu = mkDefault pkgs.xen;
-    virtualisation.xen.stored = mkDefault "${cfg.package}/bin/oxenstored";
+  ## Implementation ##
 
-    environment.systemPackages = [ cfg.package ];
+  config = lib.modules.mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = pkgs.stdenv.isx86_64;
+        message = "Xen is currently not supported on ${pkgs.stdenv.hostPlatform.system}.";
+      }
+      {
+        assertion =
+          config.boot.loader.systemd-boot.enable
+          || (config.boot ? lanzaboote) && config.boot.lanzaboote.enable;
+        message = "Xen only supports booting on systemd-boot or Lanzaboote.";
+      }
+      {
+        assertion = config.boot.initrd.systemd.enable;
+        message = "Xen does not support the legacy script-based Stage 1 initrd.";
+      }
+      {
+        assertion = cfg.dom0Resources.maxMemory >= cfg.dom0Resources.memory;
+        message = ''
+          You have allocated more memory to dom0 than virtualisation.xen.dom0Resources.maxMemory
+          allows for. Please increase the maximum memory limit, or decrease the default memory allocation.
+        '';
+      }
+      {
+        assertion = cfg.debug -> cfg.trace;
+        message = "Xen's debugging features are enabled, but logging is disabled. This is most likely not what you want.";
+      }
+      {
+        assertion = cfg.store.settings.quota.maxWatchEvents >= cfg.store.settings.quota.maxOutstanding;
+        message = ''
+          Upstream Xen recommends that maxWatchEvents be equal to or greater than maxOutstanding,
+          in order to mitigate denial of service attacks from malicious frontends.
+        '';
+      }
+    ];
 
-    boot.kernelModules =
-      [ "xen-evtchn" "xen-gntdev" "xen-gntalloc" "xen-blkback" "xen-netback"
-        "xen-pciback" "evtchn" "gntdev" "netbk" "blkbk" "xen-scsibk"
-        "usbbk" "pciback" "xen-acpi-processor" "blktap2" "tun" "netxen_nic"
-        "xen_wdt" "xen-acpi-processor" "xen-privcmd" "xen-scsiback"
+    virtualisation.xen.bootParams =
+      lib.lists.optionals cfg.trace [
+        "loglvl=all"
+        "guest_loglvl=all"
+      ]
+      ++
+        lib.lists.optional (cfg.dom0Resources.memory != 0)
+          "dom0_mem=${toString cfg.dom0Resources.memory}M${
+            lib.strings.optionalString (
+              cfg.dom0Resources.memory != cfg.dom0Resources.maxMemory
+            ) ",max:${toString cfg.dom0Resources.maxMemory}M"
+          }"
+      ++ lib.lists.optional (
+        cfg.dom0Resources.maxVCPUs != 0
+      ) "dom0_max_vcpus=${toString cfg.dom0Resources.maxVCPUs}";
+
+    boot = {
+      kernelModules = [
+        "xen-evtchn"
+        "xen-gntdev"
+        "xen-gntalloc"
+        "xen-blkback"
+        "xen-netback"
+        "xen-pciback"
+        "evtchn"
+        "gntdev"
+        "netbk"
+        "blkbk"
+        "xen-scsibk"
+        "usbbk"
+        "pciback"
+        "xen-acpi-processor"
+        "blktap2"
+        "tun"
+        "netxen_nic"
+        "xen_wdt"
+        "xen-acpi-processor"
+        "xen-privcmd"
+        "xen-scsiback"
         "xenfs"
       ];
 
-    # The xenfs module is needed in system.activationScripts.xen, but
-    # the modprobe command there fails silently. Include xenfs in the
-    # initrd as a work around.
-    boot.initrd.kernelModules = [ "xenfs" ];
-
-    # The radeonfb kernel module causes the screen to go black as soon
-    # as it's loaded, so don't load it.
-    boot.blacklistedKernelModules = [ "radeonfb" ];
+      # The xenfs module is needed to mount /proc/xen.
+      initrd.kernelModules = [ "xenfs" ];
 
-    # Increase the number of loopback devices from the default (8),
-    # which is way too small because every VM virtual disk requires a
-    # loopback device.
-    boot.extraModprobeConfig =
-      ''
+      # Increase the number of loopback devices from the default (8),
+      # which is way too small because every VM virtual disk requires a
+      # loopback device.
+      extraModprobeConfig = ''
         options loop max_loop=64
       '';
 
-    virtualisation.xen.bootParams = [] ++
-      optionals cfg.trace [ "loglvl=all" "guest_loglvl=all" ] ++
-      optional (cfg.domain0MemorySize != 0) "dom0_mem=${toString cfg.domain0MemorySize}M";
-
-    system.extraSystemBuilderCmds =
-      ''
-        ln -s ${cfg.package}/boot/xen.gz $out/xen.gz
-        echo "${toString cfg.bootParams}" > $out/xen-params
-      '';
+      # Xen Bootspec extension. This extension allows NixOS bootloaders to
+      # fetch the `xen.efi` path and access the `cfg.bootParams` option.
+      bootspec.extensions = {
+        "org.xenproject.bootspec.v1" = {
+          xen = cfg.efi.path;
+          xenParams = cfg.bootParams;
+        };
+      };
 
-    # Mount the /proc/xen pseudo-filesystem.
-    system.activationScripts.xen =
-      ''
-        if [ -d /proc/xen ]; then
-            ${pkgs.kmod}/bin/modprobe xenfs 2> /dev/null
-            ${pkgs.util-linux}/bin/mountpoint -q /proc/xen || \
-                ${pkgs.util-linux}/bin/mount -t xenfs none /proc/xen
-        fi
+      # See the `xenBootBuilder` script in the main `let...in` statement of this file.
+      loader.systemd-boot.extraInstallCommands = ''
+        ${lib.meta.getExe xenBootBuilder} ${cfg.efi.bootBuilderVerbosity}
       '';
+    };
 
     # Domain 0 requires a pvops-enabled kernel.
-    system.requiredKernelConfig = with config.lib.kernelConfig;
-      [ (isYes "XEN")
-        (isYes "X86_IO_APIC")
-        (isYes "ACPI")
-        (isYes "XEN_DOM0")
-        (isYes "PCI_XEN")
-        (isYes "XEN_DEV_EVTCHN")
-        (isYes "XENFS")
-        (isYes "XEN_COMPAT_XENFS")
-        (isYes "XEN_SYS_HYPERVISOR")
-        (isYes "XEN_GNTDEV")
-        (isYes "XEN_BACKEND")
-        (isModule "XEN_NETDEV_BACKEND")
-        (isModule "XEN_BLKDEV_BACKEND")
-        (isModule "XEN_PCIDEV_BACKEND")
-        (isYes "XEN_BALLOON")
-        (isYes "XEN_SCRUB_PAGES")
+    # All NixOS kernels come with this enabled by default; this is merely a sanity check.
+    system.requiredKernelConfig = with config.lib.kernelConfig; [
+      (isYes "XEN")
+      (isYes "X86_IO_APIC")
+      (isYes "ACPI")
+      (isYes "XEN_DOM0")
+      (isYes "PCI_XEN")
+      (isYes "XEN_DEV_EVTCHN")
+      (isYes "XENFS")
+      (isYes "XEN_COMPAT_XENFS")
+      (isYes "XEN_SYS_HYPERVISOR")
+      (isYes "XEN_GNTDEV")
+      (isYes "XEN_BACKEND")
+      (isModule "XEN_NETDEV_BACKEND")
+      (isModule "XEN_BLKDEV_BACKEND")
+      (isModule "XEN_PCIDEV_BACKEND")
+      (isYes "XEN_BALLOON")
+      (isYes "XEN_SCRUB_PAGES")
+    ];
+
+    environment = {
+      systemPackages = [
+        cfg.package
+        cfg.qemu.package
       ];
+      etc =
+        # Set up Xen Domain 0 configuration files.
+        {
+          "xen/xl.conf".source = "${cfg.package}/etc/xen/xl.conf"; # TODO: Add options to configure xl.conf declaratively. It's worth considering making a new "xl value" type, as it could be reused to produce xl.cfg (domain definition) files.
+          "xen/scripts-xen" = {
+            source = "${cfg.package}/etc/xen/scripts/*";
+            target = "xen/scripts";
+          };
+          "default/xencommons".text = ''
+            source ${cfg.package}/etc/default/xencommons
+
+            XENSTORED="${cfg.store.path}"
+            QEMU_XEN="${cfg.qemu.package}/${cfg.qemu.package.qemu-system-i386}"
+            ${lib.strings.optionalString cfg.trace ''
+              XENSTORED_TRACE=yes
+              XENCONSOLED_TRACE=all
+            ''}
+          '';
+          "default/xendomains".text = ''
+            source ${cfg.package}/etc/default/xendomains
 
-
-    environment.etc =
-      {
-        "xen/xl.conf".source = "${cfg.package}/etc/xen/xl.conf";
-        "xen/scripts".source = "${cfg.package}/etc/xen/scripts";
-        "default/xendomains".text = ''
-          source ${cfg.package}/etc/default/xendomains
-
-          ${cfg.domains.extraConfig}
-        '';
-      }
-      // optionalAttrs (builtins.compareVersions cfg.package.version "4.10" >= 0) {
-        # in V 4.10 oxenstored requires /etc/xen/oxenstored.conf to start
-        "xen/oxenstored.conf".source = "${cfg.package}/etc/xen/oxenstored.conf";
-      };
+            ${cfg.domains.extraConfig}
+          '';
+        }
+        # The OCaml-based Xen Store Daemon requires /etc/xen/oxenstored.conf to start.
+        // lib.attrsets.optionalAttrs (cfg.store.type == "ocaml") {
+          "xen/oxenstored.conf".text = ''
+            pid-file = ${cfg.store.settings.pidFile}
+            test-eagain = ${lib.trivial.boolToString cfg.store.settings.testEAGAIN}
+            merge-activate = ${toString cfg.store.settings.enableMerge}
+            conflict-burst-limit = ${toString cfg.store.settings.conflict.burstLimit}
+            conflict-max-history-seconds = ${toString cfg.store.settings.conflict.maxHistorySeconds}
+            conflict-rate-limit-is-aggregate = ${toString cfg.store.settings.conflict.rateLimitIsAggregate}
+            perms-activate = ${toString cfg.store.settings.perms.enable}
+            perms-watch-activate = ${toString cfg.store.settings.perms.enableWatch}
+            quota-activate = ${toString cfg.store.settings.quota.enable}
+            quota-maxentity = ${toString cfg.store.settings.quota.maxEntity}
+            quota-maxsize = ${toString cfg.store.settings.quota.maxSize}
+            quota-maxwatch = ${toString cfg.store.settings.quota.maxWatch}
+            quota-transaction = ${toString cfg.store.settings.quota.transaction}
+            quota-maxrequests = ${toString cfg.store.settings.quota.maxRequests}
+            quota-path-max = ${toString cfg.store.settings.quota.maxPath}
+            quota-maxoutstanding = ${toString cfg.store.settings.quota.maxOutstanding}
+            quota-maxwatchevents = ${toString cfg.store.settings.quota.maxWatchEvents}
+            persistent = ${lib.trivial.boolToString cfg.store.settings.persistent}
+            xenstored-log-file = ${cfg.store.settings.xenstored.log.file}
+            xenstored-log-level = ${
+              if isNull cfg.store.settings.xenstored.log.level then
+                "null"
+              else
+                cfg.store.settings.xenstored.log.level
+            }
+            xenstored-log-nb-files = ${toString cfg.store.settings.xenstored.log.nbFiles}
+            access-log-file = ${cfg.store.settings.xenstored.accessLog.file}
+            access-log-nb-lines = ${toString cfg.store.settings.xenstored.accessLog.nbLines}
+            acesss-log-nb-chars = ${toString cfg.store.settings.xenstored.accessLog.nbChars}
+            access-log-special-ops = ${lib.trivial.boolToString cfg.store.settings.xenstored.accessLog.specialOps}
+            ring-scan-interval = ${toString cfg.store.settings.ringScanInterval}
+            xenstored-kva = ${cfg.store.settings.xenstored.xenfs.kva}
+            xenstored-port = ${cfg.store.settings.xenstored.xenfs.port}
+          '';
+        };
+    };
 
     # Xen provides udev rules.
     services.udev.packages = [ cfg.package ];
 
-    services.udev.path = [ pkgs.bridge-utils pkgs.iproute2 ];
-
-    systemd.services.xen-store = {
-      description = "Xen Store Daemon";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" "xen-store.socket" ];
-      requires = [ "xen-store.socket" ];
-      preStart = ''
-        export XENSTORED_ROOTDIR="/var/lib/xenstored"
-        rm -f "$XENSTORED_ROOTDIR"/tdb* &>/dev/null
-
-        mkdir -p /var/run
-        mkdir -p /var/log/xen # Running xl requires /var/log/xen and /var/lib/xen,
-        mkdir -p /var/lib/xen # so we create them here unconditionally.
-        grep -q control_d /proc/xen/capabilities
-        '';
-      serviceConfig = if (builtins.compareVersions cfg.package.version "4.8" < 0) then
-        { ExecStart = ''
-            ${cfg.stored}${optionalString cfg.trace " -T /var/log/xen/xenstored-trace.log"} --no-fork
-            '';
-        } else {
-          ExecStart = ''
-            ${cfg.package}/etc/xen/scripts/launch-xenstore
-            '';
-          Type            = "notify";
-          RemainAfterExit = true;
-          NotifyAccess    = "all";
-        };
-      postStart = ''
-        ${optionalString (builtins.compareVersions cfg.package.version "4.8" < 0) ''
-          time=0
-          timeout=30
-          # Wait for xenstored to actually come up, timing out after 30 seconds
-          while [ $time -lt $timeout ] && ! `${cfg.package}/bin/xenstore-read -s / >/dev/null 2>&1` ; do
-              time=$(($time+1))
-              sleep 1
-          done
-
-          # Exit if we timed out
-          if ! [ $time -lt $timeout ] ; then
-              echo "Could not start Xenstore Daemon"
-              exit 1
-          fi
-        ''}
-        echo "executing xen-init-dom0"
-        ${cfg.package}/lib/xen/bin/xen-init-dom0
-        '';
-    };
+    systemd = {
+      # Xen provides systemd units.
+      packages = [ cfg.package ];
+
+      mounts = [
+        {
+          description = "Mount /proc/xen files";
+          what = "xenfs";
+          where = "/proc/xen";
+          type = "xenfs";
+          unitConfig = {
+            ConditionPathExists = "/proc/xen";
+            RefuseManualStop = "true";
+          };
+        }
+      ];
 
-    systemd.sockets.xen-store = {
-      description = "XenStore Socket for userspace API";
-      wantedBy = [ "sockets.target" ];
-      socketConfig = {
-        ListenStream = [ "/var/run/xenstored/socket" "/var/run/xenstored/socket_ro" ];
-        SocketMode = "0660";
-        SocketUser = "root";
-        SocketGroup = "root";
-      };
-    };
+      services = {
 
+        # While this service is installed by the `xen` package, it shouldn't be used in dom0.
+        xendriverdomain.enable = false;
 
-    systemd.services.xen-console = {
-      description = "Xen Console Daemon";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "xen-store.service" ];
-      requires = [ "xen-store.service" ];
-      preStart = ''
-        mkdir -p /var/run/xen
-        ${optionalString cfg.trace "mkdir -p /var/log/xen"}
-        grep -q control_d /proc/xen/capabilities
-        '';
-      serviceConfig = {
-        ExecStart = ''
-          ${cfg.package}/bin/xenconsoled\
-            ${optionalString ((builtins.compareVersions cfg.package.version "4.8" >= 0)) " -i"}\
-            ${optionalString cfg.trace " --log=all --log-dir=/var/log/xen"}
+        xenstored = {
+          wantedBy = [ "multi-user.target" ];
+          preStart = ''
+            export XENSTORED_ROOTDIR="/var/lib/xenstored"
+            rm -f "$XENSTORED_ROOTDIR"/tdb* &>/dev/null
+            mkdir -p /var/{run,log,lib}/xen
           '';
-      };
-    };
-
-
-    systemd.services.xen-qemu = {
-      description = "Xen Qemu Daemon";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "xen-console.service" ];
-      requires = [ "xen-store.service" ];
-      serviceConfig.ExecStart = ''
-        ${cfg.package-qemu}/${cfg.package-qemu.qemu-system-i386} \
-           -xen-attach -xen-domid 0 -name dom0 -M xenpv \
-           -nographic -monitor /dev/null -serial /dev/null -parallel /dev/null
-        '';
-    };
+        };
 
+        xen-init-dom0 = {
+          restartIfChanged = false;
+          wantedBy = [ "multi-user.target" ];
+        };
 
-    systemd.services.xen-watchdog = {
-      description = "Xen Watchdog Daemon";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "xen-qemu.service" "xen-domains.service" ];
-      serviceConfig.ExecStart = "${cfg.package}/bin/xenwatchdogd 30 15";
-      serviceConfig.Type = "forking";
-      serviceConfig.RestartSec = "1";
-      serviceConfig.Restart = "on-failure";
-    };
+        xen-qemu-dom0-disk-backend = {
+          wantedBy = [ "multi-user.target" ];
+          serviceConfig = {
+            PIDFile = cfg.qemu.pidFile;
+            ExecStart = ''
+              ${cfg.qemu.package}/${cfg.qemu.package.qemu-system-i386} \
+              -xen-domid 0 -xen-attach -name dom0 -nographic -M xenpv \
+              -daemonize -monitor /dev/null -serial /dev/null -parallel \
+              /dev/null -nodefaults -no-user-config -pidfile \
+              ${cfg.qemu.pidFile}
+            '';
+          };
+        };
 
+        xenconsoled.wantedBy = [ "multi-user.target" ];
 
-    systemd.services.xen-bridge = {
-      description = "Xen bridge";
-      wantedBy = [ "multi-user.target" ];
-      before = [ "xen-domains.service" ];
-      preStart = ''
-        mkdir -p /var/run/xen
-        touch /var/run/xen/dnsmasq.pid
-        touch /var/run/xen/dnsmasq.etherfile
-        touch /var/run/xen/dnsmasq.leasefile
-
-        IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Usable\ range`
-        export XEN_BRIDGE_IP_RANGE_START="${"\${data[1]//[[:blank:]]/}"}"
-        export XEN_BRIDGE_IP_RANGE_END="${"\${data[2]//[[:blank:]]/}"}"
-
-        IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Network\ address`
-        export XEN_BRIDGE_NETWORK_ADDRESS="${"\${data[1]//[[:blank:]]/}"}"
-
-        IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Network\ mask`
-        export XEN_BRIDGE_NETMASK="${"\${data[1]//[[:blank:]]/}"}"
-
-        echo "${cfg.bridge.address} host gw dns" > /var/run/xen/dnsmasq.hostsfile
-
-        cat <<EOF > /var/run/xen/dnsmasq.conf
-        no-daemon
-        pid-file=/var/run/xen/dnsmasq.pid
-        interface=${cfg.bridge.name}
-        except-interface=lo
-        bind-interfaces
-        auth-zone=xen.local,$XEN_BRIDGE_NETWORK_ADDRESS/${toString cfg.bridge.prefixLength}
-        domain=xen.local
-        addn-hosts=/var/run/xen/dnsmasq.hostsfile
-        expand-hosts
-        strict-order
-        no-hosts
-        bogus-priv
-        ${optionalString (!cfg.bridge.forwardDns) ''
-          no-resolv
-          no-poll
-          auth-server=dns.xen.local,${cfg.bridge.name}
-        ''}
-        filterwin2k
-        clear-on-reload
-        domain-needed
-        dhcp-hostsfile=/var/run/xen/dnsmasq.etherfile
-        dhcp-authoritative
-        dhcp-range=$XEN_BRIDGE_IP_RANGE_START,$XEN_BRIDGE_IP_RANGE_END
-        dhcp-no-override
-        no-ping
-        dhcp-leasefile=/var/run/xen/dnsmasq.leasefile
-        EOF
-
-        # DHCP
-        ${pkgs.iptables}/bin/iptables -w -I INPUT  -i ${cfg.bridge.name} -p tcp -s $XEN_BRIDGE_NETWORK_ADDRESS/${toString cfg.bridge.prefixLength} --sport 68 --dport 67 -j ACCEPT
-        ${pkgs.iptables}/bin/iptables -w -I INPUT  -i ${cfg.bridge.name} -p udp -s $XEN_BRIDGE_NETWORK_ADDRESS/${toString cfg.bridge.prefixLength} --sport 68 --dport 67 -j ACCEPT
-        # DNS
-        ${pkgs.iptables}/bin/iptables -w -I INPUT  -i ${cfg.bridge.name} -p tcp -d ${cfg.bridge.address} --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
-        ${pkgs.iptables}/bin/iptables -w -I INPUT  -i ${cfg.bridge.name} -p udp -d ${cfg.bridge.address} --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
-
-        ${pkgs.bridge-utils}/bin/brctl addbr ${cfg.bridge.name}
-        ${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} ${cfg.bridge.address}
-        ${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} netmask $XEN_BRIDGE_NETMASK
-        ${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} up
-      '';
-      serviceConfig.ExecStart = "${pkgs.dnsmasq}/bin/dnsmasq --conf-file=/var/run/xen/dnsmasq.conf";
-      postStop = ''
-        IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Network\ address`
-        export XEN_BRIDGE_NETWORK_ADDRESS="${"\${data[1]//[[:blank:]]/}"}"
-
-        ${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} down
-        ${pkgs.bridge-utils}/bin/brctl delbr ${cfg.bridge.name}
-
-        # DNS
-        ${pkgs.iptables}/bin/iptables -w -D INPUT  -i ${cfg.bridge.name} -p udp -d ${cfg.bridge.address} --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
-        ${pkgs.iptables}/bin/iptables -w -D INPUT  -i ${cfg.bridge.name} -p tcp -d ${cfg.bridge.address} --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
-        # DHCP
-        ${pkgs.iptables}/bin/iptables -w -D INPUT  -i ${cfg.bridge.name} -p udp -s $XEN_BRIDGE_NETWORK_ADDRESS/${toString cfg.bridge.prefixLength} --sport 68 --dport 67 -j ACCEPT
-        ${pkgs.iptables}/bin/iptables -w -D INPUT  -i ${cfg.bridge.name} -p tcp -s $XEN_BRIDGE_NETWORK_ADDRESS/${toString cfg.bridge.prefixLength} --sport 68 --dport 67 -j ACCEPT
-      '';
-    };
-
+        xen-watchdog = {
+          wantedBy = [ "multi-user.target" ];
+          serviceConfig = {
+            RestartSec = "1";
+            Restart = "on-failure";
+          };
+        };
 
-    systemd.services.xen-domains = {
-      description = "Xen domains - automatically starts, saves and restores Xen domains";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "xen-bridge.service" "xen-qemu.service" ];
-      requires = [ "xen-bridge.service" "xen-qemu.service" ];
-      ## To prevent a race between dhcpcd and xend's bridge setup script
-      ## (which renames eth* to peth* and recreates eth* as a virtual
-      ## device), start dhcpcd after xend.
-      before = [ "dhcpd.service" ];
-      restartIfChanged = false;
-      serviceConfig.RemainAfterExit = "yes";
-      path = [ cfg.package cfg.package-qemu ];
-      environment.XENDOM_CONFIG = "${cfg.package}/etc/sysconfig/xendomains";
-      preStart = "mkdir -p /var/lock/subsys -m 755";
-      serviceConfig.ExecStart = "${cfg.package}/etc/init.d/xendomains start";
-      serviceConfig.ExecStop = "${cfg.package}/etc/init.d/xendomains stop";
+        xendomains = {
+          restartIfChanged = false;
+          path = [
+            cfg.package
+            cfg.qemu.package
+          ];
+          preStart = "mkdir -p /var/lock/subsys -m 755";
+          wantedBy = [ "multi-user.target" ];
+        };
+      };
     };
-
   };
+  meta.maintainers = with lib.maintainers; [ sigmasquadron ];
 }