1 # Xen hypervisor (Dom0) support.
11 cfg = config.virtualisation.xen;
13 xenBootBuilder = pkgs.writeShellApplication {
14 name = "xenBootBuilder";
25 ++ lib.lists.optionals (cfg.efi.bootBuilderVerbosity == "info") (
33 efiMountPoint = config.boot.loader.efi.efiSysMountPoint;
36 # We disable SC2016 because we don't want to expand the regexes in the sed commands.
37 excludeShellChecks = [ "SC2016" ];
39 text = builtins.readFile ./xen-boot-builder.sh;
44 imports = with lib.modules; [
45 (mkRemovedOptionModule
52 "The Xen Network Bridge options are currently unavailable. Please set up your own bridge manually."
54 (mkRemovedOptionModule
61 "The Xen Network Bridge options are currently unavailable. Please set up your own bridge manually."
63 (mkRemovedOptionModule
70 "The Xen Network Bridge options are currently unavailable. Please set up your own bridge manually."
72 (mkRemovedOptionModule
79 "The Xen Network Bridge options are currently unavailable. Please set up your own bridge manually."
81 (mkRenamedOptionModule
94 (mkRenamedOptionModule
107 (mkRenamedOptionModule
124 options.virtualisation.xen = {
126 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";
128 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";
130 trace = lib.options.mkOption {
131 type = lib.types.bool;
133 defaultText = lib.options.literalExpression "false";
135 description = "Whether to enable Xen debug tracing and logging for Domain 0.";
138 package = lib.options.mkOption {
139 type = lib.types.package;
141 defaultText = lib.options.literalExpression "pkgs.xen";
142 example = lib.options.literalExpression "pkgs.xen-slim";
144 The package used for Xen Hypervisor.
153 package = lib.options.mkOption {
154 type = lib.types.package;
156 defaultText = lib.options.literalExpression "pkgs.xen";
157 example = lib.options.literalExpression "pkgs.qemu_xen";
159 The package with QEMU binaries that runs in Domain 0
160 and virtualises the unprivileged domains.
166 comment = "For use with `pkgs.xen-slim`.";
170 pidFile = lib.options.mkOption {
171 type = lib.types.path;
172 default = "/run/xen/qemu-dom0.pid";
173 example = "/var/run/xen/qemu-dom0.pid";
174 description = "Path to the QEMU PID file.";
178 bootParams = lib.options.mkOption {
182 "iommu=force:true,qinval:true,debug:true"
187 type = lib.types.listOf lib.types.str;
189 Xen Command Line parameters passed to Domain 0 at boot time.
190 Note: these are different from `boot.kernelParams`. See
191 the [Xen documentation](https://xenbits.xenproject.org/docs/unstable/misc/xen-command-line.html) for more information.
196 bootBuilderVerbosity = lib.options.mkOption {
197 type = lib.types.enum [
206 The EFI boot entry builder script should be called with exactly one of the following arguments in order to specify its verbosity:
208 - `quiet` supresses all messages.
210 - `default` adds a simple "Installing Xen Hypervisor boot entries...done." message to the script.
212 - `info` is the same as `default`, but it also prints a diff with information on which generations were altered.
213 - This option adds two extra dependencies to the script: `diffutils` and `bat`.
215 - `debug` prints information messages for every single step of the script.
217 This option does not alter the actual functionality of the script, just the number of messages printed when rebuilding the system.
221 path = lib.options.mkOption {
222 type = lib.types.path;
223 default = "${cfg.package.boot}/${cfg.package.efi}";
224 defaultText = lib.options.literalExpression "\${config.virtualisation.xen.package.boot}/\${config.virtualisation.xen.package.efi}";
225 example = lib.options.literalExpression "\${config.virtualisation.xen.package}/boot/efi/efi/nixos/xen-\${config.virtualisation.xen.package.version}.efi";
227 Path to xen.efi. `pkgs.xen` is patched to install the xen.efi file
228 on `$boot/boot/xen.efi`, but an unpatched Xen build may install it
229 somewhere else, such as `$out/boot/efi/efi/nixos/xen.efi`. Unless
230 you're building your own Xen derivation, you should leave this
231 option as the default value.
237 maxVCPUs = lib.options.mkOption {
240 type = lib.types.ints.unsigned;
242 Amount of virtual CPU cores allocated to Domain 0 on boot.
243 If set to 0, all cores are assigned to Domain 0, and
244 unprivileged domains will compete with Domain 0 for CPU time.
248 memory = lib.options.mkOption {
251 type = lib.types.ints.unsigned;
253 Amount of memory (in MiB) allocated to Domain 0 on boot.
254 If set to 0, all memory is assigned to Domain 0, and
255 unprivileged domains will compete with Domain 0 for free RAM.
259 maxMemory = lib.options.mkOption {
260 default = cfg.dom0Resources.memory;
261 defaultText = lib.options.literalExpression "config.virtualisation.xen.dom0Resources.memory";
263 type = lib.types.ints.unsigned;
265 Maximum amount of memory (in MiB) that Domain 0 can
266 dynamically allocate to itself. Does nothing if set
267 to the same amount as virtualisation.xen.memory, or
268 if that option is set to 0.
274 extraConfig = lib.options.mkOption {
275 type = lib.types.lines;
278 XENDOMAINS_SAVE=/persist/xen/save
279 XENDOMAINS_RESTORE=false
280 XENDOMAINS_CREATE_USLEEP=10000000
283 Options defined here will override the defaults for xendomains.
284 The default options can be seen in the file included from
285 /etc/default/xendomains.
291 path = lib.options.mkOption {
292 type = lib.types.path;
293 default = "${cfg.package}/bin/oxenstored";
294 defaultText = lib.options.literalExpression "\${config.virtualisation.xen.package}/bin/oxenstored";
295 example = lib.options.literalExpression "\${config.virtualisation.xen.package}/bin/xenstored";
297 Path to the Xen Store Daemon. This option is useful to
298 switch between the legacy C-based Xen Store Daemon, and
299 the newer OCaml-based Xen Store Daemon, `oxenstored`.
302 type = lib.options.mkOption {
303 type = lib.types.enum [
307 default = if (lib.strings.hasSuffix "oxenstored" cfg.store.path) then "ocaml" else "c";
310 description = "Helper internal option that determines the type of the Xen Store Daemon based on cfg.store.path.";
312 settings = lib.options.mkOption {
316 quota.maxWatchEvents = 2048;
318 conflict.maxHistorySeconds = 0.12;
319 conflict.burstLimit = 15.0;
320 xenstored.log.file = "/dev/null";
321 xenstored.log.level = "info";
324 The OCaml-based Xen Store Daemon configuration. This
325 option does nothing with the C-based `xenstored`.
327 type = lib.types.submodule {
329 pidFile = lib.options.mkOption {
330 default = "/run/xen/xenstored.pid";
331 example = "/var/run/xen/xenstored.pid";
332 type = lib.types.path;
333 description = "Path to the Xen Store Daemon PID file.";
335 testEAGAIN = lib.options.mkOption {
337 defaultText = lib.options.literalExpression "config.virtualisation.xen.debug";
339 type = lib.types.bool;
341 description = "Randomly fail a transaction with EAGAIN. This option is used for debugging purposes only.";
343 enableMerge = lib.options.mkOption {
346 type = lib.types.bool;
347 description = "Whether to enable transaction merge support.";
350 burstLimit = lib.options.mkOption {
353 type = lib.types.addCheck (
356 name = "nonnegativeFloat";
357 description = "nonnegative floating point number, meaning >=0";
358 descriptionClass = "nonRestrictiveClause";
362 Limits applied to domains whose writes cause other domains' transaction
363 commits to fail. Must include decimal point.
365 The burst limit is the number of conflicts a domain can cause to
366 fail in a short period; this value is used for both the initial and
367 the maximum value of each domain's conflict-credit, which falls by
368 one point for each conflict caused, and when it reaches zero the
369 domain's requests are ignored.
372 maxHistorySeconds = lib.options.mkOption {
375 type = lib.types.addCheck (
376 lib.types.float // { description = "nonnegative floating point number, meaning >=0"; }
379 Limits applied to domains whose writes cause other domains' transaction
380 commits to fail. Must include decimal point.
382 The conflict-credit is replenished over time:
383 one point is issued after each conflict.maxHistorySeconds, so this
384 is the minimum pause-time during which a domain will be ignored.
387 rateLimitIsAggregate = lib.options.mkOption {
390 type = lib.types.bool;
392 If the conflict.rateLimitIsAggregate option is `true`, then after each
393 tick one point of conflict-credit is given to just one domain: the
394 one at the front of the queue. If `false`, then after each tick each
395 domain gets a point of conflict-credit.
397 In environments where it is known that every transaction will
398 involve a set of nodes that is writable by at most one other domain,
399 then it is safe to set this aggregate limit flag to `false` for better
400 performance. (This can be determined by considering the layout of
401 the xenstore tree and permissions, together with the content of the
402 transactions that require protection.)
404 A transaction which involves a set of nodes which can be modified by
405 multiple other domains can suffer conflicts caused by any of those
406 domains, so the flag must be set to `true`.
411 enable = lib.options.mkOption {
414 type = lib.types.bool;
415 description = "Whether to enable the node permission system.";
417 enableWatch = lib.options.mkOption {
420 type = lib.types.bool;
422 Whether to enable the watch permission system.
424 When this is set to `true`, unprivileged guests can only get watch events
425 for xenstore entries that they would've been able to read.
427 When this is set to `false`, unprivileged guests may get watch events
428 for xenstore entries that they cannot read. The watch event contains
429 only the entry name, not the value.
430 This restores behaviour prior to [XSA-115](https://xenbits.xenproject.org/xsa/advisory-115.html).
435 enable = lib.options.mkOption {
438 type = lib.types.bool;
439 description = "Whether to enable the quota system.";
441 maxEntity = lib.options.mkOption {
444 type = lib.types.ints.positive;
445 description = "Entity limit for transactions.";
447 maxSize = lib.options.mkOption {
450 type = lib.types.ints.positive;
451 description = "Size limit for transactions.";
453 maxWatch = lib.options.mkOption {
456 type = lib.types.ints.positive;
457 description = "Maximum number of watches by the Xenstore Watchdog.";
459 transaction = lib.options.mkOption {
462 type = lib.types.ints.positive;
463 description = "Maximum number of transactions.";
465 maxRequests = lib.options.mkOption {
468 type = lib.types.ints.positive;
469 description = "Maximum number of requests per transaction.";
471 maxPath = lib.options.mkOption {
474 type = lib.types.ints.positive;
475 description = "Path limit for the quota system.";
477 maxOutstanding = lib.options.mkOption {
480 type = lib.types.ints.positive;
481 description = "Maximum outstanding requests, i.e. in-flight requests / domain.";
483 maxWatchEvents = lib.options.mkOption {
486 type = lib.types.ints.positive;
487 description = "Maximum number of outstanding watch events per watch.";
490 persistent = lib.options.mkOption {
493 type = lib.types.bool;
494 description = "Whether to activate the filed base backend.";
498 file = lib.options.mkOption {
499 default = "/var/log/xen/xenstored.log";
500 example = "/dev/null";
501 type = lib.types.path;
502 description = "Path to the Xen Store log file.";
504 level = lib.options.mkOption {
505 default = if cfg.trace then "debug" else null;
506 defaultText = lib.options.literalExpression "if (config.virtualisation.xen.trace == true) then \"debug\" else null";
508 type = lib.types.nullOr (
516 description = "Logging level for the Xen Store.";
518 # The hidden options below have no upstream documentation whatsoever.
519 # The nb* options appear to alter the log rotation behaviour, and
520 # the specialOps option appears to affect the Xenbus logging logic.
521 nbFiles = lib.options.mkOption {
524 type = lib.types.int;
526 description = "Set `xenstored-log-nb-files`.";
530 file = lib.options.mkOption {
531 default = "/var/log/xen/xenstored-access.log";
532 example = "/var/log/security/xenstored-access.log";
533 type = lib.types.path;
534 description = "Path to the Xen Store access log file.";
536 nbLines = lib.options.mkOption {
539 type = lib.types.int;
541 description = "Set `access-log-nb-lines`.";
543 nbChars = lib.options.mkOption {
546 type = lib.types.int;
548 description = "Set `acesss-log-nb-chars`.";
550 specialOps = lib.options.mkOption {
553 type = lib.types.bool;
555 description = "Set `access-log-special-ops`.";
559 kva = lib.options.mkOption {
560 default = "/proc/xen/xsd_kva";
561 example = cfg.store.settings.xenstored.xenfs.kva;
562 type = lib.types.path;
565 Path to the Xen Store Daemon KVA location inside the XenFS pseudo-filesystem.
566 While it is possible to alter this value, some drivers may be hardcoded to follow the default paths.
569 port = lib.options.mkOption {
570 default = "/proc/xen/xsd_port";
571 example = cfg.store.settings.xenstored.xenfs.port;
572 type = lib.types.path;
575 Path to the Xen Store Daemon userspace port inside the XenFS pseudo-filesystem.
576 While it is possible to alter this value, some drivers may be hardcoded to follow the default paths.
581 ringScanInterval = lib.options.mkOption {
584 type = lib.types.addCheck (
588 description = "nonzero signed integer, meaning !=0";
589 descriptionClass = "nonRestrictiveClause";
593 Perodic scanning for all the rings as a safenet for lazy clients.
594 Define the interval in seconds; set to a negative integer to disable.
605 config = lib.modules.mkIf cfg.enable {
608 assertion = pkgs.stdenv.hostPlatform.isx86_64;
609 message = "Xen is currently not supported on ${pkgs.stdenv.hostPlatform.system}.";
613 config.boot.loader.systemd-boot.enable
614 || (config.boot ? lanzaboote) && config.boot.lanzaboote.enable;
615 message = "Xen only supports booting on systemd-boot or Lanzaboote.";
618 assertion = config.boot.initrd.systemd.enable;
619 message = "Xen does not support the legacy script-based Stage 1 initrd.";
622 assertion = cfg.dom0Resources.maxMemory >= cfg.dom0Resources.memory;
624 You have allocated more memory to dom0 than virtualisation.xen.dom0Resources.maxMemory
625 allows for. Please increase the maximum memory limit, or decrease the default memory allocation.
629 assertion = cfg.debug -> cfg.trace;
630 message = "Xen's debugging features are enabled, but logging is disabled. This is most likely not what you want.";
633 assertion = cfg.store.settings.quota.maxWatchEvents >= cfg.store.settings.quota.maxOutstanding;
635 Upstream Xen recommends that maxWatchEvents be equal to or greater than maxOutstanding,
636 in order to mitigate denial of service attacks from malicious frontends.
641 virtualisation.xen.bootParams =
642 lib.lists.optionals cfg.trace [
647 lib.lists.optional (cfg.dom0Resources.memory != 0)
648 "dom0_mem=${toString cfg.dom0Resources.memory}M${
649 lib.strings.optionalString (
650 cfg.dom0Resources.memory != cfg.dom0Resources.maxMemory
651 ) ",max:${toString cfg.dom0Resources.maxMemory}M"
653 ++ lib.lists.optional (
654 cfg.dom0Resources.maxVCPUs != 0
655 ) "dom0_max_vcpus=${toString cfg.dom0Resources.maxVCPUs}";
683 # The xenfs module is needed to mount /proc/xen.
684 initrd.kernelModules = [ "xenfs" ];
686 # Increase the number of loopback devices from the default (8),
687 # which is way too small because every VM virtual disk requires a
689 extraModprobeConfig = ''
690 options loop max_loop=64
693 # Xen Bootspec extension. This extension allows NixOS bootloaders to
694 # fetch the `xen.efi` path and access the `cfg.bootParams` option.
695 bootspec.extensions = {
696 "org.xenproject.bootspec.v1" = {
698 xenParams = cfg.bootParams;
702 # See the `xenBootBuilder` script in the main `let...in` statement of this file.
703 loader.systemd-boot.extraInstallCommands = ''
704 ${lib.meta.getExe xenBootBuilder} ${cfg.efi.bootBuilderVerbosity}
708 # Domain 0 requires a pvops-enabled kernel.
709 # All NixOS kernels come with this enabled by default; this is merely a sanity check.
710 system.requiredKernelConfig = with config.lib.kernelConfig; [
712 (isYes "X86_IO_APIC")
716 (isYes "XEN_DEV_EVTCHN")
718 (isYes "XEN_COMPAT_XENFS")
719 (isYes "XEN_SYS_HYPERVISOR")
721 (isYes "XEN_BACKEND")
722 (isModule "XEN_NETDEV_BACKEND")
723 (isModule "XEN_BLKDEV_BACKEND")
724 (isModule "XEN_PCIDEV_BACKEND")
725 (isYes "XEN_BALLOON")
726 (isYes "XEN_SCRUB_PAGES")
735 # Set up Xen Domain 0 configuration files.
737 "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.
738 "xen/scripts-xen" = {
739 source = "${cfg.package}/etc/xen/scripts/*";
740 target = "xen/scripts";
742 "default/xencommons".text = ''
743 source ${cfg.package}/etc/default/xencommons
745 XENSTORED="${cfg.store.path}"
746 QEMU_XEN="${cfg.qemu.package}/${cfg.qemu.package.qemu-system-i386}"
747 ${lib.strings.optionalString cfg.trace ''
749 XENCONSOLED_TRACE=all
752 "default/xendomains".text = ''
753 source ${cfg.package}/etc/default/xendomains
755 ${cfg.domains.extraConfig}
758 # The OCaml-based Xen Store Daemon requires /etc/xen/oxenstored.conf to start.
759 // lib.attrsets.optionalAttrs (cfg.store.type == "ocaml") {
760 "xen/oxenstored.conf".text = ''
761 pid-file = ${cfg.store.settings.pidFile}
762 test-eagain = ${lib.trivial.boolToString cfg.store.settings.testEAGAIN}
763 merge-activate = ${toString cfg.store.settings.enableMerge}
764 conflict-burst-limit = ${toString cfg.store.settings.conflict.burstLimit}
765 conflict-max-history-seconds = ${toString cfg.store.settings.conflict.maxHistorySeconds}
766 conflict-rate-limit-is-aggregate = ${toString cfg.store.settings.conflict.rateLimitIsAggregate}
767 perms-activate = ${toString cfg.store.settings.perms.enable}
768 perms-watch-activate = ${toString cfg.store.settings.perms.enableWatch}
769 quota-activate = ${toString cfg.store.settings.quota.enable}
770 quota-maxentity = ${toString cfg.store.settings.quota.maxEntity}
771 quota-maxsize = ${toString cfg.store.settings.quota.maxSize}
772 quota-maxwatch = ${toString cfg.store.settings.quota.maxWatch}
773 quota-transaction = ${toString cfg.store.settings.quota.transaction}
774 quota-maxrequests = ${toString cfg.store.settings.quota.maxRequests}
775 quota-path-max = ${toString cfg.store.settings.quota.maxPath}
776 quota-maxoutstanding = ${toString cfg.store.settings.quota.maxOutstanding}
777 quota-maxwatchevents = ${toString cfg.store.settings.quota.maxWatchEvents}
778 persistent = ${lib.trivial.boolToString cfg.store.settings.persistent}
779 xenstored-log-file = ${cfg.store.settings.xenstored.log.file}
780 xenstored-log-level = ${
781 if isNull cfg.store.settings.xenstored.log.level then
784 cfg.store.settings.xenstored.log.level
786 xenstored-log-nb-files = ${toString cfg.store.settings.xenstored.log.nbFiles}
787 access-log-file = ${cfg.store.settings.xenstored.accessLog.file}
788 access-log-nb-lines = ${toString cfg.store.settings.xenstored.accessLog.nbLines}
789 acesss-log-nb-chars = ${toString cfg.store.settings.xenstored.accessLog.nbChars}
790 access-log-special-ops = ${lib.trivial.boolToString cfg.store.settings.xenstored.accessLog.specialOps}
791 ring-scan-interval = ${toString cfg.store.settings.ringScanInterval}
792 xenstored-kva = ${cfg.store.settings.xenstored.xenfs.kva}
793 xenstored-port = ${cfg.store.settings.xenstored.xenfs.port}
798 # Xen provides udev rules.
799 services.udev.packages = [ cfg.package ];
802 # Xen provides systemd units.
803 packages = [ cfg.package ];
807 description = "Mount /proc/xen files";
812 ConditionPathExists = "/proc/xen";
813 RefuseManualStop = "true";
820 # While this service is installed by the `xen` package, it shouldn't be used in dom0.
821 xendriverdomain.enable = false;
824 wantedBy = [ "multi-user.target" ];
826 export XENSTORED_ROOTDIR="/var/lib/xenstored"
827 rm -f "$XENSTORED_ROOTDIR"/tdb* &>/dev/null
828 mkdir -p /var/{run,log,lib}/xen
833 restartIfChanged = false;
834 wantedBy = [ "multi-user.target" ];
837 xen-qemu-dom0-disk-backend = {
838 wantedBy = [ "multi-user.target" ];
840 PIDFile = cfg.qemu.pidFile;
842 ${cfg.qemu.package}/${cfg.qemu.package.qemu-system-i386} \
843 -xen-domid 0 -xen-attach -name dom0 -nographic -M xenpv \
844 -daemonize -monitor /dev/null -serial /dev/null -parallel \
845 /dev/null -nodefaults -no-user-config -pidfile \
851 xenconsoled.wantedBy = [ "multi-user.target" ];
854 wantedBy = [ "multi-user.target" ];
857 Restart = "on-failure";
862 restartIfChanged = false;
867 preStart = "mkdir -p /var/lock/subsys -m 755";
868 wantedBy = [ "multi-user.target" ];
873 meta.maintainers = with lib.maintainers; [ sigmasquadron ];