876 lines
30 KiB
Nix
876 lines
30 KiB
Nix
|
# Xen Project Hypervisor (Dom0) support.
|
||
|
|
||
|
{
|
||
|
config,
|
||
|
lib,
|
||
|
pkgs,
|
||
|
...
|
||
|
}:
|
||
|
|
||
|
let
|
||
|
inherit (builtins) readFile;
|
||
|
inherit (lib.modules) mkRemovedOptionModule mkRenamedOptionModule mkIf;
|
||
|
inherit (lib.options)
|
||
|
mkOption
|
||
|
mkEnableOption
|
||
|
literalExpression
|
||
|
mkPackageOption
|
||
|
;
|
||
|
inherit (lib.types)
|
||
|
listOf
|
||
|
str
|
||
|
ints
|
||
|
lines
|
||
|
enum
|
||
|
path
|
||
|
submodule
|
||
|
addCheck
|
||
|
float
|
||
|
bool
|
||
|
int
|
||
|
nullOr
|
||
|
;
|
||
|
inherit (lib.lists) optional optionals;
|
||
|
inherit (lib.strings) hasSuffix optionalString;
|
||
|
inherit (lib.meta) getExe;
|
||
|
inherit (lib.attrsets) optionalAttrs;
|
||
|
inherit (lib.trivial) boolToString;
|
||
|
inherit (lib.teams.xen) members;
|
||
|
|
||
|
cfg = config.virtualisation.xen;
|
||
|
|
||
|
xenBootBuilder = pkgs.writeShellApplication {
|
||
|
name = "xenBootBuilder";
|
||
|
runtimeInputs =
|
||
|
(with pkgs; [
|
||
|
binutils
|
||
|
coreutils
|
||
|
findutils
|
||
|
gawk
|
||
|
gnugrep
|
||
|
gnused
|
||
|
jq
|
||
|
])
|
||
|
++ optionals (cfg.efi.bootBuilderVerbosity == "info") (
|
||
|
with pkgs;
|
||
|
[
|
||
|
bat
|
||
|
diffutils
|
||
|
]
|
||
|
);
|
||
|
runtimeEnv = {
|
||
|
efiMountPoint = config.boot.loader.efi.efiSysMountPoint;
|
||
|
};
|
||
|
|
||
|
# We disable SC2016 because we don't want to expand the regexes in the sed commands.
|
||
|
excludeShellChecks = [ "SC2016" ];
|
||
|
|
||
|
text = readFile ./xen-boot-builder.sh;
|
||
|
};
|
||
|
in
|
||
|
|
||
|
{
|
||
|
imports = [
|
||
|
(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 ##
|
||
|
|
||
|
options.virtualisation.xen = {
|
||
|
|
||
|
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";
|
||
|
|
||
|
debug = mkEnableOption "Xen debug features for Domain 0. This option enables some hidden debugging tests and features, and should not be used in production";
|
||
|
|
||
|
trace = mkOption {
|
||
|
type = bool;
|
||
|
default = cfg.debug;
|
||
|
defaultText = literalExpression "false";
|
||
|
example = true;
|
||
|
description = "Whether to enable Xen debug tracing and logging for Domain 0.";
|
||
|
};
|
||
|
|
||
|
package = mkPackageOption pkgs "Xen Hypervisor" { default = [ "xen" ]; };
|
||
|
|
||
|
qemu = {
|
||
|
package = mkPackageOption pkgs "QEMU (with Xen Hypervisor support)" {
|
||
|
default = [ "qemu_xen" ];
|
||
|
};
|
||
|
pidFile = mkOption {
|
||
|
type = path;
|
||
|
default = "/run/xen/qemu-dom0.pid";
|
||
|
example = "/var/run/xen/qemu-dom0.pid";
|
||
|
description = "Path to the QEMU PID file.";
|
||
|
};
|
||
|
};
|
||
|
|
||
|
bootParams = mkOption {
|
||
|
default = [ ];
|
||
|
example = ''
|
||
|
[
|
||
|
"iommu=force:true,qinval:true,debug:true"
|
||
|
"noreboot=true"
|
||
|
"vga=ask"
|
||
|
]
|
||
|
'';
|
||
|
type = listOf 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.
|
||
|
'';
|
||
|
};
|
||
|
|
||
|
efi = {
|
||
|
bootBuilderVerbosity = mkOption {
|
||
|
type = 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:
|
||
|
|
||
|
- `quiet` supresses all messages.
|
||
|
|
||
|
- `default` adds a simple "Installing Xen Project 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`.
|
||
|
|
||
|
- `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.
|
||
|
'';
|
||
|
};
|
||
|
|
||
|
path = mkOption {
|
||
|
type = path;
|
||
|
default = "${cfg.package.boot}/${cfg.package.efi}";
|
||
|
defaultText = literalExpression "\${config.virtualisation.xen.package.boot}/\${config.virtualisation.xen.package.efi}";
|
||
|
example = 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.
|
||
|
'';
|
||
|
};
|
||
|
};
|
||
|
|
||
|
dom0Resources = {
|
||
|
maxVCPUs = mkOption {
|
||
|
default = 0;
|
||
|
example = 4;
|
||
|
type = 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 = mkOption {
|
||
|
default = 0;
|
||
|
example = 512;
|
||
|
type = 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 = mkOption {
|
||
|
default = cfg.dom0Resources.memory;
|
||
|
defaultText = literalExpression "config.virtualisation.xen.dom0Resources.memory";
|
||
|
example = 1024;
|
||
|
type = 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.
|
||
|
'';
|
||
|
};
|
||
|
};
|
||
|
|
||
|
domains = {
|
||
|
extraConfig = mkOption {
|
||
|
type = 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.
|
||
|
'';
|
||
|
};
|
||
|
};
|
||
|
|
||
|
store = {
|
||
|
path = mkOption {
|
||
|
type = path;
|
||
|
default = "${cfg.package}/bin/oxenstored";
|
||
|
defaultText = literalExpression "\${config.virtualisation.xen.package}/bin/oxenstored";
|
||
|
example = 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 = mkOption {
|
||
|
type = enum [
|
||
|
"c"
|
||
|
"ocaml"
|
||
|
];
|
||
|
default = if (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 = 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 = submodule {
|
||
|
options = {
|
||
|
pidFile = mkOption {
|
||
|
default = "/run/xen/xenstored.pid";
|
||
|
example = "/var/run/xen/xenstored.pid";
|
||
|
type = path;
|
||
|
description = "Path to the Xen Store Daemon PID file.";
|
||
|
};
|
||
|
testEAGAIN = mkOption {
|
||
|
default = cfg.debug;
|
||
|
defaultText = literalExpression "config.virtualisation.xen.debug";
|
||
|
example = true;
|
||
|
type = bool;
|
||
|
visible = false;
|
||
|
description = "Randomly fail a transaction with EAGAIN. This option is used for debugging purposes only.";
|
||
|
};
|
||
|
enableMerge = mkOption {
|
||
|
default = true;
|
||
|
example = false;
|
||
|
type = bool;
|
||
|
description = "Whether to enable transaction merge support.";
|
||
|
};
|
||
|
conflict = {
|
||
|
burstLimit = mkOption {
|
||
|
default = 5.0;
|
||
|
example = 15.0;
|
||
|
type = addCheck (
|
||
|
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 = mkOption {
|
||
|
default = 5.0e-2;
|
||
|
example = 1.0;
|
||
|
type = addCheck (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 = mkOption {
|
||
|
default = true;
|
||
|
example = false;
|
||
|
type = 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 = mkOption {
|
||
|
default = true;
|
||
|
example = false;
|
||
|
type = bool;
|
||
|
description = "Whether to enable the node permission system.";
|
||
|
};
|
||
|
enableWatch = mkOption {
|
||
|
default = true;
|
||
|
example = false;
|
||
|
type = 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 = mkOption {
|
||
|
default = true;
|
||
|
example = false;
|
||
|
type = bool;
|
||
|
description = "Whether to enable the quota system.";
|
||
|
};
|
||
|
maxEntity = mkOption {
|
||
|
default = 1000;
|
||
|
example = 1024;
|
||
|
type = ints.positive;
|
||
|
description = "Entity limit for transactions.";
|
||
|
};
|
||
|
maxSize = mkOption {
|
||
|
default = 2048;
|
||
|
example = 4096;
|
||
|
type = ints.positive;
|
||
|
description = "Size limit for transactions.";
|
||
|
};
|
||
|
maxWatch = mkOption {
|
||
|
default = 100;
|
||
|
example = 256;
|
||
|
type = ints.positive;
|
||
|
description = "Maximum number of watches by the Xenstore Watchdog.";
|
||
|
};
|
||
|
transaction = mkOption {
|
||
|
default = 10;
|
||
|
example = 50;
|
||
|
type = ints.positive;
|
||
|
description = "Maximum number of transactions.";
|
||
|
};
|
||
|
maxRequests = mkOption {
|
||
|
default = 1024;
|
||
|
example = 1024;
|
||
|
type = ints.positive;
|
||
|
description = "Maximum number of requests per transaction.";
|
||
|
};
|
||
|
maxPath = mkOption {
|
||
|
default = 1024;
|
||
|
example = 1024;
|
||
|
type = ints.positive;
|
||
|
description = "Path limit for the quota system.";
|
||
|
};
|
||
|
maxOutstanding = mkOption {
|
||
|
default = 1024;
|
||
|
example = 1024;
|
||
|
type = ints.positive;
|
||
|
description = "Maximum outstanding requests, i.e. in-flight requests / domain.";
|
||
|
};
|
||
|
maxWatchEvents = mkOption {
|
||
|
default = 1024;
|
||
|
example = 2048;
|
||
|
type = ints.positive;
|
||
|
description = "Maximum number of outstanding watch events per watch.";
|
||
|
};
|
||
|
};
|
||
|
persistent = mkOption {
|
||
|
default = false;
|
||
|
example = true;
|
||
|
type = bool;
|
||
|
description = "Whether to activate the filed base backend.";
|
||
|
};
|
||
|
xenstored = {
|
||
|
log = {
|
||
|
file = mkOption {
|
||
|
default = "/var/log/xen/xenstored.log";
|
||
|
example = "/dev/null";
|
||
|
type = path;
|
||
|
description = "Path to the Xen Store log file.";
|
||
|
};
|
||
|
level = mkOption {
|
||
|
default = if cfg.trace then "debug" else null;
|
||
|
defaultText = literalExpression "if (config.virtualisation.xen.trace == true) then \"debug\" else null";
|
||
|
example = "error";
|
||
|
type = nullOr (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 = mkOption {
|
||
|
default = 10;
|
||
|
example = 16;
|
||
|
type = int;
|
||
|
visible = false;
|
||
|
description = "Set `xenstored-log-nb-files`.";
|
||
|
};
|
||
|
};
|
||
|
accessLog = {
|
||
|
file = mkOption {
|
||
|
default = "/var/log/xen/xenstored-access.log";
|
||
|
example = "/var/log/security/xenstored-access.log";
|
||
|
type = path;
|
||
|
description = "Path to the Xen Store access log file.";
|
||
|
};
|
||
|
nbLines = mkOption {
|
||
|
default = 13215;
|
||
|
example = 16384;
|
||
|
type = int;
|
||
|
visible = false;
|
||
|
description = "Set `access-log-nb-lines`.";
|
||
|
};
|
||
|
nbChars = mkOption {
|
||
|
default = 180;
|
||
|
example = 256;
|
||
|
type = int;
|
||
|
visible = false;
|
||
|
description = "Set `acesss-log-nb-chars`.";
|
||
|
};
|
||
|
specialOps = mkOption {
|
||
|
default = false;
|
||
|
example = true;
|
||
|
type = bool;
|
||
|
visible = false;
|
||
|
description = "Set `access-log-special-ops`.";
|
||
|
};
|
||
|
};
|
||
|
xenfs = {
|
||
|
kva = mkOption {
|
||
|
default = "/proc/xen/xsd_kva";
|
||
|
example = cfg.store.settings.xenstored.xenfs.kva;
|
||
|
type = 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 = mkOption {
|
||
|
default = "/proc/xen/xsd_port";
|
||
|
example = cfg.store.settings.xenstored.xenfs.port;
|
||
|
type = 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 = mkOption {
|
||
|
default = 20;
|
||
|
example = 30;
|
||
|
type = addCheck (
|
||
|
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.
|
||
|
'';
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
|
||
|
## Implementation ##
|
||
|
|
||
|
config = mkIf cfg.enable {
|
||
|
assertions = [
|
||
|
{
|
||
|
assertion = pkgs.stdenv.hostPlatform.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.
|
||
|
'';
|
||
|
}
|
||
|
];
|
||
|
|
||
|
virtualisation.xen.bootParams =
|
||
|
optionals cfg.trace [
|
||
|
"loglvl=all"
|
||
|
"guest_loglvl=all"
|
||
|
]
|
||
|
++
|
||
|
optional (cfg.dom0Resources.memory != 0)
|
||
|
"dom0_mem=${toString cfg.dom0Resources.memory}M${
|
||
|
optionalString (
|
||
|
cfg.dom0Resources.memory != cfg.dom0Resources.maxMemory
|
||
|
) ",max:${toString cfg.dom0Resources.maxMemory}M"
|
||
|
}"
|
||
|
++ 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 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.
|
||
|
extraModprobeConfig = ''
|
||
|
options loop max_loop=64
|
||
|
'';
|
||
|
|
||
|
# 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;
|
||
|
};
|
||
|
};
|
||
|
|
||
|
# See the `xenBootBuilder` script in the main `let...in` statement of this file.
|
||
|
loader.systemd-boot.extraInstallCommands = ''
|
||
|
${getExe xenBootBuilder} ${cfg.efi.bootBuilderVerbosity}
|
||
|
'';
|
||
|
};
|
||
|
|
||
|
# Domain 0 requires a pvops-enabled kernel.
|
||
|
# 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}"
|
||
|
${optionalString cfg.trace ''
|
||
|
XENSTORED_TRACE=yes
|
||
|
XENCONSOLED_TRACE=all
|
||
|
''}
|
||
|
'';
|
||
|
"default/xendomains".text = ''
|
||
|
source ${cfg.package}/etc/default/xendomains
|
||
|
|
||
|
${cfg.domains.extraConfig}
|
||
|
'';
|
||
|
}
|
||
|
# The OCaml-based Xen Store Daemon requires /etc/xen/oxenstored.conf to start.
|
||
|
// optionalAttrs (cfg.store.type == "ocaml") {
|
||
|
"xen/oxenstored.conf".text = ''
|
||
|
pid-file = ${cfg.store.settings.pidFile}
|
||
|
test-eagain = ${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 = ${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 = ${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 ];
|
||
|
|
||
|
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";
|
||
|
};
|
||
|
}
|
||
|
];
|
||
|
|
||
|
services = {
|
||
|
|
||
|
# While this service is installed by the `xen` package, it shouldn't be used in dom0.
|
||
|
xendriverdomain.enable = false;
|
||
|
|
||
|
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
|
||
|
'';
|
||
|
};
|
||
|
|
||
|
xen-init-dom0 = {
|
||
|
restartIfChanged = false;
|
||
|
wantedBy = [ "multi-user.target" ];
|
||
|
};
|
||
|
|
||
|
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" ];
|
||
|
|
||
|
xen-watchdog = {
|
||
|
wantedBy = [ "multi-user.target" ];
|
||
|
serviceConfig = {
|
||
|
RestartSec = "1";
|
||
|
Restart = "on-failure";
|
||
|
};
|
||
|
};
|
||
|
|
||
|
xendomains = {
|
||
|
restartIfChanged = false;
|
||
|
path = [
|
||
|
cfg.package
|
||
|
cfg.qemu.package
|
||
|
];
|
||
|
preStart = "mkdir -p /var/lock/subsys -m 755";
|
||
|
wantedBy = [ "multi-user.target" ];
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
meta.maintainers = members;
|
||
|
}
|