Add VM configuration options and migrate to --block
- Rename `disks` to `additionalDisks` with structured format (path, readOnly, enableDiscard, blockSize, devIdentifier, useDirect) - Add custom boot options: rootDisk, kernel, initramfs, rootDiskReadonly - Add kernelParams for extra kernel command line options - Add gpu option (default: "context-types=cross-domain:virgl2") - Add sharedDirectories for crosvm --shared-dir - Add global crosvmLogLevel option (default: "info") - Add --name argument to crosvm set to VM name - Migrate deprecated --disk/--rwdisk to --block format - Switch flake to nixos-unstable channel Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
5e32c13b03
commit
eb4e2ffcaa
5 changed files with 194 additions and 20 deletions
26
CLAUDE.md
26
CLAUDE.md
|
|
@ -109,6 +109,7 @@ qubes-lite.lib.makeRootfsNixos "x86_64-linux" {
|
|||
programs.qubes-lite = {
|
||||
enable = true;
|
||||
user = "david";
|
||||
crosvmLogLevel = "info"; # error, warn, info, debug, trace
|
||||
guestPrograms = [ pkgs.firefox pkgs.chromium ];
|
||||
|
||||
nixosVms = [{
|
||||
|
|
@ -117,6 +118,31 @@ qubes-lite.lib.makeRootfsNixos "x86_64-linux" {
|
|||
memory = 4096;
|
||||
disposable = true; # Auto-shutdown when idle
|
||||
idleTimeout = 120; # Shutdown after 2 minutes idle
|
||||
|
||||
# Disk configuration (uses crosvm --block)
|
||||
additionalDisks = [{
|
||||
path = "/tmp/data.qcow2";
|
||||
readOnly = false; # default false
|
||||
enableDiscard = true; # default true
|
||||
blockSize = 4096; # default 512
|
||||
devIdentifier = "data"; # optional device ID
|
||||
useDirect = false; # default false (O_DIRECT)
|
||||
}];
|
||||
|
||||
# Custom boot (optional - defaults to built rootfs)
|
||||
# rootDisk = { path = "/path/to/root.qcow2"; };
|
||||
# kernel = /path/to/bzImage;
|
||||
# initramfs = /path/to/initrd;
|
||||
rootDiskReadonly = true; # default true
|
||||
|
||||
# Extra kernel parameters
|
||||
kernelParams = [ "debug" ];
|
||||
|
||||
# GPU config (crosvm --gpu=)
|
||||
gpu = "context-types=cross-domain:virgl2"; # default
|
||||
|
||||
# Shared directories (crosvm --shared-dir)
|
||||
sharedDirectories = [ "/tmp/shared:shared:uid=1000" ];
|
||||
}];
|
||||
};
|
||||
# Access built package via config.programs.qubes-lite.package
|
||||
|
|
|
|||
14
flake.lock
generated
14
flake.lock
generated
|
|
@ -43,16 +43,16 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1770136044,
|
||||
"narHash": "sha256-tlFqNG/uzz2++aAmn4v8J0vAkV3z7XngeIIB3rM3650=",
|
||||
"lastModified": 1770115704,
|
||||
"narHash": "sha256-KHFT9UWOF2yRPlAnSXQJh6uVcgNcWlFqqiAZ7OVlHNc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e576e3c9cf9bad747afcddd9e34f51d18c855b4e",
|
||||
"rev": "e6eae2ee2110f3d31110d5c222cd395303343b08",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-25.11",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
|
|
@ -87,11 +87,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769691507,
|
||||
"narHash": "sha256-8aAYwyVzSSwIhP2glDhw/G0i5+wOrren3v6WmxkVonM=",
|
||||
"lastModified": 1770228511,
|
||||
"narHash": "sha256-wQ6NJSuFqAEmIg2VMnLdCnUc0b7vslUohqqGGD+Fyxk=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "28b19c5844cc6e2257801d43f2772a4b4c050a1b",
|
||||
"rev": "337a4fe074be1042a35086f15481d763b8ddc0e7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
wayland-proxy-virtwl = {
|
||||
url = "git+https://git.dsg.is/davidlowsec/wayland-proxy-virtwl.git?submodules=1";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
|
|
|
|||
|
|
@ -46,16 +46,50 @@ let
|
|||
guestConfig = lib.recursiveUpdate cfg.guestConfig vm.guestConfig;
|
||||
};
|
||||
|
||||
# Format a disk configuration as --block argument
|
||||
formatBlockArg =
|
||||
disk:
|
||||
let
|
||||
parts = [
|
||||
"path=${disk.path}"
|
||||
"ro=${lib.boolToString disk.readOnly}"
|
||||
"sparse=${lib.boolToString disk.enableDiscard}"
|
||||
"block-size=${toString disk.blockSize}"
|
||||
"direct=${lib.boolToString disk.useDirect}"
|
||||
]
|
||||
++ lib.optional (disk.devIdentifier != null) "id=${disk.devIdentifier}";
|
||||
in
|
||||
"--block ${lib.concatStringsSep "," parts}";
|
||||
|
||||
# Generate VM launcher script
|
||||
mkVmScript =
|
||||
vm:
|
||||
let
|
||||
rootfs = buildRootfs vm;
|
||||
# Only build rootfs if we need it (no custom root/kernel/initramfs)
|
||||
needsBuiltRootfs = vm.rootDisk == null || vm.kernel == null || vm.initramfs == null;
|
||||
rootfs = if needsBuiltRootfs then buildRootfs vm else null;
|
||||
|
||||
# Determine root disk, kernel, and initramfs sources
|
||||
rootDiskPath = if vm.rootDisk != null then vm.rootDisk.path else "${rootfs}/nixos.qcow2";
|
||||
rootDiskConfig = {
|
||||
path = rootDiskPath;
|
||||
readOnly = vm.rootDiskReadonly;
|
||||
enableDiscard = if vm.rootDisk != null then vm.rootDisk.enableDiscard else true;
|
||||
blockSize = if vm.rootDisk != null then vm.rootDisk.blockSize else 512;
|
||||
devIdentifier = if vm.rootDisk != null then vm.rootDisk.devIdentifier else null;
|
||||
useDirect = if vm.rootDisk != null then vm.rootDisk.useDirect else false;
|
||||
};
|
||||
kernelPath = if vm.kernel != null then vm.kernel else "${rootfs}/bzImage";
|
||||
initramfsPath = if vm.initramfs != null then vm.initramfs else "${rootfs}/initrd";
|
||||
|
||||
vmIp = "${networkBase}.${toString vm.id}";
|
||||
gwIp = "${networkBase}.${toString (vm.id - 1)}";
|
||||
ipv6 = "fd4d:06ff:48e4:${toString (vm.id - 1)}::2/48";
|
||||
gwv6 = "fd4d:06ff:48e4:${toString (vm.id - 1)}::1";
|
||||
additionalDisks = lib.concatMapStringsSep " " (d: "--rwdisk ${d}") vm.disks;
|
||||
|
||||
additionalDisksArgs = lib.concatMapStringsSep " " formatBlockArg vm.additionalDisks;
|
||||
sharedDirArgs = lib.concatMapStringsSep " " (d: "--shared-dir ${d}") vm.sharedDirectories;
|
||||
extraKernelParams = lib.concatMapStringsSep " " (p: "-p \"${p}\"") vm.kernelParams;
|
||||
in
|
||||
pkgs.writeShellScript "qubes-lite-start-${vm.name}" ''
|
||||
#!/bin/sh
|
||||
|
|
@ -65,12 +99,14 @@ let
|
|||
rm -f "$XDG_RUNTIME_DIR/crosvm-${vm.name}.sock"
|
||||
|
||||
exec ${cfg._internal.crosvm}/bin/crosvm run \
|
||||
--name ${vm.name} \
|
||||
--log-level=${cfg.crosvmLogLevel} \
|
||||
-m ${toString vm.memory} \
|
||||
--initrd=${rootfs}/initrd \
|
||||
--initrd=${initramfsPath} \
|
||||
--serial=hardware=virtio-console \
|
||||
--disk ${rootfs}/nixos.qcow2 \
|
||||
${additionalDisks} \
|
||||
-p "init=${rootfs.config.system.build.toplevel}/init" \
|
||||
${formatBlockArg rootDiskConfig} \
|
||||
${additionalDisksArgs} \
|
||||
${lib.optionalString (rootfs != null) ''-p "init=${rootfs.config.system.build.toplevel}/init"''} \
|
||||
-p "net.ifnames=0" \
|
||||
-p "spectrumname=${vm.name}" \
|
||||
${lib.optionalString vm.network ''
|
||||
|
|
@ -84,12 +120,14 @@ let
|
|||
-p "isDisposable=1" \
|
||||
-p "idleTimeout=${toString vm.idleTimeout}" \
|
||||
''} \
|
||||
${extraKernelParams} \
|
||||
${sharedDirArgs} \
|
||||
--cid ${toString vm.id} \
|
||||
--cpus ${toString vm.cpus} \
|
||||
--gpu=context-types=cross-domain:virgl2 \
|
||||
--gpu=${vm.gpu} \
|
||||
-s "$XDG_RUNTIME_DIR/crosvm-${vm.name}.sock" \
|
||||
--wayland-sock "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" \
|
||||
${rootfs}/bzImage
|
||||
${kernelPath}
|
||||
'';
|
||||
|
||||
# vm-run: Run command in VM (socket-activated)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,49 @@
|
|||
let
|
||||
cfg = config.programs.qubes-lite;
|
||||
|
||||
# Disk configuration submodule for --block arguments
|
||||
diskSubmodule = lib.types.submodule {
|
||||
options = {
|
||||
path = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Path to the disk image or block device.";
|
||||
example = "/tmp/data.qcow2";
|
||||
};
|
||||
|
||||
readOnly = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether the disk should be read-only.";
|
||||
};
|
||||
|
||||
enableDiscard = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether the disk should support the discard operation.";
|
||||
};
|
||||
|
||||
blockSize = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 512;
|
||||
description = "Reported block size of the disk in bytes.";
|
||||
example = 4096;
|
||||
};
|
||||
|
||||
devIdentifier = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Block device identifier (ASCII string, up to 20 characters).";
|
||||
example = "datadisk";
|
||||
};
|
||||
|
||||
useDirect = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Use O_DIRECT mode to bypass page cache.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
vmSubmodule = lib.types.submodule {
|
||||
options = {
|
||||
id = lib.mkOption {
|
||||
|
|
@ -55,16 +98,76 @@ let
|
|||
description = "Seconds to wait after last command exits before shutting down (only used when disposable=true).";
|
||||
};
|
||||
|
||||
disks = lib.mkOption {
|
||||
additionalDisks = lib.mkOption {
|
||||
type = lib.types.listOf diskSubmodule;
|
||||
default = [ ];
|
||||
description = "Additional disks to attach to the VM.";
|
||||
example = lib.literalExpression ''
|
||||
[{
|
||||
path = "/tmp/data.qcow2";
|
||||
readOnly = true;
|
||||
blockSize = 4096;
|
||||
devIdentifier = "datadisk";
|
||||
}]
|
||||
'';
|
||||
};
|
||||
|
||||
rootDisk = lib.mkOption {
|
||||
type = lib.types.nullOr diskSubmodule;
|
||||
default = null;
|
||||
description = "Custom root disk. If not set, uses the built rootfs image.";
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
path = "/path/to/custom-root.qcow2";
|
||||
readOnly = 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 root disk should be read-only.";
|
||||
};
|
||||
|
||||
kernelParams = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Additional disk paths (qcow2 or block devices).";
|
||||
description = "Extra kernel command line parameters.";
|
||||
example = [
|
||||
"/tmp/data.qcow2"
|
||||
"/dev/mapper/main-banking"
|
||||
"acpi=off"
|
||||
"debug"
|
||||
];
|
||||
};
|
||||
|
||||
gpu = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "context-types=cross-domain:virgl2";
|
||||
description = "GPU configuration passed to crosvm's --gpu option.";
|
||||
example = "context-types=cross-domain";
|
||||
};
|
||||
|
||||
sharedDirectories = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Shared directories passed to crosvm's --shared-dir option.";
|
||||
example = [ "/tmp/shared:shared:uid=1000" ];
|
||||
};
|
||||
|
||||
guestPrograms = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.package;
|
||||
default = [ ];
|
||||
|
|
@ -94,6 +197,13 @@ in
|
|||
default = true;
|
||||
};
|
||||
|
||||
crosvmLogLevel = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "info";
|
||||
description = "Log level for crosvm (error, warn, info, debug, trace).";
|
||||
example = "debug";
|
||||
};
|
||||
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "User who owns TAP interfaces and runs VMs. Required.";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue