Guest & devices
The guest and its agent
A slim Linux guest on the latest kernel, with an agent that runs commands and moves files over a private channel.
The FlashVMM guest is a slim Linux system (the latest Linux kernel and a no-distro BusyBox and musl root filesystem) whose PID 1 is the FlashVMM guest agent, a static Rust binary that boots the VM, reseeds its randomness from the host, and serves command execution and file transfer over a framed virtio-console channel.
The image carries only what a workload needs to start: a static BusyBox with every applet linked, the musl loader for musl binaries a workload brings, a static curl with certificate-verified HTTPS on mbedTLS and Mozilla's CA bundle, TinySSH for when a VM asks for SSH, and the agent as /init. Every upstream piece is built from its latest release and accepted only with a valid publisher signature or published checksum. An Alpine variant is built from the same tooling. On macOS and Windows arm64, the same userland ships as an initramfs.
Key facts
| Runs on | Every platform: Linux (KVM), macOS (HVF and VZ engines), Windows (WHP), x86_64 and arm64 guests |
|---|---|
| Interfaces | CLI flash exec, flash cp, flash guest http|send|recv · MCP flash_exec, flash_cp · REST /v1/flashvm/guest/execs, /v1/flashvm/guest/files, POST /v1/flashvm/guest/requests |
| Root filesystem | Slim BusyBox and musl image, 64 MiB ext4 by default; --disk-mib sets a larger disk and the agent grows the filesystem online at boot |
| Exec frame | kind: u8 | id: u32 LE | len: u32 LE | payload[len]; host-to-guest kinds below 0x80, guest-to-host at or above |
| Exec limits | 64 running per VM · 300 s default timeout, 24 h maximum · 16 MiB kept per output stream · 256 finished execs kept |
| File transfer | 256 KiB data chunks · files up to 1 GiB · 2 transfers at once · permissions kept |
| Reseed | 64 bytes of host entropy, credited with RNDADDENTROPY and applied with RNDRESEEDCRNG |
How it works
Boot, in order
- PID 1 duties. The agent mounts
/proc,/dev,/sysand shared memory, and from then on reaps every orphaned process. - Disk grow. It reads the block device's size and grows the ext4 root online (
EXT4_IOC_RESIZE_FS) before the VM reports ready, so the workload always sees the full disk. - Exec service. It opens the virtio-console's
flashvm.execport and announces itself with a HELLO frame. - Start. It waits for the host's start record on the control channel.
- Randomness, first. Before anything else draws from the kernel pool (NICs, the share, the SSH host key, the workload), it credits host entropy and forces a CRNG reseed.
- Clock. It sets the wall clock from the host's value, so TLS works from the first connection.
- Network. It configures every NIC the host addressed (NIC 0's gateway becomes the default route) and brings up the link on unaddressed bridge-mode NICs for the guest's own DHCP client.
- Share. It mounts the shared folder (virtio-fs) at the path the host chose, and any folders exported beside it.
- Build output, local. The guest kernel keeps build output in the shared folder (
node_modules,.venv, andtarget,buildordistwhere the project’s.gitignorenames them) on the VM’s own storage. The host marks each such folder when the guest first looks it up, and the kernel binds local storage over it before the first file is written, so a build never waits on the share and the host project stays clean. Removing the folder works as it does anywhere. - Workload. It starts the entry program, supervises SSH alongside it when the VM asked for it, and reports the exit.
Control channel per platform
Each OS uses its native transport for boot control: Linux and Windows x86 use a dedicated serial port (/dev/ttyS1, newline-delimited JSON) beside the kernel console; macOS HVF uses a versioned shared-memory command protocol defined by byte offsets; the macOS VZ engine carries the agent's mailbox over virtio-vsock; Windows arm64 uses a shared-memory mailbox. The exec and file channel is the same virtio-console port everywhere.
Random-seed refresh
The host supplies entropy and the guest applies it. At cold boot on Linux, the agent reads 64 bytes from virtio-rng (/dev/hwrng), or takes them from the start record where the host sends them there (Windows x86). On restore and fork, the VMM draws 64 fresh bytes from the host at the instant of resume and carries them, hex-encoded, in the resume record; the agent credits them with RNDADDENTROPY and immediately issues RNDRESEEDCRNG, which re-keys the CRNG so every clone of one snapshot diverges at once. On macOS HVF the host draws the 64 bytes with getentropy and passes them with the entry start; the agent applies them the same way before it spawns the workload. The agent reports the result as a boot-progress line.
The exec and file channel
The host half (flash-exec) is one engine-free crate shared by every OS's VMM. Frames are length-prefixed: kind: u8 | id: u32 LE | len: u32 LE | payload. Control records (EXEC, KILL, PUT, GET, MKDIR, DEL, EXIT, ERROR, OK) are small JSON payloads; data frames (STDIN, STDOUT, STDERR, PUT_DATA, GET_DATA) are raw bytes in chunks of up to 256 KiB. A header claiming more than 4 MiB resets the parser.
- Exec. A command runs as the workload user in its own process group with BusyBox applets on
PATH, argv passed as an array with no shell. stdout and stderr stream live; the first 16 MiB of each are kept (truncatedmarks the rest), within a 512 MiB budget per VMM. Attimeout_sthe whole process tree is killed. The record ends inexited,timed_out,killedorfailed. - Files. PUT writes one file byte for byte, creating parents; GET reads a file or lists a directory; DEL removes a file, an empty directory, or a tree with
recursive.flash cpmoves whole trees with their permissions. - Restore and fork. Execs and transfers belong to the VMM that started them, so a restored or forked child starts with none. The new VMM sends PING and the agent answers with a fresh HELLO.
Talking to the workload
Beyond exec, a workload can serve HTTP inside the guest: started with --http-port, it receives requests sent to POST /v1/flashvm/guest/requests (flash guest http). With --messaging-topics, flash guest send and recv exchange messages with it through the agent's local socket.
Interfaces
CLI
flash run --bundle ./bundle --vcpus 2 --mem-mib 1024 --id dev --disk-mib 4096 --detach
# Streams output; exits with the command's code (124 timed out, 137 killed)
flash exec dev -- sh -c 'uname -a; df -h /'
flash exec dev --timeout 60 --cwd /tmp --env MODE=test -- ./run-tests.sh
flash exec dev --json --changes -- sh -c 'echo hi > /tmp/out.txt'
flash cp ./src dev:/work/src
flash cp dev:/work/out ./out
MCP
flash_exec(vm_id: "dev", argv: ["sh", "-c", "uname -a"], timeout_s: 60, cwd: "/tmp", env: {"MODE": "test"}, changes: true)
flash_cp(src: "./src", dst: "dev:/work/src")
REST
POST /v1/flashvm/guest/execs {"argv": ["sh", "-c", "make"], "cwd": "/work", "timeout_s": 600,
"env": [["CC", "cc"]], "stdin_b64": ""}
GET /v1/flashvm/guest/execs/{exec_id}
GET /v1/flashvm/guest/execs/{exec_id}/events (SSE: live output)
PATCH /v1/flashvm/guest/execs/{exec_id} {"state": "killed"}
DELETE /v1/flashvm/guest/execs/{exec_id}
GET /v1/flashvm/guest/files?path=/work/out.txt
PUT /v1/flashvm/guest/files?path=/work/in.txt (body: the file's bytes)
DELETE /v1/flashvm/guest/files?path=/work/tmp&recursive=true
Contract:
409 exec_unavailableuntil the agent's exec channel is up (the VM is still booting).429 too_many_execswhen 64 execs are already running.404 not_foundfor a missing path;409 not_emptydeleting a non-empty directory withoutrecursive=true;422 invalid_queryfor a missing, relative or root path.503 no_guest_handlerfromflash guest httpon a VM started without--http-port.
Guarantees
- Clones diverge. Every restored or forked guest reseeds its CRNG from fresh host entropy before its workload runs again.
- The full disk from the start. The root filesystem is grown before the VM reports ready.
- Structured, bounded execs. Exit code, signal, state, output and duration in one record, with process-tree kill at the timeout.
- Byte-exact files. Uploads and downloads move bytes unchanged, with permissions kept.
- Verified supply chain. Every upstream component of the image is the latest release, checked against its publisher's signature or checksum.