diff --git a/cmd/qvm/doctor.go b/cmd/qvm/doctor.go index 1929083..c3c5436 100644 --- a/cmd/qvm/doctor.go +++ b/cmd/qvm/doctor.go @@ -10,6 +10,7 @@ import ( "qvm/internal/workspace" "strconv" "strings" + "syscall" "github.com/spf13/cobra" ) @@ -232,7 +233,7 @@ func checkVirtiofsdSockets() []string { continue } - if err := process.Signal(os.Signal(nil)); err != nil { + if err := process.Signal(syscall.Signal(0)); err != nil { issues = append(issues, fmt.Sprintf("Orphaned virtiofsd socket: %s (process %d not running)", sock, pid)) } } diff --git a/flake/default-vm/flake.nix b/flake/default-vm/flake.nix index a68ab32..c5309f6 100644 --- a/flake/default-vm/flake.nix +++ b/flake/default-vm/flake.nix @@ -130,71 +130,42 @@ # Josh's timezone time.timeZone = "America/Chicago"; - # Git safe.directory for 9p ownership issues + # Git safe.directory for virtiofs ownership issues environment.etc."gitconfig".text = '' [safe] directory = * ''; - # 9p mount points for caches (must match qvm-start mount tags) + # virtiofs mount points for caches (must match qvm virtiofsd mount tags) + # Using virtiofs instead of 9p for better performance and hot-mount support fileSystems."/cache/cargo" = { device = "cargo_home"; - fsType = "9p"; - options = [ - "trans=virtio" - "version=9p2000.L" - "msize=104857600" - "_netdev" - "nofail" - ]; + fsType = "virtiofs"; + options = [ "nofail" ]; }; fileSystems."/cache/target" = { device = "cargo_target"; - fsType = "9p"; - options = [ - "trans=virtio" - "version=9p2000.L" - "msize=104857600" - "_netdev" - "nofail" - ]; + fsType = "virtiofs"; + options = [ "nofail" ]; }; fileSystems."/cache/pnpm" = { device = "pnpm_store"; - fsType = "9p"; - options = [ - "trans=virtio" - "version=9p2000.L" - "msize=104857600" - "_netdev" - "nofail" - ]; + fsType = "virtiofs"; + options = [ "nofail" ]; }; fileSystems."/cache/sccache" = { device = "sccache"; - fsType = "9p"; - options = [ - "trans=virtio" - "version=9p2000.L" - "msize=104857600" - "_netdev" - "nofail" - ]; + fsType = "virtiofs"; + options = [ "nofail" ]; }; fileSystems."/root/.config/opencode" = { device = "opencode_config"; - fsType = "9p"; - options = [ - "trans=virtio" - "version=9p2000.L" - "msize=104857600" - "_netdev" - "nofail" - ]; + fsType = "virtiofs"; + options = [ "nofail" ]; }; # Environment variables for cache directories @@ -215,47 +186,46 @@ "d /cache/sccache 0755 root root -" ]; - # Systemd mount units for cache directories - # The NixOS VM runner doesn't include custom fileSystems entries in the generated fstab, - # so we use systemd mount units to automount the 9p virtfs shares at boot. + # Systemd mount units for cache directories using virtiofs + # virtiofs provides better performance than 9p and supports hot-mounting systemd.mounts = [ { what = "cargo_home"; where = "/cache/cargo"; - type = "9p"; - options = "trans=virtio,version=9p2000.L,msize=104857600,nofail"; + type = "virtiofs"; + options = "nofail"; wantedBy = [ "multi-user.target" ]; after = [ "systemd-modules-load.service" ]; } { what = "cargo_target"; where = "/cache/target"; - type = "9p"; - options = "trans=virtio,version=9p2000.L,msize=104857600,nofail"; + type = "virtiofs"; + options = "nofail"; wantedBy = [ "multi-user.target" ]; after = [ "systemd-modules-load.service" ]; } { what = "pnpm_store"; where = "/cache/pnpm"; - type = "9p"; - options = "trans=virtio,version=9p2000.L,msize=104857600,nofail"; + type = "virtiofs"; + options = "nofail"; wantedBy = [ "multi-user.target" ]; after = [ "systemd-modules-load.service" ]; } { what = "sccache"; where = "/cache/sccache"; - type = "9p"; - options = "trans=virtio,version=9p2000.L,msize=104857600,nofail"; + type = "virtiofs"; + options = "nofail"; wantedBy = [ "multi-user.target" ]; after = [ "systemd-modules-load.service" ]; } { what = "opencode_config"; where = "/root/.config/opencode"; - type = "9p"; - options = "trans=virtio,version=9p2000.L,msize=104857600,nofail"; + type = "virtiofs"; + options = "nofail"; wantedBy = [ "multi-user.target" ]; after = [ "systemd-modules-load.service" ]; } @@ -298,15 +268,9 @@ # GB disk size virtualisation.diskSize = 40 * 1024; - # NOTE: Using 9p virtfs for filesystem sharing - # The NixOS VM runner doesn't support virtio-fs out of the box. - # We use 9p (-virtfs) which is the standard method for QEMU VMs. - # - # See: https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/virtualisation/qemu-vm.nix#L530 - # The sharedDirectories option hardcodes: -virtfs local,path=...,security_model=... - # - # 9p mounts are configured via QEMU_OPTS environment variable: - # -virtfs local,path=$HOST_PATH,mount_tag=$TAG,security_model=mapped-xattr,msize=104857600 + # NOTE: Using virtiofs for filesystem sharing (via virtiofsd + vhost-user-fs-pci) + # This provides better performance than 9p and supports hot-mounting workspaces + # without VM restart. The qvm CLI manages virtiofsd daemons for each mount. system.stateVersion = stateVersion; }; diff --git a/internal/vm/qemu.go b/internal/vm/qemu.go index 220f52e..7ca282c 100644 --- a/internal/vm/qemu.go +++ b/internal/vm/qemu.go @@ -12,13 +12,14 @@ import ( func buildQEMUCommand(cfg *config.Config, sshPort int, mounts []virtiofsd.Mount) []string { memSize := cfg.VM.Memory - // vhost-user-fs requires shared memory backend + // vhost-user-fs requires shared memory backend with share=on + // We must specify memory size only via the memory backend and attach it to NUMA + // The -m flag must match the memory backend size for QEMU to be happy args := []string{ - "-machine", "q35", + "-machine", "q35,memory-backend=mem", "-accel", "kvm", "-cpu", "host", "-object", fmt.Sprintf("memory-backend-memfd,id=mem,size=%s,share=on", memSize), - "-numa", "node,memdev=mem", "-smp", strconv.Itoa(cfg.VM.CPUs), "-display", "none", "-daemonize",