vmgen.py is standalone Python script for creating a single .qcow2 disk image
of a x86_64 Debian installation, with SSH pre-configured. You can add a list of
packages you want installed, as well as a script to run after the main
installation process.
Typical invocation goes something like:
chmod +x vmgen.py
./vmgen.py --iso input/debian-13.6.0-amd64-netinst.iso \
--ssh-public-key (cat ~/.ssh/key.pub) \
--cpus 2 --memory-gb 2 --disk-size-gb 8 --sudo \
--output VM.qcow2
./vmgen.py --help # Detailed usageLater, when you boot up the VM, the SSH server is already running on port 22:
qemu-system-amd64 -m 4G -machine q35 -accel kvm -drive file=VM.qcow2 \
-nic user,id=iguana,hostfwd=tcp::8022-:22 -display none &
ssh -i ~/.ssh/key debian@localhost -p 8022Note: You MUST use -machine q35. The default machine type is not used!
Requires uv, 7z, cpio, and QEMU.
- Only Debian 13 for now. Older or newer not tested.
- Only
q35machine type. - You need to download the installer beforehand.
Containers are great for development and deployment, but I find that having a full-blown VM is more useful to AI agents. Additionally, containers give you a lot of guarentees that you might not actually need.
Some advantages of a VM over a Container:
- Installing Packages: The VM is a standard Linux installation, duh.
- Simplified Networking Especially with User-Mode Networking. The VM is just another process and is subject to your firewall rules, goes through the same VPN connection, etc.
- Backup: Just one file.
OK, but why not cloud images?
Great question! This script makes a trade-off between cloud images and a VM. One day, I'll take a second look at how cloud images work, but last time I encountered some friction points with them:
- Tiny! You need to adjust the image size and the partition size.
(
qemu-img resizefollowed by using a partition manager inside the VM) - No SSH: You need to use
cloud-initto provision; otherwise, you need to deploy the sameinitrdhack I did here. - Weird: Takes forever on first boot, comes with passwordless root out-of-the-box, and some other nitpicks.
I had another janky script built around cloud images, but I decided to start over using VMs. Maybe I'll try again later.
It (ab)uses Debian
preseeding
mechanism. In this case, we may have an arbitrarily large preseed script; so it
has to be embedded into the initial RAM disk. (initrd) The SSH public key and
the user-supplied post-install script are also injected in the same manner. This
hack requires extracting the Kernel executable (vmlinuz) and the installer RAM
disk from the image and booting directly using them.
For the sake of documentation, I've included some of my prompts and a few of the reports I had Codex write. I found them quite educational, so they are archived here.
- archive/Development is the original development process and some of the early problems.
- archive/Experiment_Nested_Sandbox is a Squid proxy-based network sandbox; an outer VM runs the proxy, and filters the traffic for an inner VM, which could be an agent sandbox.
MIT