vmsilo/modules/options.nix
Davíð Steinn Geirsson 8de5e55801 Replace sound tri-state with playback/capture booleans
Instead of passing raw crosvm attrsets, sound is now configured
with two booleans: sound.playback (default true) and sound.capture
(default false, implies playback).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 00:30:39 +00:00

876 lines
28 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"
];
};
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 {
options = {
name = lib.mkOption {
type = lib.types.str;
description = "VM name. Used for TAP interface naming and scripts.";
example = "banking";
};
color = lib.mkOption {
type = lib.types.str;
default = "red";
description = ''
Window decoration color for this VM. Used in wayland security context.
Supported formats: named colors ("red", "green"), hex ("#FF0000"), RGB ("rgb(255,0,0)").
'';
example = "#3498db";
};
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 at boot instead of on-demand via socket activation.";
};
rootOverlay = lib.mkOption {
type = lib.types.submodule {
options = {
type = lib.mkOption {
type = lib.types.enum [
"qcow2"
"tmpfs"
];
default = "qcow2";
description = "Overlay upper layer type: 'qcow2' (disk-backed, default) or 'tmpfs' (RAM-backed).";
};
size = lib.mkOption {
type = lib.types.str;
default = "10G";
description = "Maximum size of ephemeral disk (qcow2 only). Parsed by qemu-img create.";
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>), 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 crosvmAttrsetType;
default = true;
description = ''
GPU configuration. Set to false to disable, true for default config,
or an attrset for custom --gpu options.
'';
example = lib.literalExpression ''
{
context-types = "cross-domain:virgl2";
width = 1920;
height = 1080;
}
'';
};
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. Implies playback.";
};
};
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.";
};
};
}
);
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";
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";
}]
'';
};
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.";
};
};
};
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.";
};
schedulerIsolation = lib.mkOption {
type = lib.types.enum [
"full"
"vm"
"off"
];
default = "full";
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.
- "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";
};
hostNetworking = lib.mkOption {
type = lib.types.submodule {
options = {
nat = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Enable NAT for VM internet access.";
};
interface = lib.mkOption {
type = lib.types.str;
default = "";
description = "External interface for NAT (required if nat.enable is true).";
example = "eth0";
};
};
};
};
default = { };
description = "Host networking configuration for NAT.";
};
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.listOf vmSubmodule;
default = [ ];
description = "List of NixOS-based VMs to create.";
example = lib.literalExpression ''
[{
name = "banking";
memory = 4096;
cpus = 4;
guestPrograms = [ pkgs.firefox ];
}]
'';
};
vmsilo-tray = lib.mkOption {
type = lib.types.submodule {
options = {
logLevel = lib.mkOption {
type = lib.types.str;
default = "info";
description = "Log level for vmsilo-tray host and guest daemons (error, warn, info, debug, trace).";
example = "debug";
};
};
};
default = { };
description = "Tray proxy configuration.";
};
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;
};
vmsilo-balloond = lib.mkOption {
type = lib.types.package;
description = "vmsilo-balloond package (injected by flake).";
internal = true;
};
vmsilo-tray = lib.mkOption {
type = lib.types.package;
description = "vmsilo-tray 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;
};
};
# Output: generated package with all scripts
package = lib.mkOption {
type = lib.types.package;
readOnly = true;
description = "Generated package containing VM launcher scripts.";
};
};
}