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 onEvery platform: Linux (KVM), macOS (HVF and VZ engines), Windows (WHP), x86_64 and arm64 guests
InterfacesCLI 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 filesystemSlim 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 framekind: u8 | id: u32 LE | len: u32 LE | payload[len]; host-to-guest kinds below 0x80, guest-to-host at or above
Exec limits64 running per VM · 300 s default timeout, 24 h maximum · 16 MiB kept per output stream · 256 finished execs kept
File transfer256 KiB data chunks · files up to 1 GiB · 2 transfers at once · permissions kept
Reseed64 bytes of host entropy, credited with RNDADDENTROPY and applied with RNDRESEEDCRNG

How it works

Boot, in order

  1. PID 1 duties. The agent mounts /proc, /dev, /sys and shared memory, and from then on reaps every orphaned process.
  2. 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.
  3. Exec service. It opens the virtio-console's flashvm.exec port and announces itself with a HELLO frame.
  4. Start. It waits for the host's start record on the control channel.
  5. 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.
  6. Clock. It sets the wall clock from the host's value, so TLS works from the first connection.
  7. 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.
  8. Share. It mounts the shared folder (virtio-fs) at the path the host chose, and any folders exported beside it.
  9. Build output, local. The guest kernel keeps build output in the shared folder (node_modules, .venv, and target, build or dist where the project’s .gitignore names 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.
  10. 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 (truncated marks the rest), within a 512 MiB budget per VMM. At timeout_s the whole process tree is killed. The record ends in exited, timed_out, killed or failed.
  • 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 cp moves 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_unavailable until the agent's exec channel is up (the VM is still booting).
  • 429 too_many_execs when 64 execs are already running.
  • 404 not_found for a missing path; 409 not_empty deleting a non-empty directory without recursive=true; 422 invalid_query for a missing, relative or root path.
  • 503 no_guest_handler from flash guest http on 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.

See exec, files and bundles.

What it does

Related