Skip to content

Install the guest agent

guest_exec, guest_file_read and guest_file_write talk to qemu-guest-agent running inside the guest operating system. mcqemu wires the host side of that channel (a virtio-serial port named org.qemu.guest_agent.0) into every VM it launches, so the only missing piece is the package inside the guest.

Check what you have:

guest_ping(name="vm")

Success means you are done. Failure means the agent is not installed, not running, or the guest is paused or frozen. guest_info then reports the guest OS, the agent version, and which agent commands are enabled.

Use this when the guest is up and you can reach a console (with vm_type_text, see Drive an installer) or an SSH session through a forwarded port.

The guest needs package downloads to work, so if this VM was created with sandbox_vm, it has outbound networking blocked by default; recreate it with allow_network=true for the install.

Run the pair of commands for the distribution, as root:

Terminal window
# Debian, Ubuntu
apt-get update && apt-get install -y qemu-guest-agent
systemctl enable --now qemu-guest-agent
# Fedora, RHEL, CentOS, Rocky, Alma
dnf install -y qemu-guest-agent
systemctl enable --now qemu-guest-agent
# Arch
pacman -S --noconfirm qemu-guest-agent
systemctl enable --now qemu-guest-agent
# Alpine
apk add qemu-guest-agent
rc-update add qemu-guest-agent default && rc-service qemu-guest-agent start
# openSUSE
zypper install -y qemu-guest-agent
systemctl enable --now qemu-guest-agent

Windows guests get the agent from the virtio-win ISO: attach it with launch_vm(..., iso="/path/to/virtio-win.iso") and run guest-agent\qemu-ga-x86_64.msi from inside the guest.

Then confirm from the host:

guest_ping(name="vm")
guest_exec(name="vm", command="uname", args=["-a"])

Enabling the service matters as much as installing the package. An agent that is running now but not enabled at boot will be missing the next time you launch that image, and every sandbox built on it.

Option 2: inject it into a stopped disk image

Section titled “Option 2: inject it into a stopped disk image”

Use this when you cannot get a console: a downloaded cloud image with no password set, an image whose network never comes up, or when you are preparing a base image and would rather not boot it at all.

The technique exposes the disk image to the host as a block device with qemu-nbd, mounts the guest’s root filesystem, and installs the package into it through chroot.

  1. Load the network block device module and connect the image:

    Terminal window
    sudo modprobe nbd max_part=8
    sudo qemu-nbd --connect=/dev/nbd0 ~/vms/debian.qcow2
  2. Find the root partition. Do not guess: images vary, and picking a boot or EFI partition wastes time.

    Terminal window
    lsblk /dev/nbd0
    sudo blkid /dev/nbd0p*

    The root filesystem is usually the largest ext4, xfs or btrfs partition. If lsblk shows LVM physical volumes instead of a plain filesystem, run sudo vgchange -ay and mount the resulting device under /dev/mapper/.

  3. Mount it, plus the pseudo-filesystems the package manager needs:

    Terminal window
    sudo mkdir -p /mnt/guest
    sudo mount /dev/nbd0p1 /mnt/guest
    sudo mount --bind /dev /mnt/guest/dev
    sudo mount --bind /proc /mnt/guest/proc
    sudo mount --bind /sys /mnt/guest/sys

    If the image has a separate /boot or EFI partition, mount those inside /mnt/guest too. Installing the agent does not need them, but a package manager that decides to regenerate an initramfs will fail without them.

  4. Give the chroot working DNS, keeping the original so you can put it back:

    Terminal window
    sudo cp /mnt/guest/etc/resolv.conf /mnt/guest/etc/resolv.conf.orig
    sudo cp /etc/resolv.conf /mnt/guest/etc/resolv.conf
  5. Install the agent inside the image:

    Terminal window
    sudo chroot /mnt/guest apt-get update
    sudo chroot /mnt/guest apt-get install -y qemu-guest-agent
    sudo chroot /mnt/guest systemctl enable qemu-guest-agent

    systemctl enable works in a chroot because it only creates symlinks; it does not need systemd to be running. systemctl start would fail, which is fine, because the guest starts the service on its next boot. For dnf-based images substitute dnf install -y qemu-guest-agent, and for Alpine apk add qemu-guest-agent with rc-update add qemu-guest-agent default.

  6. Restore the guest’s own resolver:

    Terminal window
    sudo mv /mnt/guest/etc/resolv.conf.orig /mnt/guest/etc/resolv.conf
  7. Tear down in reverse order, and check each step succeeded:

    Terminal window
    sudo umount -R /mnt/guest
    sudo qemu-nbd --disconnect /dev/nbd0

    If umount reports the target is busy, find what is holding it with sudo lsof +D /mnt/guest and stop that before retrying. Disconnecting the nbd device while a filesystem is still mounted loses writes.

  8. Boot it and check:

    launch_vm(name="debian", disks=["~/vms/debian.qcow2"])
    guest_ping(name="debian")

Once guest_ping answers, that image is ready to be the base for disposable clones. Shut the VM down cleanly with stop_vm so the filesystem is consistent, then never launch the base directly again; build sandboxes on it instead, as in Disposable sandboxes.

While you are in there, two things are worth doing to the image because every future sandbox inherits them: install the packages you always want, and make sure the SSH server is enabled if you plan to use the forwarded port that sandbox_vm sets up.