diff options
| author | Lena <lena@omega> | 2026-08-01 00:00:00 +0000 |
|---|---|---|
| committer | Lena <lena@omega> | 2026-08-01 00:00:00 +0000 |
| commit | c235cb62beefd03087e7cfc7f7f55f0c7c6601c9 (patch) | |
| tree | 2776f86d9226d55c78e3d735764fcd9ecbe5e08e /README | |
| download | vmm-c235cb62beefd03087e7cfc7f7f55f0c7c6601c9.tar.gz | |
A QEMU/KVM virtual machine manager in one POSIX shell script. Each
VM is a directory of plain files under $VMMDIR, configured by a
hand-edited KEY=VALUE file that is parsed rather than sourced, so
nothing in it can inject a raw QEMU argument.
Control is a QMP FIFO pair inside that directory, guarded by its
permissions alone; nothing binds a TCP port. The tests are TAP
without a framework and boot real guests where the host allows.
Diffstat (limited to 'README')
| -rw-r--r-- | README | 183 |
1 files changed, 183 insertions, 0 deletions
@@ -0,0 +1,183 @@ +vmm +=== +A QEMU/KVM virtual machine manager in one POSIX shell script. Each VM is +a directory of plain files with a KEY=VALUE config edited by hand. + + cp vmm /usr/local/bin/vmm + vmm the verbs, their arguments, the environment + +Needs qemu-img and qemu-system-x86_64 or qemu-system-aarch64, from QEMU +6.0 or newer, Linux /proc, the POSIX tools, and flock, readlink, setsid, +timeout, grep -m1 and du -h, which are not POSIX. Alpine BusyBox and +Debian base tools provide them. Native acceleration needs /dev/kvm; +HWACCEL=no uses emulation instead. `vmm viewer` needs remote-viewer, or +whatever client $VIEWER names. + + +Usage +----- + vmm create web 20G writes the config, opens $EDITOR + vmm start web + vmm console web Ctrl-] detaches + vmm stop web + +Install an OS by pointing CDROM at an ISO and booting it first: + + CDROM="/srv/iso/alpine-virt-x86_64.iso" + BOOT_ORDER=dc + +The CD is a SCSI device, so an installer with no virtio drivers will not +see it. Drop both lines once the disk is bootable. + +A GRAPHICS=vnc or spice guest puts its display on a unix socket in the VM +directory, and `vmm viewer web` hands that socket to remote-viewer. The +path goes into a URI unencoded, so a VMMDIR holding a space, #, ? or % +may not survive the client's parser. The client runs wherever vmm runs, +so from another machine forward the socket and point a viewer at the +forward instead: + + ssh -N -L 5900:/home/you/.vm/web/vnc.sock vmhost & + remote-viewer vnc://127.0.0.1:5900 + +Every VM owns `$VMMDIR/<name>/disk.qcow2`; its path is not configurable. +To run many VMs from one immutable golden image, replace each new disk +with an overlay. The overlay takes its size from the backing file: + + rm ~/.vm/web/disk.qcow2 + qemu-img create -f qcow2 -b /srv/img/alpine.qcow2 -F qcow2 \ + ~/.vm/web/disk.qcow2 + + +Configuration +------------- +One KEY=VALUE per line. A `#` in the first non-blank column starts a +comment; a line carrying a value cannot also carry one. Values may be +quoted to protect leading or trailing blanks. Unknown keys, duplicates, +unbalanced quotes and non-printable bytes are errors naming the line. +Nothing here is sourced or eval'd. + + ARCH x86_64 | aarch64 default host + HWACCEL yes | no default yes + CPUS positive integer default 2 + MEM positive integer, MiB default 2048 + IMAGE_FORMAT qcow2 | raw default qcow2 + FIRMWARE absolute path aarch64 needs it + GRAPHICS no | vnc | spice default no + NETWORK yes | no | hostonly default yes + SNAPSHOT yes | no default no + BOOT_ORDER 1-3 distinct of c(disk) d(cdrom) n(net) default c + BALLOON yes | no default yes + CDROM absolute path optional + HOSTFWD comma list of HOST:GUEST or ADDR:HOST:GUEST optional + + - BOOT_ORDER is a restriction, not a preference: QEMU runs with + -boot strict=on, so a device left out of the list never boots. A + list naming no device the config creates would restrict nothing, + and is refused. + - SNAPSHOT=yes runs the guest on an overlay that is unlinked as soon + as QEMU holds it open, so guest writes have nowhere to survive. + - GRAPHICS adds a display and USB tablet, reached with `vmm viewer`. + vnc is built into QEMU, spice is a separate module. Clipboard + sharing and guest agents are out of scope. Stop the VM before + changing graphics. + - NETWORK=hostonly isolates the guest from the host and the internet. + QEMU's own DHCP and DNS still answer, and HOSTFWD still reaches in. + - HOSTFWD binds 127.0.0.1 unless the entry names an IPv4 address of + its own, so a two field entry is unreachable from another machine. + - BALLOON=yes lets the guest return freed pages to the host as it + frees them. There is no ballooning policy to set. + - Only the host's own ARCH can use KVM. Anything else is emulated, and + aarch64 has no built-in firmware to emulate without. + - IMAGE_FORMAT describes the fixed disk. `vmm create` makes qcow2; set + raw only after deliberately replacing that file with a raw image. + + +State +----- +One directory per VM, mode 0700, under $VMMDIR: + + config KEY=VALUE, hand-edited configuration + uuid identity, feeds -uuid and the NIC MAC + disk.qcow2 created by `vmm create` + pid written and locked by QEMU itself, never by vmm + qmp.in qmp.out FIFOs carrying QMP to and from QEMU + stdout stderr QEMU's own two streams, truncated at start + console.log guest serial, truncated at start, attached or not + vnc.sock only when GRAPHICS=vnc + spice.sock only when GRAPHICS=spice + +The pidfile is authoritative. Do not remove it while QEMU is running. A +VM reading as unknown has a pidfile naming a live process vmm cannot +identify, usually a pid something else took after a crash. Nothing that +would touch a running guest acts on that VM until the pidfile is removed +by hand. + +status and list report the QEMU process, not the guest. A guest that +panics is paused rather than killed, so it still reads as running; ask +`vmm monitor <name> info status` for the guest's own view. + +Nothing caps console.log. It is truncated when the VM starts and appended +to for as long as the VM runs, so a guest that streams to its serial port +fills the filesystem it lives on. + +The MAC is derived from the uuid, so a VM keeps its DHCP lease across +restarts and a clone never collides with the VM it came from. + +create and clone write config last. A failure may leave an incomplete +directory; inspect and remove it manually before retrying. + + +Security +-------- +The VM directory is the only authentication. Whoever can write qmp.in +owns the monitor, and the monitor can attach a block device pointing at +any file QEMU can read. QEMU runs under its seccomp sandbox with +spawning denied, so the monitor cannot start a process on the host. +Nothing is ever bound to a TCP port for control. + +Disk images are trusted input: a qcow2 names its backing file in its own +header and QEMU opens it. Run `qemu-img info --backing-chain` on an image +from anywhere else. A backing file must be immutable while its overlays +exist. Never back one VM with another VM's writable disk. + + +Exit codes +---------- + 0 success, including every idempotent no-op + 1 usage, config, dependency, or operation failure + 2 no such VM + 3 VM is not running, or its monitor did not answer + 4 VM is busy, locked, or has live state vmm cannot identify + 5 VM was stopped only by force + +`vmm stop` returning 0 means the guest shut itself down. 5 means it +ignored ACPI and was killed. + + +Debugging +--------- + vmm dryrun web what would run, and whether the config parses + vmm logs web what QEMU said, what the guest printed + vmm monitor web info status ask the running guest + tail -f ~/.vm/web/console.log + +dryrun launches nothing and needs no QEMU installed, so it doubles as a +config checker anywhere. Errors name the file and the line: + + vmm: /home/you/.vm/web/config:7: unknown key: MEMROY + +A VM that will not start prints the last of QEMU's stderr. vmm removes +runtime files only after proving QEMU is gone. A busy VM has another vmm +invocation holding its lock; the kernel releases it when that process exits. +An incomplete VM directory is deliberately left for manual inspection. + + +Testing +------- + ./test every file in tests/ + ./test config argv those two only + +TAP, no framework. ./test makes a throwaway VMMDIR, defines the +assertions, and sources each file in tests/ in turn. Tests that boot a +guest on KVM skip without /dev/kvm; the emulated ones need only the +matching qemu-system binary, and its firmware for aarch64. |