Skip to content

Repository files navigation

qemu-system-generator

A systemd generator for .qemu files: one file per virtual machine, in systemd unit-file syntax, whose [QEMU] section is QEMU's own command line passed through line by line. The generator runs at boot and on systemctl daemon-reload, writes qemu-system@<name>.service and the virtiofsd@<name>-<tag> socket and service of every directory the machine shares, then exits. Nothing stays between systemd and QEMU.

It is modelled on Podman's Quadlet: the search path, drop-ins, [Install] and --dryrun work as in podman-systemd.unit(5), while [QEMU] keeps QEMU's own option names.

Status: research draft. The format is documented in man/qemu-systemd.unit.5.scd and the program in man/qemu-system-generator.8.scd; docs/ holds the walk-throughs and builds them, with the man pages, into a Sphinx site for Read the Docs. Nothing is released yet.

License: copyleft-next-0.3.1

A file

[QEMU]
machine=q35
nodefaults=
accel=kvm
cpu=host
m=2048
smp=2
drive=file=fedora.raw,if=virtio,format=raw
netdev=user,id=net0,hostfwd=tcp:127.0.0.1:10022-:22
device=virtio-net-pci-non-transitional,netdev=net0,romfile=
device=vhost-vsock-pci-non-transitional,guest-cid=42
nographic=

[virtiofsd]
shared-dir=%h
tag=home
sandbox=namespace
uid-map=:0:%U:1:
gid-map=:0:%G:1:

[Install]
WantedBy=machines.target

Each [QEMU] key is a QEMU option without its dash and the value is the option's argument, verbatim: man qemu-system is the reference. Each [virtiofsd] section is one virtiofsd with its options spelled the same way: virtiofsd --help is the reference. [Unit], [Service] and [Install] go to systemd untouched. The generator adds the lifecycle only: directories, device access, the QMP socket it powers the guest down through, the machined registration, and the virtiofs wiring.

Saved as ~/.config/qemu/systemd/fedora.qemu:

systemctl --user daemon-reload
systemctl --user start qemu-system@fedora
machinectl --user list
ssh machine/fedora
systemctl --user stop qemu-system@fedora

Getting started

docs/getting-started/ has the host requirements (systemd and Nix), the three ways to install (per user without root, system-wide, or through the flake's NixOS module) and a quick start that boots a disk image you already have. The guides in docs/bootstrap/ then boot a guest from a NixOS disk image, a UKI, a kernel and an initrd, a kernel and a QEMU of your own, or a distribution's cloud image.

Check a file before deploying it:

QEMU_SYSTEM_GENERATOR_UNIT_DIRS=examples \
    ./target/release/qemu-system-generator --user --dryrun

With Nix:

nix build
nix run . -- --user --dryrun
nix develop

Layout

src/unitfile.rs          unit-file syntax: sections as ordered lines, quoting
src/search.rs            search path, discovery, drop-ins
src/plan.rs              what a backend decodes a file into, launcher-neutral
src/backend/qemu.rs      [VM], [QEMU], [virtiofsd]
src/launcher/systemd.rs  a Plan into .service/.socket files and symlinks
src/main.rs              generator entry point and command line
data/qmp-powerdown       the QMP commands ExecStop= sends
data/qemu-system-generator.*  user units that run it without root
man/                     man pages, scdoc
docs/                    walk-throughs and the Sphinx site
examples/                one file per feature; the conformance inputs
tests/                   expected generator output, verify script
nix/                     package, NixOS module and Dagu
steps/                   the commands the guides of docs/bootstrap/ teach
dags/, config.yaml       the Dagu DAGs that wire the steps, and their config
notes/                   rules for contributors, unpublished

The suffix selects the backend and a section named after the backend's binary decodes to its command line; [QEMU] is the first backend, the systemd launcher the first launcher. Another backend adds its section, suffix and generator binary; another launcher turns the same plan into its own files.

Checks

cargo test
cargo clippy --all-targets
tests/verify
nix flake check

tests/verify generates the examples and loads them with systemd-analyze verify in both scopes. tests/update-expected rewrites the expected output after a deliberate format change.

steps/ holds the commands that do what each guide of docs/bootstrap/ teaches, and what else CI checks, and dags/ wires them for Dagu, one DAG per guide and others of CI's own, such as test, which runs cargo test; .github/workflows/bootstrap.yaml runs every DAG but runner, which runs the workflow itself. A guide's run writes where the guide writes, so it replaces ~/bootstrap/<guide> and the machines' .qemu files, and adds the generator to your Nix profile:

nix develop --ignore-env \
    --keep-env-var HOME --keep-env-var XDG_RUNTIME_DIR \
    --command dagu --config config.yaml start disk

notes/steps.md has the rules the steps follow.

nix flake check also builds the documentation as Read the Docs does and holds the Markdown and reStructuredText prose to 80 columns. To render it, or to preview it while editing:

nix run .#docs           # render to docs/_build/html
nix run .#serve -- 8001  # live-render on 127.0.0.1:8001, rebuilding on save

On a remote host, forward the port first with ssh -L 8001:127.0.0.1:8001 <host>.