Reference · reference
Host configuration file
The host configuration file holds the settings that belong to this machine rather than to any lab: the subnet pool, the DNS suffix, the pre-shared key for cross-host segments, the viewer command, the shared-app clients, the ffmpeg console recordings are encoded with, the workspace syncer's size guard, and the host's GPUs. Host configuration and WSL 2 explains when to change each one; this chapter lists the fields.
Where it lives
The file is config.wcl in vmlab's XDG config directory, which is ~/.config/vmlab/ unless XDG_CONFIG_HOME moves it. See Files and directories. It is optional: when the file is absent every default applies. When present it must start with import <vmlab-host.wcl>, and a file without the import is rejected with an error naming the missing line. The daemons read it when they start, so a change takes effect on the next vmlab up after the supervisor restarts.
import <vmlab-host.wcl>
host {
subnet_pool = "10.99.0.0/16"
dns_suffix = "lab.local"
psk = "shared-secret"
viewer = "vncviewer {}"
}
host {}
The block of host-wide settings; the file carries at most one, beside any host GPUs (below). Every field is an override: an absent field leaves the default in place, and a malformed one is reported with its line rather than silently ignored.
host {
subnet_pool = "10.213.0.0/16"
dns_suffix = "vmlab.internal"
dns_upstream = "1.1.1.1:53"
disk_low_percent = 10
psk = "…"
trunk_port = 13947
viewer = "vncviewer {}"
waypipe = "/opt/waypipe/bin/waypipe"
freerdp = "/usr/bin/xfreerdp3"
ffmpeg = "/opt/ffmpeg/bin/ffmpeg"
fastpath = "auto"
oci_chunk_size = 512MiB
config_weave_bin_dir = "~/.local/share/config-weave/bin"
workspace_max_file = 256MiB
firmware_dir = "/opt/vmlab-firmware"
}
| Field | Type | Default | Meaning |
|---|---|---|---|
| subnet_pool | utf8 | 10.213.0.0/16 | CIDR the automatic /24 segment subnets are carved from. |
| dns_suffix | utf8 | vmlab.internal | Suffix for auto-registered machine names, <vm>.<lab>.<suffix>. |
| dns_upstream | utf8 | host resolver | Upstream resolver as ip[:port] for queries the lab DNS cannot answer. |
| disk_low_percent | i64 | 10 | Free-space percentage, 0 to 100, below which the host.disk_low watchdog fires. |
| psk | utf8 | none | Pre-shared key for cross-host segment links. Set the same value on both hosts. |
| trunk_port | i64 | 13947 | TCP port the supervisor listens on for inbound cross-host segment trunks, 1 to 65535. |
| viewer | utf8 | none | VNC viewer command; {} is replaced by the target. |
| waypipe | utf8 | waypipe on PATH | Path to the host's waypipe client for shared apps from Linux guests. |
| freerdp | utf8 | xfreerdp3, then xfreerdp, on PATH | Path to the FreeRDP 3 client for shared apps from Windows guests. |
| ffmpeg | utf8 | ffmpeg on PATH | Path to the ffmpeg that encodes console recordings. It needs libx264 for .mp4 and libvpx-vp9 for .webm. |
| fastpath | utf8 | auto | Network fast path: auto probes, off forces userspace, sockmap and afxdp force a kernel tier. |
| oci_chunk_size | ByteSize | 512MiB | OCI layer chunk size for vmlab template push. |
| config_weave_bin_dir | utf8 | ~/.local/share/config-weave/bin | Directory holding the config-weave guest binaries playbooks push. |
| workspace_max_file | ByteSize | 256MiB | Workspace syncer per-file size guard. A larger file is refused by name. |
| firmware_dir | utf8 | none | UEFI firmware override, laid out <dir>/<arch>/ with the bundled set's file names. Searched before vmlab's bundled firmware and the host's. |
The parser enforces these rules, and reports every violation in one pass:
- subnet_pool is a well-formed CIDR.
- disk_low_percent is between 0 and 100.
- trunk_port is between 1 and 65535.
- fastpath is one of auto, off, sockmap, afxdp. The VMLAB_FASTPATH environment variable selects the same values.
- oci_chunk_size and workspace_max_file are non-negative sizes.
- A field the schema does not name is rejected with its position.
- gpu_address, removed after v0.24, is rejected with a message naming the gpu block that replaced it.
workspace_max_file is host config rather than a @dev argument because the cap is about this developer's link to the guest, not the lab everyone shares; the refusal message names the field. See Dev machines and the workspace syncer. firmware_dir is authoritative for an architecture it has a directory for: a pair missing from <dir>/<arch>/ is an error, never a fall back to firmware you did not choose. An architecture with no directory there uses the bundled set. See Host configuration and WSL 2.
waypipe and freerdp are read by vmlab app run itself rather than by a daemon, so a change applies to the next shared app with no restart. Neither path is checked when the file is read. A wrong one fails when vmlab app run starts it, naming the path. See Shared apps.
ffmpeg is read by the lab daemon when a recording starts, and checked then: the daemon runs it with -encoders and refuses a recording whose format it cannot write, naming this key. Nothing looks for ffmpeg at vmlab up unless --record is given. See Console recording.
gpu "<name>" {}
A host GPU: a GPU on this machine, named so a lab's gpu {} can ask for it with host_gpu rather than carry a PCI address. Declare zero or more, beside host {}. See gpu {} for how a lab picks one.
gpu "AMD1" { address = "0000:72:00.0" }
gpu "Nvidia1" {
address = "0000:01:00.0"
default = true
qemu_binary = "/opt/qemu-vmlab/bin/qemu-system-x86_64"
qemu_env = { VKR_DEBUG: "validate" }
}
gpu "Nvidia2" { address = "0000:05:00.0" vulkan_icd = "/usr/share/vulkan/icd.d/nvidia_icd.json" }
| Field | Type | Default | Meaning |
|---|---|---|---|
| label | utf8 | required | The name. A lab's host_gpu matches it case-insensitively. |
| address | utf8 | required | Host PCI address, as under /sys/bus/pci/devices/. |
| vulkan_icd | utf8 | none | Path to a Vulkan ICD JSON a vulkan guest's renderer loads, set as VK_DRIVER_FILES. Overrides the driver picked from the GPU's kernel driver. |
| default | bool | false | The GPU a lab gets when it names none, or names one this host lacks under virgl or vulkan. A lone gpu block is the default without it. |
| qemu_binary | utf8 | none | Absolute path to the QEMU every VM on this GPU runs, in every mode including passthrough, instead of qemu-system-<arch> from PATH. vmlab also passes it -L <dir> (see below). |
| qemu_env | record of utf8 | none | Variables set in that VM's QEMU process, written { KEY: "value" }. Applied last, so a key here beats the Vulkan loader variable vmlab derives. |
The parser enforces these rules:
- Every block has a label and an address.
- Two labels that differ only in case are one name declared twice, which is an error.
- At most one block carries default = true.
- qemu_binary is an absolute path.
- Every qemu_env value is a string. Each key is a WCL identifier, so it is never empty and never contains =.
The rest is checked when a lab uses the GPU, by vmlab validate and at VM start, naming the GPU: its address must name a PCI device, a virgl or vulkan guest needs that device to have a DRM render node, and its vulkan_icd must exist. A GPU bound to vfio-pci for passthrough has no render node and needs none. Its qemu_binary must be an executable file. A file named qemu-system-<arch> must also match the VM's QEMU arch, or the check refuses it and names both arches. A file with any other name is used as given.
A VM whose host GPU sets qemu_binary gets -L <dir>, where <dir> is the stock QEMU's data directory: <prefix>/share/qemu for the qemu-system-<arch> on PATH, if that directory exists. A minimal build therefore finds the firmware and option ROMs it was not installed with. QEMU searches its own data directory after the -L paths, so a full build loses nothing. With no stock QEMU on PATH, vmlab adds no -L.
vmlab first removes the inherited VK_* loader variables and the PCI-filter layer's variables from the QEMU process's environment, then sets the ones it derives: the driver filter and, under vulkan, VK_ADD_IMPLICIT_LAYER_PATH and VMLAB_VK_PCI_ADDRESS for vmlab's VK_LAYER_VMLAB_pci_filter, which leaves the guest only the device at this GPU's address. qemu_env is applied after that, and an explicit host setting beats a derived one: a VK_LOADER_DRIVERS_SELECT in qemu_env replaces vmlab's, and VMLAB_VK_PCI_FILTER_DISABLE: "1" turns the layer off. The vmlab: env: line in qemu.log lists every variable vmlab set, qemu_env included, with its value. vmlab status -v shows qemu=<path> after host_gpu= for a VM that ran a qemu_binary, then that same set as env=NAME=value,... by name, values in full; vmlab status --json carries it as the VM's qemu_env object. Both show what the VM was started with, not the host config as it is now. Template builds never have a gpu {}, so they always run the stock QEMU.
A neutral fast path
auto never selects sockmap; it was measured slower than the userspace fabric and exists for explicit evaluation. Both kernel tiers need CAP_BPF and CAP_NET_ADMIN, and a daemon that cannot prove a tier works on its host falls back to userspace silently. vmlab fastpath reports the tier in use.