Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1188 lines
41 KiB
Nix
1188 lines
41 KiB
Nix
# Option definitions for vmsilo NixOS module
|
|
{
|
|
config,
|
|
pkgs,
|
|
lib,
|
|
...
|
|
}:
|
|
|
|
let
|
|
cfg = config.programs.vmsilo;
|
|
|
|
# Shared route submodule type for IPv4/IPv6 routes
|
|
routeSubmodule = lib.types.attrsOf (
|
|
lib.types.submodule {
|
|
options.via = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "Gateway address for this route.";
|
|
};
|
|
}
|
|
);
|
|
|
|
# Network interface configuration submodule (for vm.network.interfaces)
|
|
networkInterfaceSubmodule = lib.types.submodule {
|
|
options = {
|
|
type = lib.mkOption {
|
|
type = lib.types.enum [
|
|
"tap"
|
|
];
|
|
default = "tap";
|
|
description = "Interface type: 'tap' for host tap interface.";
|
|
};
|
|
|
|
macAddress = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = "MAC address. Auto-generated from SHA1('<vmName>-<ifName>') if null.";
|
|
example = "52:ab:cd:ef:12:34";
|
|
};
|
|
|
|
tap = lib.mkOption {
|
|
type = lib.types.nullOr (
|
|
lib.types.submodule {
|
|
options = {
|
|
name = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = "TAP interface name on host. Defaults to '<vmname>-<ifIndex>'.";
|
|
};
|
|
hostAddress = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = "Host-side IP address with prefix (e.g., '10.0.0.254/24'). If null, the TAP is created without a host IP. Mutually exclusive with bridge.";
|
|
example = "10.0.0.254/24";
|
|
};
|
|
bridge = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = "Bridge name to add this TAP interface to (via networking.bridges). Mutually exclusive with hostAddress.";
|
|
example = "br0";
|
|
};
|
|
};
|
|
}
|
|
);
|
|
default = null;
|
|
description = "TAP interface configuration. Required when type = 'tap'.";
|
|
};
|
|
|
|
dhcp = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "Enable DHCP for this interface.";
|
|
};
|
|
|
|
addresses = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
description = "Static IPv4 addresses with prefix (e.g., '10.0.0.1/24').";
|
|
example = [ "10.0.0.1/24" ];
|
|
};
|
|
|
|
routes = lib.mkOption {
|
|
type = routeSubmodule;
|
|
default = { };
|
|
description = "IPv4 routes. Keys are destination CIDRs, values have 'via' gateway.";
|
|
example = lib.literalExpression ''{ "0.0.0.0/0" = { via = "10.0.0.254"; }; }'';
|
|
};
|
|
|
|
v6Addresses = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
description = "Static IPv6 addresses with prefix.";
|
|
example = [ "fc00::2/64" ];
|
|
};
|
|
|
|
v6Routes = lib.mkOption {
|
|
type = routeSubmodule;
|
|
default = { };
|
|
description = "IPv6 routes. Keys are destination CIDRs, values have 'via' gateway.";
|
|
example = lib.literalExpression ''{ "::/0" = { via = "fc00::1"; }; }'';
|
|
};
|
|
};
|
|
};
|
|
|
|
# Network configuration submodule (for vm.network)
|
|
networkSubmodule = lib.types.submodule {
|
|
options = {
|
|
nameservers = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
description = "DNS nameservers for this VM.";
|
|
example = [
|
|
"8.8.8.8"
|
|
"1.1.1.1"
|
|
];
|
|
};
|
|
|
|
isNetvm = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = ''
|
|
Mark this VM as a network gateway (netvm). Other VMs may route through it
|
|
by setting network.netvm to this VM's name. Compatible with network.netvm —
|
|
a VM can be both a netvm and itself behind another netvm.
|
|
'';
|
|
};
|
|
|
|
netvm = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = ''
|
|
Name of the netvm VM to route through, or "host" to route through
|
|
the host machine directly.
|
|
|
|
When set to a VM name, the named VM must have network.isNetvm = true.
|
|
Auto-generates:
|
|
- Interface "upstream" on this VM (with IP and default route)
|
|
- Interface "<this-vm-name>" on the netvm (with NAT and forward rules)
|
|
- A host bridge connecting both sides
|
|
|
|
When set to "host", the host acts as the gateway with NAT masquerade.
|
|
Auto-generates:
|
|
- Interface "upstream" on this VM (direct TAP with host-side IP)
|
|
- nftables NAT and forward rules on the host
|
|
'';
|
|
example = "router";
|
|
};
|
|
|
|
netvmSubnet = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = ''
|
|
Override the auto-allocated /31 subnet for this VM's netvm link.
|
|
Specify as the client (this VM's) IP with /31 prefix.
|
|
The netvm takes the next IP (+1).
|
|
Use this to resolve hash collisions or pin specific addresses.
|
|
'';
|
|
example = "10.200.5.2/31";
|
|
};
|
|
|
|
interfaces = lib.mkOption {
|
|
type = lib.types.attrsOf networkInterfaceSubmodule;
|
|
default = { };
|
|
description = "Network interfaces for this VM. Keys are guest-visible interface names.";
|
|
};
|
|
};
|
|
};
|
|
|
|
# Type for attribute values in crosvm argument attrsets
|
|
attrValueType = lib.types.oneOf [
|
|
lib.types.str
|
|
lib.types.int
|
|
lib.types.bool
|
|
];
|
|
|
|
# Type for crosvm argument attrsets (used for gpu, sound, etc.)
|
|
crosvmAttrsetType = lib.types.attrsOf attrValueType;
|
|
|
|
# Type for free-form attrsets (disks, pciDevices, sharedDirectories, vhostUser)
|
|
freeformAttrsetType = lib.types.attrsOf (lib.types.nullOr attrValueType);
|
|
|
|
vmSubmodule = lib.types.submodule (
|
|
{ name, ... }:
|
|
{
|
|
options = {
|
|
name = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = name;
|
|
readOnly = true;
|
|
description = "VM name (derived from attribute name).";
|
|
};
|
|
|
|
color = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "darkred";
|
|
description = ''
|
|
Window decoration color for this VM. Used in wayland security context.
|
|
Supported formats: named colors ("red", "green") or hex ("#FF0000").
|
|
'';
|
|
example = "#3498db";
|
|
};
|
|
|
|
dbus = {
|
|
notifications = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = "Enable notification proxying from this VM to the host.";
|
|
};
|
|
tray = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "Enable system tray proxying from this VM to the host.";
|
|
};
|
|
};
|
|
|
|
hypervisor = lib.mkOption {
|
|
type = lib.types.enum [
|
|
"crosvm"
|
|
"cloud-hypervisor"
|
|
];
|
|
default = "crosvm";
|
|
description = "VMM to use for this VM. 'crosvm' (default) or 'cloud-hypervisor'.";
|
|
};
|
|
|
|
waylandProxy = lib.mkOption {
|
|
type = lib.types.enum [
|
|
"wayland-proxy-virtwl"
|
|
"sommelier"
|
|
];
|
|
default = "wayland-proxy-virtwl";
|
|
description = "Wayland proxy to use in the guest VM.";
|
|
example = "sommelier";
|
|
};
|
|
|
|
memory = lib.mkOption {
|
|
type = lib.types.int;
|
|
default = 1024;
|
|
description = "Memory allocation in MB.";
|
|
example = 4096;
|
|
};
|
|
|
|
cpus = lib.mkOption {
|
|
type = lib.types.int;
|
|
default = 2;
|
|
description = "Number of virtual CPUs.";
|
|
example = 4;
|
|
};
|
|
|
|
network = lib.mkOption {
|
|
type = networkSubmodule;
|
|
default = { };
|
|
description = "Network configuration for this VM.";
|
|
example = lib.literalExpression ''
|
|
{
|
|
nameservers = [ "8.8.8.8" ];
|
|
interfaces = {
|
|
wan = {
|
|
type = "tap";
|
|
tap.hostAddress = "10.0.0.254/24";
|
|
addresses = [ "10.0.0.1/24" ];
|
|
routes."0.0.0.0/0" = { via = "10.0.0.254"; };
|
|
};
|
|
};
|
|
}
|
|
'';
|
|
};
|
|
|
|
autoShutdown = lib.mkOption {
|
|
type = lib.types.submodule {
|
|
options = {
|
|
enable = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "Enable auto-shutdown when idle. VM shuts down after autoShutdown.after seconds with no active commands.";
|
|
};
|
|
|
|
after = lib.mkOption {
|
|
type = lib.types.int;
|
|
default = 60;
|
|
description = "Seconds to wait after last command exits before shutting down.";
|
|
};
|
|
};
|
|
};
|
|
default = { };
|
|
description = "Auto-shutdown configuration for idle VMs.";
|
|
};
|
|
|
|
autoStart = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "Start this VM automatically instead of on-demand via socket activation. GPU VMs start when the graphical session begins; non-GPU VMs start at boot.";
|
|
};
|
|
|
|
rootOverlay = lib.mkOption {
|
|
type = lib.types.submodule {
|
|
options = {
|
|
type = lib.mkOption {
|
|
type = lib.types.enum [
|
|
"raw"
|
|
"tmpfs"
|
|
];
|
|
default = "raw";
|
|
description = "Overlay upper layer type: 'raw' (disk-backed, default) or 'tmpfs' (RAM-backed).";
|
|
};
|
|
|
|
size = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "10G";
|
|
description = "Maximum size of ephemeral disk (raw only). Parsed by truncate.";
|
|
example = "20G";
|
|
};
|
|
};
|
|
};
|
|
default = { };
|
|
description = "Root overlay configuration. Controls where VM writes go.";
|
|
};
|
|
|
|
dependsOn = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
description = "List of VM names to start when this VM starts.";
|
|
};
|
|
|
|
additionalDisks = lib.mkOption {
|
|
type = lib.types.listOf freeformAttrsetType;
|
|
default = [ ];
|
|
description = ''
|
|
Additional disks to attach to the VM. Each attrset requires `path` (positional).
|
|
Other keys are passed directly to crosvm --block (e.g., ro, sparse, block-size, id).
|
|
'';
|
|
example = lib.literalExpression ''
|
|
[{
|
|
path = "/tmp/data.qcow2";
|
|
ro = false;
|
|
sparse = true;
|
|
block-size = 4096;
|
|
id = "datadisk";
|
|
}]
|
|
'';
|
|
};
|
|
|
|
rootDisk = lib.mkOption {
|
|
type = lib.types.nullOr freeformAttrsetType;
|
|
default = null;
|
|
description = ''
|
|
Custom root disk. If not set, uses the built rootfs image with rootDiskReadonly.
|
|
Requires `path` (positional). Other keys are passed directly to crosvm --block.
|
|
'';
|
|
example = lib.literalExpression ''
|
|
{
|
|
path = "/path/to/custom-root.qcow2";
|
|
ro = true;
|
|
}
|
|
'';
|
|
};
|
|
|
|
kernel = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
description = "Custom kernel image. If not set, uses the built rootfs kernel.";
|
|
example = "/path/to/bzImage";
|
|
};
|
|
|
|
initramfs = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
description = "Custom initramfs. If not set, uses the built rootfs initrd.";
|
|
example = "/path/to/initrd";
|
|
};
|
|
|
|
rootDiskReadonly = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = "Whether the built rootfs should be read-only. Ignored when rootDisk is set.";
|
|
};
|
|
|
|
copyChannel = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "Include a NixOS channel in the rootfs image, built from the same nixpkgs revision used to build the VM.";
|
|
};
|
|
|
|
sharedHome = lib.mkOption {
|
|
type = lib.types.either lib.types.bool lib.types.str;
|
|
default = true;
|
|
description = ''
|
|
Share a host directory as /home/user in the guest via virtiofs.
|
|
Set to true for default path (/shared/<vmname>/home), a string for a custom host path, or false to disable.
|
|
'';
|
|
example = "/data/vms/banking-home";
|
|
};
|
|
|
|
kernelParams = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
description = "Extra kernel command line parameters.";
|
|
example = [
|
|
"acpi=off"
|
|
"debug"
|
|
];
|
|
};
|
|
|
|
gpu = lib.mkOption {
|
|
type = lib.types.either lib.types.bool (
|
|
lib.types.submodule {
|
|
options = {
|
|
wayland = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = "Enable cross-domain context type for Wayland passthrough.";
|
|
};
|
|
opengl = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = "Enable virgl2 context type for OpenGL acceleration.";
|
|
};
|
|
allowWX = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.bool;
|
|
default = null;
|
|
description = "Override global gpu.allowWX for this VM. null = inherit global.";
|
|
};
|
|
logLevel = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = "Log level for this VM's GPU device service. null = inherit global crosvm.logLevel.";
|
|
};
|
|
seccompPolicy = lib.mkOption {
|
|
type = lib.types.enum [
|
|
"enforcing"
|
|
"log"
|
|
];
|
|
default = "enforcing";
|
|
description = "Seccomp policy for the GPU device service. 'enforcing' blocks unlisted syscalls; 'log' only logs them.";
|
|
};
|
|
disableSandbox = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "Disable non-seccomp sandboxing for the GPU device service. Useful for debugging. Use gpu.seccompPolicy = \"log\" to also relax seccomp.";
|
|
};
|
|
};
|
|
}
|
|
);
|
|
default = true;
|
|
description = ''
|
|
GPU configuration. Set to false to disable, true for default config
|
|
(wayland + opengl), or an attrset to select specific GPU features.
|
|
'';
|
|
example = lib.literalExpression ''
|
|
{
|
|
wayland = true;
|
|
opengl = true;
|
|
}
|
|
'';
|
|
};
|
|
|
|
sound = {
|
|
playback = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = "Enable sound playback.";
|
|
};
|
|
capture = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "Enable sound capture.";
|
|
};
|
|
logLevel = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "info";
|
|
description = "RUST_LOG level for the sound device service.";
|
|
example = "debug";
|
|
};
|
|
seccompPolicy = lib.mkOption {
|
|
type = lib.types.enum [
|
|
"enforcing"
|
|
"log"
|
|
];
|
|
default = "enforcing";
|
|
description = "Seccomp policy for the sound device service. 'enforcing' blocks unlisted syscalls; 'log' only logs them.";
|
|
};
|
|
};
|
|
|
|
sharedDirectories = lib.mkOption {
|
|
type = lib.types.attrsOf (
|
|
lib.types.submodule {
|
|
options = {
|
|
path = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "Host directory path to share with the guest.";
|
|
};
|
|
threadPoolSize = lib.mkOption {
|
|
type = lib.types.int;
|
|
default = 0;
|
|
description = "Thread pool size for virtiofsd.";
|
|
};
|
|
xattr = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = "Enable extended attributes.";
|
|
};
|
|
posixAcl = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = "Enable POSIX ACLs. Incompatible with translateUid/translateGid.";
|
|
};
|
|
readonly = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "Share as read-only.";
|
|
};
|
|
inodeFileHandles = lib.mkOption {
|
|
type = lib.types.enum [
|
|
"never"
|
|
"prefer"
|
|
"mandatory"
|
|
];
|
|
default = "prefer";
|
|
description = "Inode file handles mode.";
|
|
};
|
|
cache = lib.mkOption {
|
|
type = lib.types.enum [
|
|
"auto"
|
|
"always"
|
|
"never"
|
|
"metadata"
|
|
];
|
|
default = "auto";
|
|
description = "Cache policy.";
|
|
};
|
|
allowMmap = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "Allow memory-mapped I/O.";
|
|
};
|
|
enableReaddirplus = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = "Enable readdirplus. When false, passes --no-readdirplus.";
|
|
};
|
|
writeback = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "Enable writeback caching.";
|
|
};
|
|
allowDirectIo = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "Allow direct I/O.";
|
|
};
|
|
logLevel = lib.mkOption {
|
|
type = lib.types.enum [
|
|
"error"
|
|
"warn"
|
|
"info"
|
|
"debug"
|
|
"trace"
|
|
"off"
|
|
];
|
|
default = "info";
|
|
description = "virtiofsd log level.";
|
|
};
|
|
killprivV2 = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = ''
|
|
Enable FUSE_HANDLE_KILLPRIV_V2. Avoids duplicating work around
|
|
stripping SUID/SGID bits off binaries when written to by non-root.
|
|
'';
|
|
};
|
|
uidMap = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = ''
|
|
Map a range of UIDs from the host into the namespace,
|
|
given as ":namespace_uid:host_uid:count:". Done by user namespace.
|
|
'';
|
|
};
|
|
gidMap = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = ''
|
|
Map a range of GIDs from the host into the namespace,
|
|
given as ":namespace_gid:host_gid:count:". Done by user namespace.
|
|
'';
|
|
};
|
|
translateUid = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = ''
|
|
Translate UIDs between guest and host internally by virtiofsd,
|
|
given as "<type>:<source base>:<target base>:<count>".
|
|
Incompatible with posixAcl.
|
|
'';
|
|
};
|
|
translateGid = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = ''
|
|
Translate GIDs between guest and host internally by virtiofsd,
|
|
given as "<type>:<source base>:<target base>:<count>".
|
|
Incompatible with posixAcl.
|
|
'';
|
|
};
|
|
preserveNoatime = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "Preserve O_NOATIME flag on files.";
|
|
};
|
|
mountPath = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = ''
|
|
Guest mount path. When set, the shared directory is automatically
|
|
mounted at this path inside the guest via systemd.mount-extra kernel
|
|
parameter. When null (default), the virtiofs tag is available but
|
|
not mounted automatically.
|
|
'';
|
|
example = "/mnt/shared";
|
|
};
|
|
};
|
|
}
|
|
);
|
|
default = { };
|
|
description = ''
|
|
Shared directories via virtiofsd. Keys are used as the virtiofs tag.
|
|
Each entry runs a dedicated virtiofsd process connected to crosvm via vhost-user.
|
|
'';
|
|
example = lib.literalExpression ''
|
|
{
|
|
home = {
|
|
path = "/tmp/host_shared_dir";
|
|
mountPath = "/mnt/shared";
|
|
xattr = true;
|
|
posixAcl = true;
|
|
};
|
|
}
|
|
'';
|
|
};
|
|
|
|
vhostUser = lib.mkOption {
|
|
type = lib.types.listOf freeformAttrsetType;
|
|
default = [ ];
|
|
description = ''
|
|
vhost-user devices for --vhost-user. Each attrset is formatted as key=value pairs.
|
|
'';
|
|
example = lib.literalExpression ''
|
|
[{
|
|
type = "net";
|
|
socket = "/path/to/vhost-user.sock";
|
|
}]
|
|
'';
|
|
};
|
|
|
|
pciDevices = lib.mkOption {
|
|
type = lib.types.listOf freeformAttrsetType;
|
|
default = [ ];
|
|
description = ''
|
|
PCI devices to pass through via --vfio. Each attrset requires `path` (BDF or sysfs path).
|
|
BDF format ("01:00.0" or "0000:01:00.0") is auto-converted to sysfs path.
|
|
Other keys are passed as key=value pairs (e.g., iommu=on).
|
|
Devices must also be listed in isolatedPciDevices.
|
|
'';
|
|
example = lib.literalExpression ''
|
|
[{
|
|
path = "01:00.0";
|
|
iommu = "on";
|
|
}]
|
|
'';
|
|
};
|
|
|
|
usbDevices = lib.mkOption {
|
|
type = lib.types.listOf (
|
|
lib.types.submodule {
|
|
options = {
|
|
vendorId = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "USB vendor ID (hex, e.g., '17ef').";
|
|
example = "17ef";
|
|
};
|
|
productId = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "USB product ID (hex, e.g., '60e0').";
|
|
example = "60e0";
|
|
};
|
|
serial = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = "USB serial number to match a specific device. If null, matches all devices with this VID:PID.";
|
|
example = "A02019100900";
|
|
};
|
|
};
|
|
}
|
|
);
|
|
default = [ ];
|
|
description = ''
|
|
USB devices to pass through to this VM. Devices are attached when the VM starts
|
|
and detached when it stops. Matched by VID:PID, optionally narrowed by serial number.
|
|
If multiple physical devices match a VID:PID (and no serial filter), all are attached.
|
|
'';
|
|
example = lib.literalExpression ''
|
|
[
|
|
{ vendorId = "17ef"; productId = "60e0"; }
|
|
{ vendorId = "046d"; productId = "c52b"; serial = "A02019100900"; }
|
|
]
|
|
'';
|
|
};
|
|
|
|
guestPrograms = lib.mkOption {
|
|
type = lib.types.listOf lib.types.package;
|
|
default = [ ];
|
|
description = "VM-specific packages to include in the rootfs.";
|
|
example = lib.literalExpression "[ pkgs.firefox ]";
|
|
};
|
|
|
|
guestConfig = lib.mkOption {
|
|
type = lib.types.either lib.types.deferredModule (lib.types.listOf lib.types.deferredModule);
|
|
default = [ ];
|
|
description = ''
|
|
VM-specific NixOS configuration modules.
|
|
|
|
Accepts a single NixOS module or a list of modules. Each module can be
|
|
an attribute set, a module function ({config, pkgs, lib, ...}: { ... }),
|
|
or a path to a module file.
|
|
'';
|
|
example = lib.literalExpression ''
|
|
# Single attrset
|
|
{ services.openssh.enable = true; }
|
|
|
|
# Single module function
|
|
{ config, pkgs, ... }: {
|
|
services.openssh.enable = true;
|
|
}
|
|
|
|
# List of modules
|
|
[
|
|
./my-guest-module.nix
|
|
{ services.openssh.enable = true; }
|
|
({ config, pkgs, ... }: { environment.systemPackages = [ pkgs.vim ]; })
|
|
]
|
|
'';
|
|
};
|
|
|
|
crosvm = lib.mkOption {
|
|
type = lib.types.submodule {
|
|
options = {
|
|
logLevel = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = "Per-VM log level override. If null, uses global crosvm.logLevel.";
|
|
example = "debug";
|
|
};
|
|
|
|
extraArgs = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
description = "Per-VM extra arguments passed to crosvm before 'run'. Appended to global crosvm.extraArgs.";
|
|
example = [
|
|
"--syslog-tag"
|
|
"banking"
|
|
];
|
|
};
|
|
|
|
extraRunArgs = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
description = "Per-VM extra arguments passed to crosvm after 'run'. Appended to global crosvm.extraRunArgs.";
|
|
example = [ "--disable-sandbox" ];
|
|
};
|
|
};
|
|
};
|
|
default = { };
|
|
description = "Per-VM crosvm configuration.";
|
|
};
|
|
|
|
cloud-hypervisor = lib.mkOption {
|
|
type = lib.types.submodule {
|
|
options = {
|
|
logLevel = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = "Log level override for this VM's cloud-hypervisor instance. null = inherit global.";
|
|
};
|
|
hugepages = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.bool;
|
|
default = null;
|
|
description = "Use hugetlbfs-backed memory for this VM. null = inherit global. Requires pre-allocated hugepages (vm.nr_hugepages).";
|
|
};
|
|
seccompPolicy = lib.mkOption {
|
|
type = lib.types.enum [
|
|
"enforcing"
|
|
"log"
|
|
];
|
|
default = "enforcing";
|
|
description = "Seccomp policy for this VM's cloud-hypervisor instance. \"enforcing\" kills on violation, \"log\" logs violations without killing.";
|
|
};
|
|
extraArgs = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
description = "Extra arguments to pass to cloud-hypervisor for this VM.";
|
|
};
|
|
extraConfig = lib.mkOption {
|
|
type = lib.types.attrs;
|
|
default = { };
|
|
description = "Extra attributes merged into the cloud-hypervisor JSON VM configuration.";
|
|
example = lib.literalExpression "{ rng = { src = \"/dev/urandom\"; }; }";
|
|
};
|
|
};
|
|
};
|
|
default = { };
|
|
description = "Per-VM cloud-hypervisor configuration overrides.";
|
|
};
|
|
|
|
};
|
|
}
|
|
);
|
|
|
|
in
|
|
{
|
|
options.programs.vmsilo = {
|
|
enable = lib.mkEnableOption "vmsilo VM management";
|
|
|
|
enableBashIntegration = lib.mkEnableOption "Bash completion for vmsilo commands" // {
|
|
default = true;
|
|
};
|
|
|
|
crosvm = lib.mkOption {
|
|
type = lib.types.submodule {
|
|
options = {
|
|
logLevel = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "info";
|
|
description = "Log level for crosvm (error, warn, info, debug, trace).";
|
|
example = "debug";
|
|
};
|
|
|
|
extraArgs = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
description = "Extra arguments passed to crosvm before 'run' subcommand.";
|
|
example = [
|
|
"--syslog-tag"
|
|
"vmsilo"
|
|
];
|
|
};
|
|
|
|
extraRunArgs = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
description = "Extra arguments passed to crosvm after 'run' subcommand.";
|
|
example = [ "--disable-sandbox" ];
|
|
};
|
|
};
|
|
};
|
|
default = { };
|
|
description = "Global crosvm configuration.";
|
|
};
|
|
|
|
gpu = lib.mkOption {
|
|
type = lib.types.submodule {
|
|
options = {
|
|
allowWX = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "Allow W+X (write+execute) memory in the GPU device backend service. Required for some NVIDIA drivers.";
|
|
};
|
|
};
|
|
};
|
|
default = { };
|
|
description = "Global GPU device backend configuration.";
|
|
};
|
|
|
|
"cloud-hypervisor" = lib.mkOption {
|
|
type = lib.types.submodule {
|
|
options = {
|
|
logLevel = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "info";
|
|
description = "Default log level for cloud-hypervisor VMM.";
|
|
example = "debug";
|
|
};
|
|
hugepages = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "Use hugetlbfs-backed memory (MAP_HUGETLB) for cloud-hypervisor VMs. Requires pre-allocated hugepages (vm.nr_hugepages).";
|
|
};
|
|
extraArgs = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
description = "Extra arguments to pass to cloud-hypervisor.";
|
|
};
|
|
};
|
|
};
|
|
default = { };
|
|
description = "Global cloud-hypervisor VMM configuration.";
|
|
};
|
|
|
|
schedulerIsolation = lib.mkOption {
|
|
type = lib.types.enum [
|
|
"full"
|
|
"vm"
|
|
"off"
|
|
];
|
|
default = "vm";
|
|
description = ''
|
|
Mitigate hyperthreading attacks using scheduler thread isolation.
|
|
- "full": vCPU threads may not share a core with any other thread (including other vCPUs of the same VM). Sets --core-scheduling=true. Has a significant performance cost and especially bad for realtime tasks like playing audio.
|
|
- "vm": vCPU threads may share a core with other vCPU threads from the same VM, but not threads from other VMs or the host. Sets --per-vm-core-scheduling.
|
|
- "off": No mitigations. Sets --core-scheduling=false.
|
|
'';
|
|
};
|
|
|
|
user = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "User who owns TAP interfaces and runs VMs. Required.";
|
|
example = "david";
|
|
};
|
|
|
|
netvmRange = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "10.200.0.0/16";
|
|
description = ''
|
|
IP range used for auto-allocating /31 subnets for netvm links.
|
|
Must be a CIDR block large enough to avoid collisions across all netvm pairs.
|
|
Individual pairs can override with network.netvmSubnet.
|
|
'';
|
|
example = "10.100.0.0/16";
|
|
};
|
|
|
|
isolatedPciDevices = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
description = ''
|
|
PCI devices to isolate from the host using vfio-pci.
|
|
Accepts BDF format: "01:00.0" or "0000:01:00.0".
|
|
These devices will be claimed by vfio-pci at early boot.
|
|
|
|
Note: For maximum reliability, also blacklist native drivers
|
|
using boot.blacklistedKernelModules.
|
|
'';
|
|
example = [
|
|
"01:00.0"
|
|
"02:00.0"
|
|
];
|
|
};
|
|
|
|
nixosVms = lib.mkOption {
|
|
type = lib.types.attrsOf vmSubmodule;
|
|
default = { };
|
|
description = "NixOS-based VMs to create. Keys are VM names.";
|
|
example = lib.literalExpression ''
|
|
{
|
|
banking = {
|
|
memory = 4096;
|
|
cpus = 4;
|
|
guestPrograms = [ pkgs.firefox ];
|
|
};
|
|
}
|
|
'';
|
|
};
|
|
|
|
vmsilo-dbus-proxy = lib.mkOption {
|
|
type = lib.types.submodule {
|
|
options = {
|
|
logLevel = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "info";
|
|
description = "Log level for vmsilo-dbus-proxy host and guest daemons.";
|
|
example = "debug";
|
|
};
|
|
};
|
|
};
|
|
default = { };
|
|
description = "D-Bus proxy configuration (tray and notifications).";
|
|
};
|
|
|
|
vmsilo-balloond = lib.mkOption {
|
|
type = lib.types.submodule {
|
|
options = {
|
|
logLevel = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "info";
|
|
description = "Log level for vmsilo-balloond (error, warn, info, debug, trace).";
|
|
example = "debug";
|
|
};
|
|
|
|
pollInterval = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "2s";
|
|
description = "Policy evaluation interval (e.g. \"2s\", \"1s\", \"500ms\").";
|
|
example = "1s";
|
|
};
|
|
|
|
criticalHostPercent = lib.mkOption {
|
|
type = lib.types.int;
|
|
default = 5;
|
|
description = "Host critical threshold as percentage of total RAM.";
|
|
example = 10;
|
|
};
|
|
|
|
criticalGuestAvailable = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "256m";
|
|
description = "Guest critical threshold — hard floor for guest free memory. Balloon inflation will never push guest free memory below this value (e.g. \"256m\", \"400m\").";
|
|
example = "400m";
|
|
};
|
|
|
|
guestAvailableBias = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "400m";
|
|
description = "Guest bias term — soft cushion above the critical floor that scales with balloon fullness, discouraging inflation as the guest approaches the floor (e.g. \"300m\", \"500m\").";
|
|
example = "500m";
|
|
};
|
|
|
|
minPollInterval = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "250ms";
|
|
description = "Minimum poll interval under memory pressure (e.g. \"250ms\", \"500ms\").";
|
|
example = "500ms";
|
|
};
|
|
|
|
psiCeiling = lib.mkOption {
|
|
type = lib.types.int;
|
|
default = 25;
|
|
description = "PSI avg10 percentage that maps to the minimum poll interval (0-100).";
|
|
example = 50;
|
|
};
|
|
|
|
extraArgs = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
description = "Extra command line arguments to pass to vmsilo-balloond.";
|
|
example = [ "--some-flag" ];
|
|
};
|
|
};
|
|
};
|
|
default = { };
|
|
description = "VM balloon memory management daemon configuration.";
|
|
};
|
|
|
|
# Internal options for dependency injection from flake.nix and inter-module communication
|
|
_internal = {
|
|
crosvm = lib.mkOption {
|
|
type = lib.types.package;
|
|
description = "crosvm package (injected by flake).";
|
|
internal = true;
|
|
};
|
|
|
|
wayland-proxy-virtwl = lib.mkOption {
|
|
type = lib.types.package;
|
|
description = "wayland-proxy-virtwl package (injected by flake).";
|
|
internal = true;
|
|
};
|
|
|
|
sommelier = lib.mkOption {
|
|
type = lib.types.package;
|
|
description = "sommelier package (injected by flake).";
|
|
internal = true;
|
|
};
|
|
|
|
vhost-device-sound = lib.mkOption {
|
|
type = lib.types.package;
|
|
description = "vhost-device-sound package (injected by flake).";
|
|
internal = true;
|
|
};
|
|
|
|
vmsilo-balloond = lib.mkOption {
|
|
type = lib.types.package;
|
|
description = "vmsilo-balloond package (injected by flake).";
|
|
internal = true;
|
|
};
|
|
|
|
vmsilo-dbus-proxy = lib.mkOption {
|
|
type = lib.types.package;
|
|
description = "vmsilo-dbus-proxy package (injected by flake).";
|
|
internal = true;
|
|
};
|
|
|
|
"cloud-hypervisor" = lib.mkOption {
|
|
type = lib.types.package;
|
|
description = "cloud-hypervisor package (injected by flake).";
|
|
internal = true;
|
|
};
|
|
|
|
vmsilo-wayland-seccontext = lib.mkOption {
|
|
type = lib.types.package;
|
|
description = "vmsilo-wayland-seccontext package (injected by flake).";
|
|
internal = true;
|
|
};
|
|
|
|
# Generated scripts (set by scripts.nix, consumed by services.nix and package.nix)
|
|
vmScripts = lib.mkOption {
|
|
type = lib.types.attrsOf lib.types.path;
|
|
description = "VM launcher scripts (name -> script path).";
|
|
internal = true;
|
|
default = { };
|
|
};
|
|
|
|
proxyScripts = lib.mkOption {
|
|
type = lib.types.attrsOf lib.types.path;
|
|
description = "Proxy scripts (name -> script path).";
|
|
internal = true;
|
|
default = { };
|
|
};
|
|
|
|
userScripts = lib.mkOption {
|
|
type = lib.types.attrsOf lib.types.path;
|
|
description = "User-facing scripts (vm-run, vm-start, etc).";
|
|
internal = true;
|
|
default = { };
|
|
};
|
|
|
|
desktopFilesPackage = lib.mkOption {
|
|
type = lib.types.package;
|
|
description = "Package containing .desktop files for VM apps.";
|
|
internal = true;
|
|
};
|
|
|
|
bashCompletionScript = lib.mkOption {
|
|
type = lib.types.path;
|
|
description = "Bash completion script for vm-* commands.";
|
|
internal = true;
|
|
};
|
|
|
|
usbHelperLib = lib.mkOption {
|
|
type = lib.types.path;
|
|
description = "USB helper library script.";
|
|
internal = true;
|
|
};
|
|
|
|
userUid = lib.mkOption {
|
|
type = lib.types.int;
|
|
description = "UID of the configured vmsilo user.";
|
|
internal = true;
|
|
};
|
|
|
|
userGid = lib.mkOption {
|
|
type = lib.types.int;
|
|
description = "GID of the configured vmsilo user's primary group.";
|
|
internal = true;
|
|
};
|
|
|
|
userHome = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "Home directory of the configured vmsilo user.";
|
|
internal = true;
|
|
};
|
|
|
|
# Generated by netvm.nix: maps VM name -> { interfaces, guestConfig }
|
|
# Used to inject netvm-derived interfaces and guest config into VMs
|
|
# without creating a self-referential cycle on nixosVms.
|
|
netvmInjections = lib.mkOption {
|
|
type = lib.types.attrsOf (
|
|
lib.types.submodule {
|
|
options = {
|
|
interfaces = lib.mkOption {
|
|
# Cannot use networkInterfaceSubmodule here — doing so would cause the module
|
|
# system to read nixosVms during its own evaluation, creating infinite recursion.
|
|
# netvm.nix provides fully-specified attrsets that mirror the submodule defaults.
|
|
type = lib.types.attrsOf lib.types.anything;
|
|
default = { };
|
|
description = "Auto-generated network interfaces for this VM from netvm links.";
|
|
};
|
|
guestConfig = lib.mkOption {
|
|
type = lib.types.listOf lib.types.deferredModule;
|
|
default = [ ];
|
|
description = "Auto-generated guest NixOS modules for this VM from netvm links.";
|
|
};
|
|
nameservers = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
description = "Auto-generated nameservers for this VM from netvm links.";
|
|
};
|
|
};
|
|
}
|
|
);
|
|
default = { };
|
|
internal = true;
|
|
description = "Auto-generated netvm interface/config injections keyed by VM name. Set by netvm.nix.";
|
|
};
|
|
};
|
|
|
|
# Output: generated package with all scripts
|
|
package = lib.mkOption {
|
|
type = lib.types.package;
|
|
readOnly = true;
|
|
description = "Generated package containing VM launcher scripts.";
|
|
};
|
|
};
|
|
}
|