1 # Xen Project Hypervisor (Dom0) support.
11 inherit (builtins) readFile;
12 inherit (lib.modules) mkRemovedOptionModule mkRenamedOptionModule mkIf;
33 inherit (lib.lists) optional optionals;
34 inherit (lib.strings) hasSuffix optionalString;
35 inherit (lib.meta) getExe;
36 inherit (lib.attrsets) optionalAttrs;
37 inherit (lib.trivial) boolToString;
38 inherit (lib.teams.xen) members;
40 cfg = config.virtualisation.xen;
42 xenBootBuilder = pkgs.writeShellApplication {
43 name = "xenBootBuilder";
54 ++ optionals (cfg.efi.bootBuilderVerbosity == "info") (
62 efiMountPoint = config.boot.loader.efi.efiSysMountPoint;
65 # We disable SC2016 because we don't want to expand the regexes in the sed commands.
66 excludeShellChecks = [ "SC2016" ];
68 text = readFile ./xen-boot-builder.sh;
74 (mkRemovedOptionModule
81 "The Xen Network Bridge options are currently unavailable. Please set up your own bridge manually."
83 (mkRemovedOptionModule
90 "The Xen Network Bridge options are currently unavailable. Please set up your own bridge manually."
92 (mkRemovedOptionModule
99 "The Xen Network Bridge options are currently unavailable. Please set up your own bridge manually."
101 (mkRemovedOptionModule
108 "The Xen Network Bridge options are currently unavailable. Please set up your own bridge manually."
110 (mkRenamedOptionModule
123 (mkRenamedOptionModule
136 (mkRenamedOptionModule
153 options.virtualisation.xen = {
155 enable = mkEnableOption "the Xen Project 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";
157 debug = mkEnableOption "Xen debug features for Domain 0. This option enables some hidden debugging tests and features, and should not be used in production";
162 defaultText = literalExpression "false";
164 description = "Whether to enable Xen debug tracing and logging for Domain 0.";
167 package = mkPackageOption pkgs "Xen Hypervisor" { default = [ "xen" ]; };
170 package = mkPackageOption pkgs "QEMU (with Xen Hypervisor support)" {
171 default = [ "qemu_xen" ];
175 default = "/run/xen/qemu-dom0.pid";
176 example = "/var/run/xen/qemu-dom0.pid";
177 description = "Path to the QEMU PID file.";
181 bootParams = mkOption {
185 "iommu=force:true,qinval:true,debug:true"
192 Xen Command Line parameters passed to Domain 0 at boot time.
193 Note: these are different from `boot.kernelParams`. See
194 the [Xen documentation](https://xenbits.xenproject.org/docs/unstable/misc/xen-command-line.html) for more information.
199 bootBuilderVerbosity = mkOption {
209 The EFI boot entry builder script should be called with exactly one of the following arguments in order to specify its verbosity:
211 - `quiet` supresses all messages.
213 - `default` adds a simple "Installing Xen Project Hypervisor boot entries...done." message to the script.
215 - `info` is the same as `default`, but it also prints a diff with information on which generations were altered.
216 - This option adds two extra dependencies to the script: `diffutils` and `bat`.
218 - `debug` prints information messages for every single step of the script.
220 This option does not alter the actual functionality of the script, just the number of messages printed when rebuilding the system.
226 default = "${cfg.package.boot}/${cfg.package.efi}";
227 defaultText = literalExpression "\${config.virtualisation.xen.package.boot}/\${config.virtualisation.xen.package.efi}";
228 example = literalExpression "\${config.virtualisation.xen.package}/boot/efi/efi/nixos/xen-\${config.virtualisation.xen.package.version}.efi";
230 Path to xen.efi. `pkgs.xen` is patched to install the xen.efi file
231 on `$boot/boot/xen.efi`, but an unpatched Xen build may install it
232 somewhere else, such as `$out/boot/efi/efi/nixos/xen.efi`. Unless
233 you're building your own Xen derivation, you should leave this
234 option as the default value.
240 maxVCPUs = mkOption {
243 type = ints.unsigned;
245 Amount of virtual CPU cores allocated to Domain 0 on boot.
246 If set to 0, all cores are assigned to Domain 0, and
247 unprivileged domains will compete with Domain 0 for CPU time.
254 type = ints.unsigned;
256 Amount of memory (in MiB) allocated to Domain 0 on boot.
257 If set to 0, all memory is assigned to Domain 0, and
258 unprivileged domains will compete with Domain 0 for free RAM.
262 maxMemory = mkOption {
263 default = cfg.dom0Resources.memory;
264 defaultText = literalExpression "config.virtualisation.xen.dom0Resources.memory";
266 type = ints.unsigned;
268 Maximum amount of memory (in MiB) that Domain 0 can
269 dynamically allocate to itself. Does nothing if set
270 to the same amount as virtualisation.xen.memory, or
271 if that option is set to 0.
277 extraConfig = mkOption {
281 XENDOMAINS_SAVE=/persist/xen/save
282 XENDOMAINS_RESTORE=false
283 XENDOMAINS_CREATE_USLEEP=10000000
286 Options defined here will override the defaults for xendomains.
287 The default options can be seen in the file included from
288 /etc/default/xendomains.
296 default = "${cfg.package}/bin/oxenstored";
297 defaultText = literalExpression "\${config.virtualisation.xen.package}/bin/oxenstored";
298 example = literalExpression "\${config.virtualisation.xen.package}/bin/xenstored";
300 Path to the Xen Store Daemon. This option is useful to
301 switch between the legacy C-based Xen Store Daemon, and
302 the newer OCaml-based Xen Store Daemon, `oxenstored`.
310 default = if (hasSuffix "oxenstored" cfg.store.path) then "ocaml" else "c";
313 description = "Helper internal option that determines the type of the Xen Store Daemon based on cfg.store.path.";
315 settings = mkOption {
319 quota.maxWatchEvents = 2048;
321 conflict.maxHistorySeconds = 0.12;
322 conflict.burstLimit = 15.0;
323 xenstored.log.file = "/dev/null";
324 xenstored.log.level = "info";
327 The OCaml-based Xen Store Daemon configuration. This
328 option does nothing with the C-based `xenstored`.
333 default = "/run/xen/xenstored.pid";
334 example = "/var/run/xen/xenstored.pid";
336 description = "Path to the Xen Store Daemon PID file.";
338 testEAGAIN = mkOption {
340 defaultText = literalExpression "config.virtualisation.xen.debug";
344 description = "Randomly fail a transaction with EAGAIN. This option is used for debugging purposes only.";
346 enableMerge = mkOption {
350 description = "Whether to enable transaction merge support.";
353 burstLimit = mkOption {
359 name = "nonnegativeFloat";
360 description = "nonnegative floating point number, meaning >=0";
361 descriptionClass = "nonRestrictiveClause";
365 Limits applied to domains whose writes cause other domains' transaction
366 commits to fail. Must include decimal point.
368 The burst limit is the number of conflicts a domain can cause to
369 fail in a short period; this value is used for both the initial and
370 the maximum value of each domain's conflict-credit, which falls by
371 one point for each conflict caused, and when it reaches zero the
372 domain's requests are ignored.
375 maxHistorySeconds = mkOption {
378 type = addCheck (float // { description = "nonnegative floating point number, meaning >=0"; }) (
382 Limits applied to domains whose writes cause other domains' transaction
383 commits to fail. Must include decimal point.
385 The conflict-credit is replenished over time:
386 one point is issued after each conflict.maxHistorySeconds, so this
387 is the minimum pause-time during which a domain will be ignored.
390 rateLimitIsAggregate = mkOption {
395 If the conflict.rateLimitIsAggregate option is `true`, then after each
396 tick one point of conflict-credit is given to just one domain: the
397 one at the front of the queue. If `false`, then after each tick each
398 domain gets a point of conflict-credit.
400 In environments where it is known that every transaction will
401 involve a set of nodes that is writable by at most one other domain,
402 then it is safe to set this aggregate limit flag to `false` for better
403 performance. (This can be determined by considering the layout of
404 the xenstore tree and permissions, together with the content of the
405 transactions that require protection.)
407 A transaction which involves a set of nodes which can be modified by
408 multiple other domains can suffer conflicts caused by any of those
409 domains, so the flag must be set to `true`.
418 description = "Whether to enable the node permission system.";
420 enableWatch = mkOption {
425 Whether to enable the watch permission system.
427 When this is set to `true`, unprivileged guests can only get watch events
428 for xenstore entries that they would've been able to read.
430 When this is set to `false`, unprivileged guests may get watch events
431 for xenstore entries that they cannot read. The watch event contains
432 only the entry name, not the value.
433 This restores behaviour prior to [XSA-115](https://xenbits.xenproject.org/xsa/advisory-115.html).
442 description = "Whether to enable the quota system.";
444 maxEntity = mkOption {
447 type = ints.positive;
448 description = "Entity limit for transactions.";
453 type = ints.positive;
454 description = "Size limit for transactions.";
456 maxWatch = mkOption {
459 type = ints.positive;
460 description = "Maximum number of watches by the Xenstore Watchdog.";
462 transaction = mkOption {
465 type = ints.positive;
466 description = "Maximum number of transactions.";
468 maxRequests = mkOption {
471 type = ints.positive;
472 description = "Maximum number of requests per transaction.";
477 type = ints.positive;
478 description = "Path limit for the quota system.";
480 maxOutstanding = mkOption {
483 type = ints.positive;
484 description = "Maximum outstanding requests, i.e. in-flight requests / domain.";
486 maxWatchEvents = mkOption {
489 type = ints.positive;
490 description = "Maximum number of outstanding watch events per watch.";
493 persistent = mkOption {
497 description = "Whether to activate the filed base backend.";
502 default = "/var/log/xen/xenstored.log";
503 example = "/dev/null";
505 description = "Path to the Xen Store log file.";
508 default = if cfg.trace then "debug" else null;
509 defaultText = literalExpression "if (config.virtualisation.xen.trace == true) then \"debug\" else null";
511 type = nullOr (enum [
517 description = "Logging level for the Xen Store.";
519 # The hidden options below have no upstream documentation whatsoever.
520 # The nb* options appear to alter the log rotation behaviour, and
521 # the specialOps option appears to affect the Xenbus logging logic.
527 description = "Set `xenstored-log-nb-files`.";
532 default = "/var/log/xen/xenstored-access.log";
533 example = "/var/log/security/xenstored-access.log";
535 description = "Path to the Xen Store access log file.";
542 description = "Set `access-log-nb-lines`.";
549 description = "Set `acesss-log-nb-chars`.";
551 specialOps = mkOption {
556 description = "Set `access-log-special-ops`.";
561 default = "/proc/xen/xsd_kva";
562 example = cfg.store.settings.xenstored.xenfs.kva;
566 Path to the Xen Store Daemon KVA location inside the XenFS pseudo-filesystem.
567 While it is possible to alter this value, some drivers may be hardcoded to follow the default paths.
571 default = "/proc/xen/xsd_port";
572 example = cfg.store.settings.xenstored.xenfs.port;
576 Path to the Xen Store Daemon userspace port inside the XenFS pseudo-filesystem.
577 While it is possible to alter this value, some drivers may be hardcoded to follow the default paths.
582 ringScanInterval = mkOption {
589 description = "nonzero signed integer, meaning !=0";
590 descriptionClass = "nonRestrictiveClause";
594 Perodic scanning for all the rings as a safenet for lazy clients.
595 Define the interval in seconds; set to a negative integer to disable.
606 config = mkIf cfg.enable {
609 assertion = pkgs.stdenv.hostPlatform.isx86_64;
610 message = "Xen is currently not supported on ${pkgs.stdenv.hostPlatform.system}.";
614 config.boot.loader.systemd-boot.enable
615 || (config.boot ? lanzaboote) && config.boot.lanzaboote.enable;
616 message = "Xen only supports booting on systemd-boot or Lanzaboote.";
619 assertion = config.boot.initrd.systemd.enable;
620 message = "Xen does not support the legacy script-based Stage 1 initrd.";
623 assertion = cfg.dom0Resources.maxMemory >= cfg.dom0Resources.memory;
625 You have allocated more memory to dom0 than virtualisation.xen.dom0Resources.maxMemory
626 allows for. Please increase the maximum memory limit, or decrease the default memory allocation.
630 assertion = cfg.debug -> cfg.trace;
631 message = "Xen's debugging features are enabled, but logging is disabled. This is most likely not what you want.";
634 assertion = cfg.store.settings.quota.maxWatchEvents >= cfg.store.settings.quota.maxOutstanding;
636 Upstream Xen recommends that maxWatchEvents be equal to or greater than maxOutstanding,
637 in order to mitigate denial of service attacks from malicious frontends.
642 virtualisation.xen.bootParams =
643 optionals cfg.trace [
648 optional (cfg.dom0Resources.memory != 0)
649 "dom0_mem=${toString cfg.dom0Resources.memory}M${
651 cfg.dom0Resources.memory != cfg.dom0Resources.maxMemory
652 ) ",max:${toString cfg.dom0Resources.maxMemory}M"
655 cfg.dom0Resources.maxVCPUs != 0
656 ) "dom0_max_vcpus=${toString cfg.dom0Resources.maxVCPUs}";
684 # The xenfs module is needed to mount /proc/xen.
685 initrd.kernelModules = [ "xenfs" ];
687 # Increase the number of loopback devices from the default (8),
688 # which is way too small because every VM virtual disk requires a
690 extraModprobeConfig = ''
691 options loop max_loop=64
694 # Xen Bootspec extension. This extension allows NixOS bootloaders to
695 # fetch the `xen.efi` path and access the `cfg.bootParams` option.
696 bootspec.extensions = {
697 "org.xenproject.bootspec.v1" = {
699 xenParams = cfg.bootParams;
703 # See the `xenBootBuilder` script in the main `let...in` statement of this file.
704 loader.systemd-boot.extraInstallCommands = ''
705 ${getExe xenBootBuilder} ${cfg.efi.bootBuilderVerbosity}
709 # Domain 0 requires a pvops-enabled kernel.
710 # All NixOS kernels come with this enabled by default; this is merely a sanity check.
711 system.requiredKernelConfig = with config.lib.kernelConfig; [
713 (isYes "X86_IO_APIC")
717 (isYes "XEN_DEV_EVTCHN")
719 (isYes "XEN_COMPAT_XENFS")
720 (isYes "XEN_SYS_HYPERVISOR")
722 (isYes "XEN_BACKEND")
723 (isModule "XEN_NETDEV_BACKEND")
724 (isModule "XEN_BLKDEV_BACKEND")
725 (isModule "XEN_PCIDEV_BACKEND")
726 (isYes "XEN_BALLOON")
727 (isYes "XEN_SCRUB_PAGES")
736 # Set up Xen Domain 0 configuration files.
738 "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.
739 "xen/scripts-xen" = {
740 source = "${cfg.package}/etc/xen/scripts/*";
741 target = "xen/scripts";
743 "default/xencommons".text = ''
744 source ${cfg.package}/etc/default/xencommons
746 XENSTORED="${cfg.store.path}"
747 QEMU_XEN="${cfg.qemu.package}/${cfg.qemu.package.qemu-system-i386}"
748 ${optionalString cfg.trace ''
750 XENCONSOLED_TRACE=all
753 "default/xendomains".text = ''
754 source ${cfg.package}/etc/default/xendomains
756 ${cfg.domains.extraConfig}
759 # The OCaml-based Xen Store Daemon requires /etc/xen/oxenstored.conf to start.
760 // optionalAttrs (cfg.store.type == "ocaml") {
761 "xen/oxenstored.conf".text = ''
762 pid-file = ${cfg.store.settings.pidFile}
763 test-eagain = ${boolToString cfg.store.settings.testEAGAIN}
764 merge-activate = ${toString cfg.store.settings.enableMerge}
765 conflict-burst-limit = ${toString cfg.store.settings.conflict.burstLimit}
766 conflict-max-history-seconds = ${toString cfg.store.settings.conflict.maxHistorySeconds}
767 conflict-rate-limit-is-aggregate = ${toString cfg.store.settings.conflict.rateLimitIsAggregate}
768 perms-activate = ${toString cfg.store.settings.perms.enable}
769 perms-watch-activate = ${toString cfg.store.settings.perms.enableWatch}
770 quota-activate = ${toString cfg.store.settings.quota.enable}
771 quota-maxentity = ${toString cfg.store.settings.quota.maxEntity}
772 quota-maxsize = ${toString cfg.store.settings.quota.maxSize}
773 quota-maxwatch = ${toString cfg.store.settings.quota.maxWatch}
774 quota-transaction = ${toString cfg.store.settings.quota.transaction}
775 quota-maxrequests = ${toString cfg.store.settings.quota.maxRequests}
776 quota-path-max = ${toString cfg.store.settings.quota.maxPath}
777 quota-maxoutstanding = ${toString cfg.store.settings.quota.maxOutstanding}
778 quota-maxwatchevents = ${toString cfg.store.settings.quota.maxWatchEvents}
779 persistent = ${boolToString cfg.store.settings.persistent}
780 xenstored-log-file = ${cfg.store.settings.xenstored.log.file}
781 xenstored-log-level = ${
782 if isNull cfg.store.settings.xenstored.log.level then
785 cfg.store.settings.xenstored.log.level
787 xenstored-log-nb-files = ${toString cfg.store.settings.xenstored.log.nbFiles}
788 access-log-file = ${cfg.store.settings.xenstored.accessLog.file}
789 access-log-nb-lines = ${toString cfg.store.settings.xenstored.accessLog.nbLines}
790 acesss-log-nb-chars = ${toString cfg.store.settings.xenstored.accessLog.nbChars}
791 access-log-special-ops = ${boolToString cfg.store.settings.xenstored.accessLog.specialOps}
792 ring-scan-interval = ${toString cfg.store.settings.ringScanInterval}
793 xenstored-kva = ${cfg.store.settings.xenstored.xenfs.kva}
794 xenstored-port = ${cfg.store.settings.xenstored.xenfs.port}
799 # Xen provides udev rules.
800 services.udev.packages = [ cfg.package ];
803 # Xen provides systemd units.
804 packages = [ cfg.package ];
808 description = "Mount /proc/xen files";
813 ConditionPathExists = "/proc/xen";
814 RefuseManualStop = "true";
821 # While this service is installed by the `xen` package, it shouldn't be used in dom0.
822 xendriverdomain.enable = false;
825 wantedBy = [ "multi-user.target" ];
827 export XENSTORED_ROOTDIR="/var/lib/xenstored"
828 rm -f "$XENSTORED_ROOTDIR"/tdb* &>/dev/null
829 mkdir -p /var/{run,log,lib}/xen
834 restartIfChanged = false;
835 wantedBy = [ "multi-user.target" ];
838 xen-qemu-dom0-disk-backend = {
839 wantedBy = [ "multi-user.target" ];
841 PIDFile = cfg.qemu.pidFile;
843 ${cfg.qemu.package}/${cfg.qemu.package.qemu-system-i386} \
844 -xen-domid 0 -xen-attach -name dom0 -nographic -M xenpv \
845 -daemonize -monitor /dev/null -serial /dev/null -parallel \
846 /dev/null -nodefaults -no-user-config -pidfile \
852 xenconsoled.wantedBy = [ "multi-user.target" ];
855 wantedBy = [ "multi-user.target" ];
858 Restart = "on-failure";
863 restartIfChanged = false;
868 preStart = "mkdir -p /var/lock/subsys -m 755";
869 wantedBy = [ "multi-user.target" ];
874 meta.maintainers = members;