Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

qvm-ctl (installed as qvm) is a fast, lightweight CLI tool written in Rust to manage repeatable QEMU/KVM virtual machine lifecycles without libvirt.


What It Does

qvm-ctl is a command-line tool that lets you create, run, and manage QEMU/KVM virtual machines with simple commands instead of dealing with complex hypervisors. It automatically configures UEFI boot, TPM 2.0 emulation, virtual disks, and graphical displays so modern Linux and Windows installations work immediately out of the box. Each virtual machine is kept self-contained in its own folder on your computer, making your VMs easy to locate, inspect, and delete.


Built with Rust

Written from the ground up in idiomatic Rust, qvm-ctl emphasizes speed, safety, and operational reliability:

  • Zero Runtime Overhead: Compiled into a single binary with no interpreter dependencies or background hypervisor daemons.
  • Reliable Process Lifecycle: Direct OS signal dispatch and process supervision via native system libraries.
  • Deterministic Port & Concurrency Management: Built-in TCP socket probing and cross-process file locking guarantee conflict-free multi-VM execution.

Next Steps

To get started, follow the Installation guide to install the required system packages and set up the qvm command on your system. Next, check out the Getting Started walkthrough to create and boot your first VM, or jump into the Command Reference for full command syntax. You can also explore Configuration to tweak hardware defaults, or visit the GitHub repository to view the source code and report issues.

Installation

1. Install Dependencies

Ensure virtualization is supported (ls -l /dev/kvm). Then install dependencies for your distribution (including the Rust toolchain to compile from source):

Arch Linux

sudo pacman -S qemu-full edk2-ovmf swtpm virt-viewer freerdp rust

Debian / Ubuntu

sudo apt update
sudo apt install qemu-system-x86 qemu-utils ovmf swtpm swtpm-tools virt-viewer freerdp2-x11 cargo rustc

Fedora

sudo dnf install qemu-kvm qemu-img edk2-ovmf swtpm swtpm-tools virt-viewer freerdp cargo rust

Note: If you get permission errors accessing /dev/kvm, add your user to the kvm group:

sudo usermod -aG kvm $USER

2. Build & Install qvm

Clone the repository and build the release binary with cargo:

git clone https://github.com/HenriqueStelzer/qvm-ctl.git
cd qvm-ctl
cargo build --release
sudo install -m755 target/release/qvm /usr/local/bin/qvm

Alternatively, install directly using cargo install:

cargo install --path .

Or on Arch Linux using the included PKGBUILD:

makepkg -si

3. Verify

qvm version
# Output: qvm 1.2.0

Getting Started

Follow these 4 steps to create and run your first virtual machine.


1. Create a VM

Run qvm create <name> <iso> [virtio_driver_iso]:

# Linux VM
qvm create my-arch ~/Downloads/archlinux-x86_64.iso

# Windows VM (with optional VirtIO driver ISO)
qvm create win11 ~/Downloads/Win11_x64.iso ~/Downloads/virtio-win.iso

This creates a dedicated directory at ~/vms/<name>/ with a 40GB qcow2 virtual disk and UEFI NVRAM.


2. Boot with Installer ISO

Start the VM for installation:

qvm launch win11
  • QEMU and TPM 2.0 (swtpm) start in the background.
  • If remote-viewer or virt-viewer is installed, the graphical display opens automatically.
  • Otherwise, connect with any SPICE client: remote-viewer spice://localhost:5900.

3. Boot from Disk (Post-Install)

Once OS installation is complete, shut down the VM. For future boots, start directly from disk without attaching the installer ISO:

qvm launch win11 --no-iso

4. Manage Lifecycle

# View all VMs and their status
qvm list

# Gracefully shut down a running VM
qvm stop win11

# Delete a VM and remove its files (requires typing VM name to confirm)
qvm disable win11

Command Reference

Quick summary of all qvm subcommands.


Commands

CommandUsageDescription
createqvm create <name> <iso> [driver_iso]Provision a new VM directory, disk, and configuration
launchqvm launch <name> [--no-iso] [--headless]Start VM with or without installation media
appqvm app <name> <app_path> [args...]Run individual guest app via FreeRDP RemoteApp
stopqvm stop <name>Gracefully shut down a running VM (ACPI SIGTERM)
listqvm listDisplay all VMs and their running/stopped status
disableqvm disable <name>Stop and permanently delete a VM
versionqvm versionPrint qvm version number
helpqvm helpDisplay built-in CLI help

Global Tips

  • Color Output: Automatically disabled when stdout is piped or when NO_COLOR=1 is set.
  • Exit Status: Exits 0 on success and 1 on error with error details printed to stderr.

qvm create

Creates and initializes a new virtual machine.


Usage

qvm create <name> <iso> [driver_iso]

Arguments

  • <name>: Alphanumeric VM identifier (can include - and _).
  • <iso>: Path to the installer ISO image.
  • [driver_iso] (optional): Path to auxiliary driver ISO (e.g. VirtIO drivers for Windows).

What It Creates

Stores everything under ~/vms/<name>/ (override via QVM_DIR):

  • vm.conf: Saved VM hardware configuration.
  • disk.qcow2: QCOW2 sparse virtual disk (default: 40G, override via QVM_DISK).
  • ovmf-vars.fd: Dedicated writable UEFI NVRAM.

Examples

# Basic VM
qvm create debian-vm ~/Downloads/debian.iso

# Windows VM with VirtIO drivers
qvm create win11 ~/Downloads/Win11.iso ~/Downloads/virtio-win.iso

# Custom RAM, CPU, and Disk size
QVM_RAM=16384 QVM_CPUS=8 QVM_DISK=80G qvm create dev-box ~/Downloads/arch.iso

qvm launch

Boots a virtual machine in the background and connects to its display.


Usage

qvm launch <name> [--no-iso] [--headless]

Flags

  • --no-iso: Boots from the virtual disk only, without mounting installation or driver ISOs. Use this for normal day-to-day boots after OS installation.
  • --headless: Runs the VM in the background without automatically opening a SPICE display viewer (remote-viewer/virt-viewer). SPICE and RDP services remain accessible over their respective ports.

What It Does

  1. Locks the VM to prevent concurrent duplicate launches.
  2. Allocates the next available SPICE port (starting at 5900) and saves it to spice.port.
  3. Allocates the next available RDP port (starting at 3389) and saves it to rdp.port.
  4. Starts an isolated TPM 2.0 emulator daemon (swtpm).
  5. Launches QEMU with KVM acceleration, UEFI firmware, VirtIO hardware, and user networking with RDP port forwarding.
  6. Spawns remote-viewer or virt-viewer if installed (unless --headless is passed).

Examples

# Boot with installer ISO attached (for installation)
qvm launch win11

# Boot directly from disk (post-installation)
qvm launch win11 --no-iso

# Boot directly from disk in headless mode (no GUI viewer)
qvm launch win11 --no-iso --headless

Remote / Manual Connection

If running headless or if no viewer is installed on the host:

  • SPICE viewer:
    remote-viewer spice://localhost:5900
    
  • RDP / Remote Desktop:
    wlfreerdp /v:127.0.0.1:3389 /u:User
    

qvm app

Launches an individual application inside the guest VM and displays it as a seamless, native window on the host desktop using FreeRDP and Windows RemoteApp (RAIL).


Usage

qvm app <name> <app_path> [args...]

Arguments

  • <name>: The VM name.
  • <app_path>: The full path to the executable inside the guest (e.g. C:\Program Files\Notepad++\notepad++.exe or notepad.exe).
  • [args...]: Optional arguments to pass to the guest application.

What It Does

  1. Checks if the VM is running. If stopped, automatically starts it in headless mode (--no-iso --headless).
  2. Discovers the VM’s assigned RDP port (from rdp.port).
  3. Waits for the guest RDP service to become responsive.
  4. Detects the best available FreeRDP client on the host (wlfreerdp on Wayland, falling back to xfreerdp).
  5. Resolves credentials from environment variables (QVM_RDP_USER, QVM_RDP_PASS) or vm.conf (RDP_USER, RDP_PASS), defaulting to $USER and prompting securely if password is not preset.
  6. Launches the application in seamless RemoteApp mode with clipboard integration and dynamic resolution matching.

Guest Setup (One-time)

To allow RemoteApp execution, configure the Windows guest:

1. Enable Remote Desktop

Open Settings → System → Remote Desktop and toggle On.

2. Allow RemoteApps

By default, Windows only permits pre-registered RemoteApps. Run the following command in an Administrator Command Prompt or PowerShell inside the guest to allow launching any unlisted executable:

reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Terminal Server\TSAppAllowList" /v fDisabledAllowList /t REG_DWORD /d 1 /f

(Alternatively, open-source tools such as RemoteApp Tool can be used to generate specific allowed application lists).


Examples

# Launch Notepad
qvm app win11 notepad.exe

# Launch Notepad with an argument
qvm app win11 notepad.exe "C:\Users\Public\notes.txt"

# Launch an installed third-party application
qvm app win11 "C:\Program Files\Notepad++\notepad++.exe"

# Launch with credentials specified in environment
QVM_RDP_USER="Admin" QVM_RDP_PASS="secret" qvm app win11 cmd.exe

qvm stop

Gracefully powers down a running VM.


Usage

qvm stop <name>

How It Works

  1. Sends SIGTERM to the QEMU process, triggering an ACPI power button event in the guest OS for clean shutdown.
  2. Waits up to 10 seconds for the guest to cleanly shut down and flush disks.
  3. If the guest does not exit within 10 seconds, automatically escalates to SIGKILL.
  4. Saves TPM state via swtpm_ioctl --save and terminates the swtpm daemon.
  5. Cleans up PID and socket files.

Example

qvm stop win11
# Output:
# → Stopping win11...
# ✓ Stopped win11

qvm list

Shows all existing VMs, execution state, and connection details.


Usage

qvm list

Example Output

────────────────────────────────────────
  my-arch   stopped
  win11     running  spice://localhost:5900
────────────────────────────────────────
  • Green running: Process is active with its SPICE URL.
  • Yellow stopped: VM is configured but not running.

qvm disable

Stops and permanently deletes a virtual machine.


Usage

qvm disable <name>

Warning: This will permanently delete the entire VM directory ~/vms/<name>/ and its virtual disk.


Behavior

  1. Prompts you to type the VM name to confirm deletion:
    Type 'win11' to confirm deletion: win11
    
  2. If the VM is currently running, stops it automatically.
  3. Recursively removes the VM directory (rm -rf).

Scripted Deletion

To bypass the interactive confirmation in automated scripts, pipe the name:

echo "win11" | qvm disable win11

Configuration

Environment Overrides

Set these variables in your shell environment or prefix them to qvm create:

VariableDefaultDescription
QVM_DIR~/vmsRoot directory for all VM storage
QVM_RAM8192Default RAM in MB for new VMs
QVM_CPUS4Default vCPU count for new VMs
QVM_DISK40GDefault disk size for new VMs
QVM_RDP_USER$USERDefault username for qvm app FreeRDP sessions
QVM_RDP_PASS(unset)Default password for qvm app FreeRDP sessions
NO_COLOR(unset)Set to disable ANSI color codes

Example

# Create a VM in a custom path with 16GB RAM and 8 vCPUs
QVM_DIR=/mnt/storage/vms QVM_RAM=16384 QVM_CPUS=8 qvm create bigvm ~/iso/ubuntu.iso

Per-VM Config (vm.conf)

Each VM stores its configuration in ~/vms/<name>/vm.conf:

NAME=win11
ISO=/path/to/installer.iso
DRIVER_ISO=/path/to/virtio.iso
DISK=/home/user/vms/win11/disk.qcow2
OVMF_CODE=/usr/share/edk2/x64/OVMF_CODE_4M.secboot.fd
OVMF_VARS=/home/user/vms/win11/ovmf-vars.fd
RAM_MB=8192
VCPUS=4
CREATED=2026-09-13T18:00:00+00:00
# Optional credentials for 'qvm app'
RDP_USER=myusername
RDP_PASS=mypassword

To modify CPU or RAM for an existing VM, stop the VM and edit RAM_MB or VCPUS directly in this file. You can also persist default credentials for RemoteApp by adding RDP_USER (and optionally RDP_PASS).

Architecture & Internals

qvm-ctl is implemented in Rust as a compiled native binary. It manages virtual machines as standalone background processes without hypervisor daemons or runtime interpreter dependencies.


Rust Module Design

The codebase is organized into clean, single-responsibility modules:

  • cli: Command-line argument parsing and validation powered by clap.
  • qemu: Pure, testable QEMU command builder mapping VM configuration to Q35/KVM parameters.
  • port: Subprocess-free dynamic TCP port allocation using std::net::TcpListener (starting at port 5900 for SPICE and 3389 for RDP).
  • process: Subprocess management, PID file tracking, and graceful signal dispatch via nix.
  • ovmf: Multi-distribution discovery for UEFI OVMF_CODE and OVMF_VARS firmware templates across Arch, Debian/Ubuntu, and Fedora.
  • tpm: Lifecycle supervisor for swtpm software TPM 2.0 daemon and Unix control sockets.
  • config: Strongly-typed parser and serializer for vm.conf key-value pairs and environment overrides.
  • error: Comprehensive error domain modeled with thiserror and anyhow.

Directory Layout

Each VM is self-contained in ~/vms/<name>/:

~/vms/<name>/
├── vm.conf          # VM configuration and hardware parameters
├── disk.qcow2       # Primary QCOW2 sparse virtual disk
├── ovmf-vars.fd     # Independent writable UEFI NVRAM
├── qemu.log         # Process stdout & stderr log
├── spice.port       # Currently allocated SPICE port
├── rdp.port         # Currently allocated RDP port
├── <name>.pid       # QEMU process ID
├── swtpm.pid        # swtpm process ID
├── swtpm.sock       # Unix control socket for swtpm
└── tpm/             # Persistent TPM 2.0 state directory

Hardware Configuration

When qvm launch runs, it executes qemu-system-x86_64 with:

  • Chipset: q35 with accel=kvm and host CPU passthrough (-cpu host).
  • Firmware: UEFI via read-only OVMF_CODE and per-VM writable ovmf-vars.fd.
  • Security: Hardware TPM 2.0 emulation via swtpm on a dedicated Unix socket (-device tpm-crb).
  • Graphics: virtio-gpu-pci with SPICE server and vdagent for clipboard synchronization and auto-resizing.
  • Input: USB tablet pointer device (-device usb-tablet) for lag-free cursor tracking.
  • Storage: Paravirtualized VirtIO block device (-drive file=$DISK,if=virtio,format=qcow2).
  • Networking: User-mode networking with VirtIO (-nic user,model=virtio-net-pci) with host port-forwarding for RDP.

Port & Concurrency Management

  • Port Allocation: Dynamic scanning with native std::net::TcpListener::bind eliminates external subprocess overhead (such as ss) while guaranteeing no port collisions.
  • Launch Locking: Uses cross-process advisory locking (fd-lock) on .launch.lock to prevent accidental concurrent execution of the same VM.

Troubleshooting

Quick solutions for common issues.


OVMF_CODE not found. Install: edk2-ovmf

Install UEFI firmware:

  • Arch: sudo pacman -S edk2-ovmf
  • Debian / Ubuntu: sudo apt install ovmf
  • Fedora: sudo dnf install edk2-ovmf

QEMU exited immediately. Check: qemu.log

Check the log:

cat ~/vms/<name>/qemu.log

Common fixes:

  • Permission denied on /dev/kvm: Add user to group: sudo usermod -aG kvm $USER and log back in.
  • VT-x / AMD-V disabled: Enable virtualization in your host motherboard BIOS/UEFI.

Windows installer cannot find the virtual disk

The installer lacks VirtIO storage drivers. Download virtio-win.iso and pass it as the third argument:

qvm create win11 /path/to/win11.iso /path/to/virtio-win.iso

In the installer, select Load driver -> browse to the VirtIO CD -> viostor/w11/amd64.


Failed to start swtpm

Install swtpm or clear orphaned sockets:

# Verify installation
command -v swtpm || sudo pacman -S swtpm

# Clear stale socket if previous run crashed
rm -f ~/vms/<name>/swtpm.sock ~/vms/<name>/swtpm.pid

VM '<name>' is already launching

If an earlier launch was interrupted, remove the stale lockfile:

rm -f ~/vms/<name>/.launch.lock

Clipboard or auto-resize not working

Install the SPICE guest agent inside the guest OS:

  • Linux guest: sudo pacman -S spice-vdagent (or sudo apt install spice-vdagent)
  • Windows guest: Run virtio-win-guest-tools.exe from the VirtIO driver ISO.