sync with labv2.nix + standalone flake with toDisk app
Previous history (https://git.sagar.ch/dotfiles/labv2.nix/commits/branch/master/modules/apps/vmix): - c359054 daku working! - 8de5cff fix integer overflow in vmix network lib - 9c25a66 daku on 25.05. with ollama - 385a3bf vmix enables relaxed sandbox - c363da1 restructure vmixLib into linux/windows subattrs with OS-specific customizeImage - edd4dc2 vmix: port namespace model and module improvements from conf.nix - 6666ecf vmix: add SPICE support, install virtio guest tools with SPICE agent - 46f5671 vmix: add QEMU guest agent channel for Windows VMs - e1fea34 vmix: add Win11 LTSC 2024 image, refactor VirtIO driver selection - c27ae68 vmix: make customizeImage chroot-sandboxed by default, opt-in impure - 305fbac virt customize needs chroot for now due to usr bin env things. could be fixed later - 264d30f vmix: add win10 VM on desk, disable SMB signing for guest Samba access - 9b64f51 vmix: split Windows templates into per-category files, add comprehensive debloat - ef91bf8 vmix: fix missing parent registry keys in Windows templates - f87f340 win10 VM on panda with AMD GPU + USB passthrough - 38e474f vmix: split Windows build into Audit Mode install + composable templates - a6a8db3 vmix: win11 support, remove build VNC, switch VMs to SPICE - 6cf5a21 generalize stage sets bg color, accent color and sets visual effects to performance - a84849f remove rdp template since it doesn't even work - 5245263 vmix: best performance template + generalize cleanup - ab12dd3 vmix: use CopyProfile for best performance visual effects - bce3326 vmix: CopyProfile for best performance visual effects - 2496107 vmix: add app templates (7zip, VLC, ImageGlass, Edge WebView, VC++ runtimes) - 29a6123 wip: debug default associations xml - 2a2e5f5 vmix: fix DefaultAssociations.xml cmd.exe escaping - cc6ff9d vmix: move DefaultAssociations.xml to template only - a4a78ec vmix: add removeWMP template to remove Windows Media Player - 3fe56de vmix: improved Edge removal (files, shortcuts, scheduled tasks) - a491767 vmix: fully remove Edge via post-oobe AppxPackage removal - 6ca1619 vmix: remove Edge DevToolsClient SystemApps + AppxPackage - 0c1ec35 vmix: sandboxie windows app template - 628bbd2 vmix: add Sandboxie-Plus template - f055a41 vmix: reorganize templates, add file associations, remove Paint - 34326f4 vmix: set Thorium as default browser via PS-SFTA in post-oobe - 86af258 vmix: Active Setup for default browser (all users, no post-oobe needed) - 35b8cb0 remove vnc display from thorium template - c7e0af6 vmix: fix Win11 generalize timeout + UCPD disable for URL associations - 43a1345 vmix: add Office 2024 template + Ohook activation in generalize - 03bbce0 vmix: updated office installation xml. more privacy options enabled - 790a0ee vmix: thorium installation - hide SFTA window - a0e5c18 vmix: fix office install.bat call + add privacy registry policies - 3df38ca vmix: fix Ohook activation + suppress Office theme dialog - df39ba3 vmix: remove sandboxie shortcut from desktop - 50d5972 vmix: skip Sandboxie desktop shortcut via installer flag - ee2fa0f vmix: fix win10 default browser - 938315b vmix: windows: set accent color to automatic. remove accent color from unnecessary elements - beceda8 vmix: allow ISO-only VMs without OS disk, add WinPE VM to panda Flake outputs: overlays.default, nixosModules.default, lib, apps.toDisk Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
dd1fb16e1b
commit
736503d730
77 changed files with 2785 additions and 796 deletions
272
nixos/vms/config.nix
Normal file
272
nixos/vms/config.nix
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
{ config, pkgs, lib, vmixLib, ... }:
|
||||
with lib;
|
||||
with vmixLib.network;
|
||||
let
|
||||
vmixCfg = config.vmix;
|
||||
mkShortIfaceName = prefix: seed: "${prefix}-${builtins.substring 0 8 (builtins.hashString "sha256" seed)}";
|
||||
|
||||
mkServices4aVMInNamespace = spaceName: vmName: cfg:
|
||||
let
|
||||
vmCfg = cfg // { name = vmName; };
|
||||
netCfg = vmCfg.networks;
|
||||
|
||||
mkTap4aLan = lanName: tapCfg:
|
||||
let
|
||||
tapInterfaceName = mkShortIfaceName "vt" "${spaceName}-${vmCfg.name}-${lanName}";
|
||||
lanInterfaceName = "brx-${lanName}";
|
||||
in
|
||||
{
|
||||
name = lanName;
|
||||
iface = tapInterfaceName;
|
||||
mac = tapCfg.mac;
|
||||
create = ''
|
||||
ip tuntap add dev ${tapInterfaceName} mode tap
|
||||
ip link set dev ${tapInterfaceName} up
|
||||
ip link set dev ${tapInterfaceName} master ${lanInterfaceName}
|
||||
'' + lib.optionalString (tapCfg.ip != null) ''
|
||||
# Make the static ip somehow part of the script so nixOs thinks the service has changed when the IP changes, which will trigger a VM restart.
|
||||
# So whenever IP changes, VM will restart automatically
|
||||
# The IP is actually bein assigned to VM by dnsmasq, when VM requests it via DHCP
|
||||
# Static IP - ${tapCfg.ip}
|
||||
'';
|
||||
delete = ''
|
||||
ip link del ${tapInterfaceName}
|
||||
'';
|
||||
};
|
||||
|
||||
mkMacvtap = macvtapName: macvtapVmCfg:
|
||||
let
|
||||
macvtapNetworkCfg = config.vmix.namespaces.${spaceName}.networks.macvtaps.${macvtapName};
|
||||
macvtapInterfaceName = mkShortIfaceName "mt" "${spaceName}-${vmCfg.name}-${macvtapNetworkCfg.uplink.iface}-${macvtapName}";
|
||||
uplinkNamespaceArg = lib.optionalString (macvtapNetworkCfg.uplink.namespace != null) "-n ${macvtapNetworkCfg.uplink.namespace}";
|
||||
in
|
||||
{
|
||||
name = macvtapName;
|
||||
iface = macvtapInterfaceName;
|
||||
mac = macvtapVmCfg.mac;
|
||||
|
||||
create = ''
|
||||
ip ${uplinkNamespaceArg} link add link ${macvtapNetworkCfg.uplink.iface} name ${macvtapInterfaceName} type macvtap mode bridge
|
||||
${lib.optionalString (macvtapVmCfg.mac != null) "ip ${uplinkNamespaceArg} link set dev ${macvtapInterfaceName} address ${macvtapVmCfg.mac}"}
|
||||
ip ${uplinkNamespaceArg} link set ${macvtapInterfaceName} netns ${spaceName}.vmix
|
||||
ip -n ${spaceName}.vmix link set dev ${macvtapInterfaceName} up
|
||||
'';
|
||||
delete = ''
|
||||
ip -n ${spaceName}.vmix link del ${macvtapInterfaceName}
|
||||
'';
|
||||
};
|
||||
|
||||
allTaps = (mapAttrsToList mkTap4aLan netCfg.lans);
|
||||
allMacvtaps = (mapAttrsToList mkMacvtap netCfg.macvtaps);
|
||||
|
||||
createTapsforLansScript = pkgs.writeShellScript "${vmCfg.name}-taps-vmix" (
|
||||
concatStringsSep "\n" (builtins.map (tap: tap.create) allTaps)
|
||||
);
|
||||
|
||||
deleteTapsforLansScript = pkgs.writeShellScript "${vmCfg.name}-taps-vmix" (
|
||||
concatStringsSep "\n" (builtins.map (tap: tap.delete) allTaps)
|
||||
);
|
||||
|
||||
createMacvTapsScript = pkgs.writeShellScript "${vmCfg.name}-taps-vmix" (
|
||||
concatStringsSep "\n" (builtins.map (macvtap: macvtap.create) allMacvtaps)
|
||||
);
|
||||
|
||||
deleteMacvTapsScript = pkgs.writeShellScript "${vmCfg.name}-taps-vmix" (
|
||||
concatStringsSep "\n" (builtins.map (macvtap: macvtap.delete) allMacvtaps)
|
||||
);
|
||||
|
||||
hasOsDisk = vmCfg.disks.os.file != null;
|
||||
|
||||
# Auto-detect Windows from _vmixOsType marker on the disk image
|
||||
isWindows = vmCfg.windows.enable || (hasOsDisk && (vmCfg.disks.os.file._vmixOsType or "linux") == "windows");
|
||||
|
||||
# Linux VMs: apply customizeImage with 9p fstab and machine-id setup
|
||||
linuxOsImage = vmixLib.linux.customizeImage vmCfg.disks.os.file {
|
||||
name = vmCfg.name;
|
||||
commands = ''
|
||||
truncate /etc/machine-id
|
||||
run-command systemd-machine-id-setup
|
||||
run-command ssh-keygen -A
|
||||
run ${vmixLib.linux.scriptsNFiles.add-9p-mounts-to-fstab vmCfg.shares}
|
||||
'';
|
||||
};
|
||||
|
||||
# Windows VMs: use disk image as-is (customization done at image build time)
|
||||
storeImage = if !hasOsDisk then null
|
||||
else if isWindows then vmCfg.disks.os.file
|
||||
else linuxOsImage;
|
||||
|
||||
# When persist = true, QEMU needs a mutable disk outside /nix/store.
|
||||
# The store image is copied to persistPath on first boot.
|
||||
osDiskPath = if !hasOsDisk then null
|
||||
else if vmCfg.disks.os.persist then vmCfg.disks.os.persistPath
|
||||
else toString storeImage;
|
||||
|
||||
# Script to seed the persistent disk from the store image on first boot
|
||||
seedPersistentDiskScript = pkgs.writeShellScript "${vmCfg.name}-seed-disk-vmix" ''
|
||||
PERSIST_PATH="${vmCfg.disks.os.persistPath}"
|
||||
if [ ! -f "$PERSIST_PATH" ]; then
|
||||
echo "Seeding persistent disk from store image..."
|
||||
mkdir -p "$(dirname "$PERSIST_PATH")"
|
||||
cp --no-preserve=mode "${toString storeImage}" "$PERSIST_PATH"
|
||||
chmod 600 "$PERSIST_PATH"
|
||||
fi
|
||||
'';
|
||||
|
||||
persistExecStartPre = lib.optional (hasOsDisk && vmCfg.disks.os.persist) seedPersistentDiskScript;
|
||||
|
||||
# QEMU expects single-letter boot codes (e.g. c,d,n), while vmix uses readable names.
|
||||
bootOrderQemu =
|
||||
let
|
||||
bootDeviceAliases = {
|
||||
os = "c";
|
||||
iso = "d";
|
||||
net = "n";
|
||||
floppy = "a";
|
||||
};
|
||||
in
|
||||
concatStrings (builtins.map (device: bootDeviceAliases.${device}) vmCfg.boot.order);
|
||||
|
||||
spiceUsbRedirArgs =
|
||||
if vmCfg.spice.enable && vmCfg.spice.usbRedir.enable then
|
||||
concatStringsSep " \\\n " ([
|
||||
"-device qemu-xhci,id=spice-usb-xhci"
|
||||
] ++ (concatMap (i: [
|
||||
"-chardev spicevmc,name=usbredir,id=spice-usbredirchardev${toString i}"
|
||||
"-device usb-redir,chardev=spice-usbredirchardev${toString i},id=spice-usbredirdev${toString i}"
|
||||
]) (range 1 vmCfg.spice.usbRedir.channels)))
|
||||
else
|
||||
"";
|
||||
|
||||
vncArgs = concatStringsSep "," (
|
||||
[
|
||||
"${vmCfg.vnc.addr}:${toString (vmCfg.vnc.port - 5900)}"
|
||||
"share=${vmCfg.vnc.sharePolicy}"
|
||||
]
|
||||
++ optional (vmCfg.vnc.websocketPort != null) "websocket=${toString vmCfg.vnc.websocketPort}"
|
||||
++ optional (vmCfg.vnc.passwordFile != null) "password-secret=vnc-pass-${vmCfg.name}"
|
||||
);
|
||||
|
||||
qemuStartVMScript = pkgs.writeShellScript "${vmCfg.name}-qemu-vmix" ''
|
||||
${optionalString vmCfg.vnc.enable ''
|
||||
${optionalString (vmCfg.vnc.passwordFile != null) ''
|
||||
if [ ! -r ${escapeShellArg vmCfg.vnc.passwordFile} ]; then
|
||||
echo "VNC password file is not readable: ${vmCfg.vnc.passwordFile}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -s ${escapeShellArg vmCfg.vnc.passwordFile} ]; then
|
||||
echo "VNC password file is empty: ${vmCfg.vnc.passwordFile}" >&2
|
||||
exit 1
|
||||
fi
|
||||
''}
|
||||
''}
|
||||
${optionalString vmCfg.spice.enable ''
|
||||
${optionalString (vmCfg.spice.passwordFile != null) ''
|
||||
if [ ! -r ${escapeShellArg vmCfg.spice.passwordFile} ]; then
|
||||
echo "SPICE password file is not readable: ${vmCfg.spice.passwordFile}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -s ${escapeShellArg vmCfg.spice.passwordFile} ]; then
|
||||
echo "SPICE password file is empty: ${vmCfg.spice.passwordFile}" >&2
|
||||
exit 1
|
||||
fi
|
||||
''}
|
||||
''}
|
||||
exec qemu-system-${vmCfg.arch} \
|
||||
${if vmCfg.nographic && vmCfg.pci.passthrough != [] then "-display none -vga none" else optionalString vmCfg.nographic "-nographic"} \
|
||||
${optionalString (vmCfg.vnc.enable && vmCfg.vnc.passwordFile != null) "-object secret,id=vnc-pass-${vmCfg.name},file=${escapeShellArg vmCfg.vnc.passwordFile}"} \
|
||||
${optionalString vmCfg.vnc.enable "-vnc ${vncArgs}"} \
|
||||
${optionalString (vmCfg.spice.enable && vmCfg.spice.passwordFile != null) "-object secret,id=spice-pass-${vmCfg.name},file=${escapeShellArg vmCfg.spice.passwordFile}"} \
|
||||
${optionalString vmCfg.spice.enable "-spice addr=${vmCfg.spice.addr},port=${toString vmCfg.spice.port}${optionalString (vmCfg.spice.passwordFile == null) ",disable-ticketing=on"}${optionalString (vmCfg.spice.passwordFile != null) ",password-secret=spice-pass-${vmCfg.name}"}"} \
|
||||
${optionalString vmCfg.spice.enable "-vga ${vmCfg.spice.displayDevice}"} \
|
||||
${optionalString (vmCfg.spice.enable && vmCfg.spice.agent.enable) "-device virtio-serial-pci -chardev spicevmc,id=vdagent,debug=0,name=vdagent -device virtserialport,chardev=vdagent,name=com.redhat.spice.0"} \
|
||||
${# Guest agent channel — prevents qemu-ga from spinning when virtio-win guest tools are installed
|
||||
optionalString isWindows "${optionalString (!vmCfg.spice.enable || !vmCfg.spice.agent.enable) "-device virtio-serial-pci"} -chardev socket,path=/tmp/qga-${vmCfg.name}.sock,server=on,wait=off,id=qga0 -device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0"} \
|
||||
${spiceUsbRedirArgs} \
|
||||
${optionalString vmCfg.kvm "-accel kvm"} \
|
||||
-name ${vmCfg.name} \
|
||||
-m ${toString vmCfg.mem.size} \
|
||||
${optionalString vmCfg.mem.balloon "-device virtio-balloon-pci"} \
|
||||
-smp cores=${toString vmCfg.cpu.cores} \
|
||||
-cpu ${vmCfg.cpu.model}${optionalString vmCfg.cpu.hideVirtualized ",kvm=off,hv_vendor_id=1234567890ab,-hypervisor"} \
|
||||
-machine type=${vmCfg.pc.type}${optionalString vmCfg.cpu.hideVirtualized ",kernel_irqchip=on"} \
|
||||
${optionalString vmCfg.bios.efi "-bios ${pkgs.OVMF.fd}/FV/OVMF.fd"} \
|
||||
${optionalString vmCfg.bios.tpm "-chardev socket,id=chrtpm,path=/tmp/mytpm-sock -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0"} \
|
||||
${# Windows: localtime RTC, USB tablet for mouse, disable S3/S4 sleep
|
||||
optionalString isWindows ''
|
||||
-rtc base=localtime,clock=host \
|
||||
-device qemu-xhci -device usb-tablet \
|
||||
-global ICH9-LMB.disable_s3=1 -global ICH9-LMB.disable_s4=1 \
|
||||
''} \
|
||||
${optionalString hasOsDisk "-drive file=${osDiskPath},format=qcow2,if=virtio${optionalString (vmCfg.disks.os.persist == false) ",snapshot=on"}"} \
|
||||
${optionalString (vmCfg.disks.iso.file != null) "-drive file=${toString vmCfg.disks.iso.file},media=cdrom,readonly=on"} \
|
||||
${concatMapStrings (diskCfg: ''
|
||||
-drive file=${toString diskCfg.file},format=${diskCfg.format},if=${vmCfg.disks.bus} \
|
||||
'') (attrValues vmCfg.disks.add)} \
|
||||
${concatStrings (mapAttrsToList (shareName: shareCfg: ''
|
||||
-virtfs local,path=${toString shareCfg.source},security_model=passthrough,mount_tag=${shareName} \
|
||||
'') vmCfg.shares)} \
|
||||
${optionalString cfg.networks.user.enable "
|
||||
-netdev user,id=user \
|
||||
-device virtio-net-pci,netdev=user \
|
||||
"} \
|
||||
${concatMapStrings (tapCfg: ''
|
||||
-device virtio-net-pci,netdev=lan-${tapCfg.name},mac=${tapCfg.mac} \
|
||||
-netdev tap,id=lan-${tapCfg.name},ifname=${tapCfg.iface},script=no,downscript=no \
|
||||
'') allTaps} \
|
||||
${concatStrings (imap1 (i: macvtap: ''
|
||||
-device virtio-net-pci,netdev=macvtap-${macvtap.name},mac=$(ip l show ${macvtap.iface} | awk '/link\/ether/{print $2}') \
|
||||
-netdev tap,id=macvtap-${macvtap.name},fd=${toString (i+2)} ${toString (i+2)}<>/dev/tap$(ip l show ${macvtap.iface} | awk -F':' '/${macvtap.iface}/{print $1}') \
|
||||
'') allMacvtaps)} \
|
||||
${concatStrings (imap1 (i: pciAddr: ''
|
||||
-device pcie-root-port,id=pci-passthrough${toString i},chassis=${toString i},slot=${toString i} \
|
||||
-device vfio-pci,host=${pciAddr},bus=pci-passthrough${toString i}${optionalString (i == 1) ",x-vga=on${optionalString (vmCfg.pci.romFile != null) ",romfile=${vmCfg.pci.romFile}"}"} \
|
||||
'') vmCfg.pci.passthrough)} \
|
||||
${concatMapStrings (usbDev: ''
|
||||
-device usb-host,vendorid=0x${usbDev.vendorId},productid=0x${usbDev.productId} \
|
||||
'') vmCfg.usb.hostDevices} \
|
||||
${optionalString (vmCfg.boot.menu == true) "-boot menu=on"} \
|
||||
${optionalString (length vmCfg.boot.order > 0) "-boot order=${bootOrderQemu}"} \
|
||||
'';
|
||||
in
|
||||
lib.optionalAttrs (cfg.enable) {
|
||||
"vm.vmix@${vmCfg.name}" = rec {
|
||||
bindsTo = [ "net.vmix@${spaceName}.target" "macvtaps.vm.vmix@${vmCfg.name}.service" ];
|
||||
unitConfig.JoinsNamespaceOf = "ns.net.vmix@${spaceName}.service";
|
||||
after = bindsTo;
|
||||
path = with pkgs; [ iproute2 qemu gawk coreutils ];
|
||||
serviceConfig = {
|
||||
ExecStartPre = persistExecStartPre ++ [ createTapsforLansScript ];
|
||||
ExecStart = qemuStartVMScript;
|
||||
ExecStopPost = deleteTapsforLansScript;
|
||||
PrivateTmp = true;
|
||||
ProtectSystem = true;
|
||||
ProtectHome = true;
|
||||
PrivateNetwork = true;
|
||||
} // lib.optionalAttrs (vmCfg.pci.passthrough != []) {
|
||||
# VFIO passthrough needs raw device access — relax sandboxing
|
||||
ProtectSystem = lib.mkForce false;
|
||||
SupplementaryGroups = [ "kvm" ];
|
||||
};
|
||||
wantedBy = lib.mkIf vmCfg.autostart [ "multi-user.target" ];
|
||||
};
|
||||
|
||||
"macvtaps.vm.vmix@${vmCfg.name}" = rec {
|
||||
bindsTo = [ "net.vmix@${spaceName}.target" ];
|
||||
after = bindsTo;
|
||||
partOf = [ "vm.vmix@${vmCfg.name}.service" ];
|
||||
path = with pkgs; [ iproute2 ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = createMacvTapsScript;
|
||||
ExecStop = deleteMacvTapsScript;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
vmServices = concatMapAttrs (spaceName: namespaceCfg: (concatMapAttrs (mkServices4aVMInNamespace spaceName) namespaceCfg.vms)) vmixCfg.namespaces;
|
||||
in
|
||||
{
|
||||
config.systemd.services = vmServices;
|
||||
}
|
||||
5
nixos/vms/default.nix
Normal file
5
nixos/vms/default.nix
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
args@{ config, pkgs, lib, vmixLib, ... }:
|
||||
with lib;
|
||||
{
|
||||
imports = [ (import ./config.nix args) ];
|
||||
}
|
||||
309
nixos/vms/submoduleOptions.nix
Normal file
309
nixos/vms/submoduleOptions.nix
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
{ config, pkgs, lib, vmixLib, ... }:
|
||||
with lib;
|
||||
{
|
||||
options = {
|
||||
autostart = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Start VM on host boot.";
|
||||
};
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Enable/disable VM service creation.";
|
||||
};
|
||||
vnc = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable VNC.";
|
||||
};
|
||||
addr = mkOption {
|
||||
type = types.str;
|
||||
default = "0.0.0.0";
|
||||
description = "VNC bind address inside the VM namespace.";
|
||||
};
|
||||
port = mkOption {
|
||||
type = types.ints.between 5900 5999;
|
||||
default = 5900;
|
||||
description = "VNC TCP port inside the VM namespace.";
|
||||
};
|
||||
forwardHostPort = mkOption {
|
||||
type = types.nullOr types.port;
|
||||
default = null;
|
||||
description = "Optional host TCP port to auto-forward to this VM VNC port.";
|
||||
};
|
||||
websocketPort = mkOption {
|
||||
type = types.nullOr types.port;
|
||||
default = null;
|
||||
description = "Optional websocket port for VNC (for noVNC-style clients).";
|
||||
};
|
||||
passwordFile = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Path to a runtime file containing the VNC password. When set, VNC auth is enabled via password-secret.";
|
||||
};
|
||||
sharePolicy = mkOption {
|
||||
type = types.enum [ "allow-exclusive" "force-shared" "ignore" ];
|
||||
default = "allow-exclusive";
|
||||
description = "VNC client sharing policy.";
|
||||
};
|
||||
};
|
||||
spice = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable SPICE display server.";
|
||||
};
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = 5930;
|
||||
description = "SPICE TCP port inside the VM namespace.";
|
||||
};
|
||||
forwardHostPort = mkOption {
|
||||
type = types.nullOr types.port;
|
||||
default = null;
|
||||
description = "Optional host TCP port to auto-forward to this VM SPICE port.";
|
||||
};
|
||||
addr = mkOption {
|
||||
type = types.str;
|
||||
default = "0.0.0.0";
|
||||
description = "SPICE bind address inside the VM namespace.";
|
||||
};
|
||||
passwordFile = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Path to a runtime file containing SPICE password. When set, ticketing is enabled automatically, otherwise ticketing is disabled.";
|
||||
};
|
||||
agent.enable = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Enable SPICE guest agent channel (clipboard/resolution helpers).";
|
||||
};
|
||||
usbRedir = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable SPICE USB redirection channels.";
|
||||
};
|
||||
channels = mkOption {
|
||||
type = types.ints.between 1 16;
|
||||
default = 4;
|
||||
description = "Number of SPICE USB redirection channels to expose.";
|
||||
};
|
||||
};
|
||||
displayDevice = mkOption {
|
||||
type = types.enum [ "virtio" "qxl" "std" "none" ];
|
||||
default = "qxl";
|
||||
description = "QEMU -vga type to use with SPICE (qxl, virtio, std, none).";
|
||||
};
|
||||
};
|
||||
nographic = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Run QEMU without a graphical window (-nographic).";
|
||||
};
|
||||
cpu.cores = mkOption {
|
||||
type = types.int;
|
||||
default = 2;
|
||||
description = "Number of CPU cores.";
|
||||
};
|
||||
cpu.model = mkOption {
|
||||
type = types.str;
|
||||
default = "host";
|
||||
description = "CPU model.";
|
||||
};
|
||||
cpu.hideVirtualized = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Hide hypervisor from guest. Prevents GPU driver Code 43 errors by stripping hypervisor CPUID leaf.";
|
||||
};
|
||||
kvm = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Enable KVM.";
|
||||
};
|
||||
arch = mkOption {
|
||||
type = types.str;
|
||||
default = "x86_64";
|
||||
description = "Architecture of the VM.";
|
||||
};
|
||||
pc.type = mkOption {
|
||||
type = types.str;
|
||||
default = "q35";
|
||||
description = "PC type.";
|
||||
};
|
||||
bios.efi = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Enable EFI BIOS.";
|
||||
};
|
||||
bios.tpm = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable TPM BIOS.";
|
||||
};
|
||||
mem.size = mkOption {
|
||||
type = types.int;
|
||||
default = 1024;
|
||||
description = "Memory size in MB.";
|
||||
};
|
||||
mem.balloon = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable memory ballooning.";
|
||||
};
|
||||
disks.os.file = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = "Path to the OS disk image. Null for ISO-only VMs. For Windows, use a vmixLib.windows.* image — config auto-detects via _vmixOsType metadata.";
|
||||
};
|
||||
disks.os.persist = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Persist OS disk changes. The store image is copied to persistPath on first boot and QEMU writes to that mutable copy.";
|
||||
};
|
||||
disks.os.persistPath = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "Mutable path for the persistent OS disk (e.g. /storage/vms/myvm/os.qcow2). Required when persist = true.";
|
||||
};
|
||||
disks.iso.file = mkOption {
|
||||
type = types.nullOr (types.either types.path types.str);
|
||||
description = "Path to the ISO file. Can be a Nix store path or a string path to a local file.";
|
||||
default = null;
|
||||
};
|
||||
disks.add = mkOption {
|
||||
default = {};
|
||||
type = types.attrsOf (types.submodule {
|
||||
options = {
|
||||
file = mkOption {
|
||||
type = types.str;
|
||||
description = "String literal path to the additional disk.";
|
||||
};
|
||||
format = mkOption {
|
||||
type = types.str;
|
||||
description = "raw/qcow2 etc";
|
||||
};
|
||||
mounts = mkOption {
|
||||
type = types.attrsOf types.str;
|
||||
description = "Mount points for the additional disk.";
|
||||
};
|
||||
opts = mkOption {
|
||||
type = types.str;
|
||||
description = "additional options in QEMU args for this disk";
|
||||
};
|
||||
};
|
||||
});
|
||||
description = "Additional disks.";
|
||||
};
|
||||
shares = mkOption {
|
||||
default = {};
|
||||
type = types.attrsOf (types.submodule {
|
||||
options = {
|
||||
source = mkOption {
|
||||
type = types.path;
|
||||
description = "Source path for the shared directory.";
|
||||
};
|
||||
target = mkOption {
|
||||
type = types.str;
|
||||
description = "Target path inside the VM for the shared directory.";
|
||||
};
|
||||
};
|
||||
});
|
||||
description = "Shared directories.";
|
||||
};
|
||||
|
||||
disks.bus = mkOption {
|
||||
type = types.str;
|
||||
default = "virtio";
|
||||
description = "Bus type for the disks.";
|
||||
};
|
||||
boot.order = mkOption {
|
||||
type = types.listOf (types.enum [ "os" "iso" "net" "floppy" ]);
|
||||
description = "Boot order.";
|
||||
default = [ "os" "iso" ];
|
||||
};
|
||||
boot.menu = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable boot menu.";
|
||||
};
|
||||
|
||||
windows = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable Windows-optimized QEMU flags. Auto-enabled when disks.os.file carries _vmixOsType = \"windows\" metadata.";
|
||||
};
|
||||
};
|
||||
|
||||
tpm = {
|
||||
stateDir = mkOption {
|
||||
type = types.str;
|
||||
default = "/tmp";
|
||||
description = "Directory for TPM state persistence. Set to a /storage path for persistence across reboots.";
|
||||
};
|
||||
};
|
||||
|
||||
pci.passthrough = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = "PCI device addresses to passthrough via VFIO (e.g. [\"0000:03:00.0\" \"0000:03:00.1\"]).";
|
||||
};
|
||||
pci.romFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = "GPU VBIOS ROM file for the first passthrough device. Required when GPU PCI ROM BAR doesn't expose the full VBIOS (common with AMD Navi+).";
|
||||
};
|
||||
|
||||
usb.hostDevices = mkOption {
|
||||
default = [];
|
||||
type = types.listOf (types.submodule {
|
||||
options = {
|
||||
vendorId = mkOption { type = types.str; description = "USB vendor ID (e.g. \"1d6b\")."; };
|
||||
productId = mkOption { type = types.str; description = "USB product ID (e.g. \"0104\")."; };
|
||||
};
|
||||
});
|
||||
description = "USB host devices to passthrough to the VM.";
|
||||
};
|
||||
|
||||
networks.user.enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "enable qemu user networking";
|
||||
};
|
||||
|
||||
networks.lans = mkOption {
|
||||
default = {};
|
||||
type = types.attrsOf (types.submodule {
|
||||
options = {
|
||||
mac = mkOption {
|
||||
type = types.str;
|
||||
description = "MAC address for the LAN interface.";
|
||||
};
|
||||
ip = mkOption {
|
||||
type = types.nullOr (types.strMatching vmixLib.network.regex.ipv4);
|
||||
default = null;
|
||||
description = "assign static IP from the lan pool.";
|
||||
};
|
||||
};
|
||||
});
|
||||
description = "LAN interfaces.";
|
||||
};
|
||||
|
||||
networks.macvtaps = mkOption {
|
||||
default = {};
|
||||
type = types.attrsOf (types.submodule {
|
||||
options = {
|
||||
mac = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "MAC address for the MACVTap interface.";
|
||||
};
|
||||
};
|
||||
});
|
||||
description = "MACVTap interfaces.";
|
||||
};
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue