| OPENHVF(1) | General Commands Manual | OPENHVF(1) |
NAME
openhvf — install
and manage OpenBSD virtual machines
SYNOPSIS
openhvf |
[-hq] [--vm
name] [--project
dir] [--emulate]
command [argument ...] |
DESCRIPTION
openhvf installs and manages
OpenBSD virtual machines under QEMU. It downloads
the installation image, creates the disk image, answers the installer over
the serial console, and from then on controls the machine's lifecycle, guest
access, and console automation. Getting from nothing to a machine that
accepts ssh(1) is one
command, and running it again does nothing.
The host does not have to run OpenBSD. Anything that runs QEMU will do, and the accelerator is chosen to suit it, so a Linux or Darwin workstation can build, test, or reproduce a bug on a real OpenBSD system without one on the desk.
Within the OpenHAP source tree, openhvf is
a development tool: it is not installed by ‘make
install’ and is not part of any release package.
openhvf operates on a project: a directory
containing an .openhvfrc configuration file and an
.openhvf/ state directory, created by the
init command. Unless
--project is given, the project root is
auto-discovered by searching upward from the current directory for an
.openhvfrc file.
The options are as follows:
--vmname- Operate on the named VM. The default is “default”. VM definitions live in .openhvf/vms/name.conf.
--projectdir- Use dir as the project root instead of auto-discovering it.
-q,--quiet- Suppress informational output.
--emulate- Force TCG software emulation instead of hardware acceleration. Without this flag the accelerator is chosen automatically: HVF on macOS, KVM on aarch64 Linux hosts with /dev/kvm, and TCG everywhere else.
-h,--help- Display usage information.
The following commands are available:
init[dir]- Initialize a project in dir (default: the current directory). Creates the .openhvfrc project configuration, the .openhvf/ directory with vms/ and state/ subdirectories, and a default VM configuration. Idempotent: does nothing if the project is already initialized.
up[--no-cache]- Ensure the VM is running. Downloads the installation image, creates the
disk image, and starts the VM as needed. Idempotent.
Unless the installed-image cache is disabled,
uptakes both sides of that cache: with no disk image present it creates one as an overlay on a matching cached base and boots the installed system directly, skipping the miniroot download and the installer entirely; after an installation it publishes the freshly installed disk as a new cached base. Caching is best effort: a failure warns and the VM comes up anyway, installing from scratch or keeping a standalone disk.--no-cacheskips both the restore and the save for this invocation, forcing a fresh installation and leaving the cache untouched. It overrides the image_cache directive and is meant for debugging the installer itself. down- Stop the VM gracefully.
destroy- Stop the VM and delete its disk image.
start- Start the VM in the background.
stop[-f,--force]- Stop the VM. With
-f, terminate it forcefully. status- Show VM status as key: value lines, including ssh_port and console_port.
ssh[command]- Open an interactive SSH session to the VM as root, or run command and exit with its exit code. Authentication uses the SSH agent.
console- Show how to connect to the VM serial console (via telnet(1)).
expectscript [argument ...]- Run an expect(1) script against the VM console.
wait[--timeout=seconds]- Block until the VM accepts SSH connections (default timeout: 120 seconds).
imagedownload|list- Manage cached installation images.
listshows cached images;download[version] shows the cache path or download URL for version (default: 7.8). Images are downloaded via the built-in proxy when the VM boots. cachelist|clear[--stale]- Manage the installed-image cache described in
IMAGE CACHE.
listprints one line per cached entry (key, size, creation time, snapshot count), marking the entry the invoked VM's configuration currently derives.clearremoves every entry;--stalekeeps only that one entry and removes the rest. Both forms also sweep temporary trees left by an interrupted save.The scope of
--staleis one VM, the one named by--vm. The key inputs version and disk_size are per-VM, so pruning for one VM removes bases another VM would have hit.clearrefuses to remove an entry while a VM in this project is running on it, and warns when removal orphans the disk of a stopped one (rebuild it withdestroyandup). VMs in other projects sharing the same cache_dir cannot be surveyed and are not covered by either check. snapshotsave|restore|rmnamesnapshotlist[--names]- Manage named snapshot layers over the cached base image, for caching guest
states
openhvfknows nothing about (typically the result of a provisioning script). See IMAGE CACHE.saveflattens the working disk into the cache under the base image it is built on;restorereplaces the working disk with a fresh overlay on name and reseeds the VM state the snapshot records. The VM must be stopped for both: a live overlay is not consistent.saveadditionally needs an installed VM whose disk is built on a cached image; a standalone disk (from--no-cacheor image_cache no) cannot be snapshotted.restoreneeds neither disk nor state and can run on a fresh checkout before the firstup.A missing or unresolvable snapshot exits 11 rather than 1, so a script can distinguish it from a real failure:
openhvf snapshot restore deps-$hash || provision_from_scratch
listandrmare scoped to the cache entry the current configuration derives.list--nameswrites bare snapshot names to standard output, one per line, for scripts; without it the listing goes to standard error like all other informational output. diskcheck|repair|info- Manage the VM disk image: verify integrity, repair errors (the VM must be stopped), or show image details.
help- Display usage information.
IMAGE CACHE
Installing OpenBSD under software
emulation costs tens of minutes. openhvf therefore
keeps the result: a pristine, compacted copy of the disk taken the moment
the installer finished, which later runs use as the backing image of a
throwaway overlay. A warm cache turns destroy
followed by up into a cheap factory reset.
Entries live under cache_dir, beside the proxy cache holding the miniroot and install sets:
<cache_dir>/proxy/... miniroot, install sets <cache_dir>/installed/<key>/base.qcow2 mode 0400, compacted <cache_dir>/installed/<key>/meta.json mode 0600 <cache_dir>/installed/<key>/snapshots/<n>.qcow2 mode 0400 <cache_dir>/installed/<key>/snapshots/<n>.json mode 0600
key is version-arch-hash, where hash is a truncated SHA-256 over everything that shapes an installed disk: the OpenBSD version, the architecture, disk_size, the contents of the install.exp installer script, and the contents of share/openhvf/cache-generation. Memory and port settings do not shape the disk and are excluded, so changing them still hits the same entry.
share/openhvf/cache-generation is a generation counter. Bump the number in it when the way the installer is driven changes outside install.exp, so existing cached images are invalidated. Keep it to the bare number: the whole file is hashed.
Entries are immutable and write-once. Each is built whole in a temporary directory and published by renaming that directory, so a reader never sees one installation's base image beside another installation's metadata.
A snapshot is an immutable named layer over an entry's base image,
created by snapshot save from a stopped VM.
Snapshots are flattened onto base.qcow2, so each is
a direct child of the base whatever the working disk was built on:
base.qcow2 <- <name>.qcow2 <- disk.qcow2
No snapshot is therefore ever another's parent, and removing one
cannot orphan another. Because snapshots live inside the entry, anything
that invalidates a base (a new key, or cache clear)
invalidates its snapshots with it.
FILES
- .openhvfrc
- Project configuration: cache_dir, state_dir, default_vm, image_cache.
- .openhvf/vms/name.conf
- Per-VM configuration: name, version, memory, disk_size, ssh_port, console_port.
- .openhvf/state/
- Runtime state (PID files, sockets, disk images).
- ~/.cache/openhvf/
- Default cache directory: downloaded installation images and installed-image cache.
- share/openhvf/cache-generation
- Installed-image cache generation counter.
image_cache yes |
no (default yes) switches
the installed-image cache on or off. It is read from the project
.openhvfrc or the global
~/.openhvfrc, project first, and may also appear
inside a vm block. --no-cache
on up overrides all of them.
EXIT STATUS
openhvf exits with one of the following
values:
- 0
- Success.
- 1
- General error.
- 2
- Invalid arguments.
- 3
- Configuration error, or not in an
openhvfproject. - 4
- VM not found.
- 5
- VM is running.
- 6
- VM is not running.
- 7
- Timeout waiting for the VM.
- 8
- SSH connection failed.
- 9
- Expect script failed.
- 10
- Image download failed.
- 11
- Snapshot not found.
The ssh command with a
command argument exits with the remote command's exit
code.
EXAMPLES
Initialize a project and boot the default VM:
$ openhvf init $ openhvf up $ openhvf wait --timeout=300
Run a command in the VM:
$ openhvf ssh "uname -a" OpenBSD openbsd-default.my.domain 7.8 GENERIC arm64
Operate on a VM other than the default:
$ openhvf --vm minimal up
SEE ALSO
HISTORY
The openhvf utility first appeared in
2025, written to run the
openhapd(8) test suite against a
real OpenBSD guest, and generalised from there.
AUTHORS
Dick Olsson <hi@senzilla.io>
SECURITY CONSIDERATIONS
openhvf generates a random root password
for each installation and stores it in the VM state directory, and, when the
installed image is cached, in that entry's meta.json
(mode 0600). Caching lengthens the secret's life: it survives
destroy and rotates only when the base key does.
This is not a localhost-only secret. The guest permits password root login, and QEMU is given ssh_port and console_port with no bind address, so the forwarded SSH port and the telnet serial console listen on every host interface. Treat a running VM, and a populated cache_dir, as reachable by anyone who can reach the host.
| July 27, 2026 | OpenBSD |