Guides

Guest agent

Run commands and move files in the guest through qemu-guest-agent, over a virtio-serial port.

QEMU's guest agent (qemu-ga, the qemu-guest-agent package of every distribution) runs inside the guest and answers a host over a virtio-serial port named org.qemu.guest_agent.0: run a program, read or write a file, ask for the host name, the network interfaces or the time, shut the guest down cleanly. Warm64 gives the guest that port with QEMU's own options, and your code a client for it.

warm-system-aarch64 ... \
  -device virtio-serial-pci \
  -chardev ringbuf,id=qga0 \
  -device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0

In the guest, install and start the agent once (it starts itself on later boots):

# Alpine
apk add qemu-guest-agent && rc-update add qemu-guest-agent && rc-service qemu-guest-agent start
# Debian, Ubuntu
apt install qemu-guest-agent && systemctl enable --now qemu-guest-agent

The agent finds its port at /dev/virtio-ports/org.qemu.guest_agent.0.

From your code

With that port on the command line, vm.guestAgent is a WARMGuestAgent. It speaks the agent's protocol (JSON commands and replies) and decodes the common ones:

const vm = await WARM.launch("... -device virtio-serial-pci -chardev ringbuf,id=qga0 -device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0", host);
await vm.guestAgent.waitReady();              // the agent answers once the guest has booted and started it
const { exitcode, stdout } = await vm.guestAgent.run("/bin/sh", ["-c", "uname -a; df -h /"]);
console.log(exitcode, new TextDecoder().decode(stdout));
await vm.guestAgent.fileWrite("/etc/motd", "built by warm64\n");
const info = await vm.guestAgent.command("guest-get-osinfo");
await vm.guestAgent.shutdown();               // a clean shutdown from inside

Any command of the agent's protocol goes through command(name, arguments); the reply's return comes back, and an error reply throws a QmpError. guest-info lists what the guest's agent supports.

Your own client

The port is a WARMChardevPort like the serial ports: vm.ports.get("org.qemu.guest_agent.0") (or the chardev, vm.chardevs.get("qga0")) gives you its bytes, for a client of your own or a library that already speaks to qemu-ga.

Under Node, a socket chardev lets an outside program talk to the agent, the way libvirt does:

warm-system-aarch64 ... -device virtio-serial-pci \
  -chardev socket,id=qga0,path=/tmp/qga.sock,server=on,wait=off \
  -device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0
# elsewhere
printf '{"execute":"guest-ping"}\n' | nc -U /tmp/qga.sock

The guest sees the host side of a socket port as connected while a client is; its reads on the port wait until then.

Other ports

A virtserialport with any name is a byte channel of your own between the guest (/dev/virtio-ports/NAME) and your code (vm.ports.get(NAME)): a control channel, a log stream, a way to pass a file in without a network. Several fit on one bus (nr= picks the port number; max_ports= sizes the bus, 31 by default). A virtconsole is an extra terminal the guest reaches as /dev/hvc0; put console=hvc0 on the kernel command line to log in on it.

Snapshots carry the ports' state and the bytes in flight.