The file identity the digest cache keys on was unix-only, so vm-measure did not
compile for Windows. The hanzo CLI links it on all five targets it ships, and
every Windows build of the CLI since it took the crate (v8.5.158, v8.5.160)
failed there: `cannot find function identity`. Off unix the identity is length
and modification time; the Linux CI leg now checks the crate for Windows.
2.0.3.
Co-authored-by: Hanzo Dev <[email protected]>
The purpose-prefixed labels (hanzo-build-*, hanzo-deploy-*, hanzo-docs-build-*)
are retired in favour of the bare <os>-<arch> every runner already advertises.
They were not a capability, only a habit, and the habit cost builds: papers asked
for hanzo-build-linux-amd64, no online runner offered it, and two idle
linux-amd64 runners sat beside the queue while the build simply never ran. The
lux tree lost three days to lux-linux-amd64 the same way.
What a job is FOR is the workflow's name. runs-on is the machine it needs.
Co-authored-by: Hanzo Dev <[email protected]>
The purpose-prefixed labels (hanzo-build-*, hanzo-deploy-*, hanzo-docs-build-*)
are retired in favour of the bare <os>-<arch> every runner already advertises.
They were not a capability, only a habit, and the habit cost builds: papers asked
for hanzo-build-linux-amd64, no online runner offered it, and two idle
linux-amd64 runners sat beside the queue while the build simply never ran. The
lux tree lost three days to lux-linux-amd64 the same way.
What a job is FOR is the workflow's name. runs-on is the machine it needs.
Co-authored-by: Hanzo Dev <[email protected]>
can_pause and can_resume returned a hardcoded false, so the capability was
declared absent while the client that would perform it already existed —
api::put against the cloud-hypervisor socket, the same call vm.create and
vm.boot already go through.
Four verbs, and the pairing matters more than any one of them:
pause stops the vCPUs. Memory stays mapped, devices stay configured, so a
resume is the guest CONTINUING rather than the guest booting. This
is the half of suspend that costs nothing — nothing written, nothing
freed.
snapshot writes the paused guest under a directory. It pauses first, and that
is not an optimisation: cloud-hypervisor refuses to snapshot a
running VM, because memory captured at one instant and device state
at another is a machine that never existed. It leaves the VM paused,
so the caller decides whether it continues or goes away.
restore replaces vm.create/vm.boot rather than following them — a restore
carries its own configuration, so a VMM that has already created a
VM has nowhere to put this one. The guest arrives PAUSED, the same
state snapshot left it in, so the round trip is symmetric.
resume the other end of pause.
VmState gains Paused at discriminant 2, which is Apple's
VZVirtualMachineStatePaused and what crates/vm-darwin already uses. The
discriminants stay aligned so one state channel means the same thing on both
backends.
vm-ch checks clean. vm-linux's failures are its own and predate this; vm-darwin
does not build on Linux at all.
Co-authored-by: Hanzo Dev <[email protected]>
A checkpoint is already content-addressed and chunked, so archiving one is not
a new format — it is the same chunks, somewhere durable. cas.rs said as much in
the one line above ChunkStore: "implement this for S3, SSH, or any other remote
storage." This is that implementation, and nothing else changed shape to admit
it.
Two pieces, each doing one thing:
S3ChunkStore a ChunkStore against an S3-compatible endpoint. SigV4 is the
minimum of it — one header set, no query signing, no chunked
upload — because a chunk is 64KB and immutable, so every request
it signs is a whole-object PUT or GET. Config comes from the
environment under the names rclone and the AWS SDKs both already
use, so a host configured for one is configured for this.
Tiered local first, archive behind it. A reload reads a chunk from the
network ONCE and keeps it, so the second resume of the same
checkpoint costs nothing over a purely local one.
Absent is not an error. No endpoint means this host has no archive, and a vm
that never archives must not fail to start because nobody configured one — the
selection point falls back to the local store it always used.
A failed archive write is a warning, not a failure: the host still has a working
local checkpoint, and the chunk is content-addressed, so a later put is the same
bytes under the same name. Losing that write costs a re-upload, never a wrong
read.
10 tests pass, the 8 that were there and 2 new.
Co-authored-by: Hanzo Dev <[email protected]>
Two fixes to 2.0.1, both found by running `hanzo up` against a real cluster on
a machine that was busy: a guest's first connection is bounded by a minute of
wall clock rather than a thousand tries, and `run --stdio` stops when its stdin
closes instead of parking in a join while still holding the forwarded ports.
Co-authored-by: Hanzo Dev <[email protected]>
The first connection to a guest was bounded by attempts: a hundred at a
millisecond and nine hundred at ten, which is about nine seconds. A guest boots
in a quarter of one on an idle machine, so the bound never showed. On a machine
at a load average past a hundred it did, and what it said was wrong twice over
— it named the refusal it was still retrying rather than the wait that had run
out, and it counted tries at a caller who has no way to convert them into time.
A minute of wall clock, and an error that says so.
`reach` is gone; `wait_ready` already was it. Two names for one connection is
the duplication this repo exists to avoid, and the newer one arrived without
noticing the older.
Co-authored-by: Hanzo Dev <[email protected]>
Two things a loaded host showed that an idle one hides.
`run --stdio` announced `ready` the moment the VMM started, which on an idle
machine is indistinguishable from the guest being reachable and on a busy one
is seconds earlier. A driver that believed it spent its first request's entire
connect budget discovering the difference: ten seconds of retries, then a
failure that named the request rather than the wait. `Sandbox::reach` blocks
until the guest's vsock server answers, and `ready` is sent after it — the same
wall clock, in a place a reader can name.
The guest never mounted /dev/shm. Every Linux system has one, and containerd
bind-mounts the host's into any pod that shares the host IPC namespace — with
nothing there it refuses to start the sandbox, so on a k3s inside the guest
`kubectl debug node/…` and every hostIPC pod failed on a message about the
host, not about the pod.
Co-authored-by: Hanzo Dev <[email protected]>
A boot is now an ordered log of what the hypervisor was handed — kernel,
command line, initrd where one is used, root image, machine shape — folded
into one extend-only SHA-384 register. The fold is the rule TDX applies to
an RTMR, and SHA-384 is the digest both TDX registers and the SEV-SNP launch
measurement use, so nothing is re-hashed at the boundary to hardware.
Only content enters a digest. A path is printed for the reader and never
hashed, so the same assets measure identically everywhere and a file digest
is an ordinary `shasum -a 384`. Digests are remembered against each image's
device, inode, length and mtime: 19 s of reading a 4 GB root image becomes
9 ms, warmed where a slow path is already being paid (after a download,
after a checkpoint save).
`hanzo-vm measure` states a launch without performing one. `run --stdio`
reports the launch log before it says `ready` — over the source images,
taken before anything is cloned — so a driver measures what it received
rather than what it asked for, and extends a second register with whatever
it deploys.
SHA-512(launch ‖ workload) is 64 bytes, the width of REPORT_DATA in an
SEV-SNP report and of a TDX TDREPORT's report data. ATTEST_REQ carries
exactly those bytes to the guest, which asks /dev/sev-guest or
/dev/tdx_guest and answers with what the platform said. The ask is
guest-side because that is the only side those devices exist on; a report
from the host would answer a different question. What is missing is
hardware: an SNP- or TDX-capable VMM on EPYC or Xeon. The ioctl numbers and
structure sizes are pinned by tests against the kernel headers, which is the
part a machine without the hardware can still check.
Co-authored-by: Hanzo Dev <[email protected]>
The hanzo-vm crate previously carried an unrelated EVM (<=1.1.22, now
hanzo-evm); 2.0.0 jumps past it so cargo install hanzo-vm installs this
CLI. The library package vm publishes as vm-core (crates.io's vm is
someone else's) with its lib target still named vm, so use vm::… is
unchanged. Whole workspace and the TypeScript SDK move to 2.0.0.
Co-authored-by: Hanzo Dev <[email protected]>
runs-on named hanzo-build-macos-arm64. RUNNERS.md registers the Apple pool as
hanzo-build-darwin-arm64 (with metal, m4max and dbc alongside it), and a job
asking for a label nobody carries is accepted and queued rather than refused —
so this leg has been sitting in a queue while its sibling job, on a registered
label, ran and made the repo look like it had CI.
Co-authored-by: Hanzo Dev <[email protected]>
The Linux CI leg runs cargo check, not clippy, so the experimental KVM
backend and the guest/sdk had accumulated lint debt that only surfaced
under a full clippy pass on the Linux hosts. All mechanical and
behavior-preserving: drop unused imports, prune no-op operations, first()
over get(0), c"" literals for nul-terminated strings, iterate slices
instead of indexing. The platform-dependent casts stay (c_char signedness
differs by arch; the FUSE protocol structs are defined whole) behind
scoped allows that explain why.
Co-authored-by: Hanzo Dev <[email protected]>
cloud-hypervisor describes vCPUs as x2apic MADT entries; without
CONFIG_X86_X2APIC the kernel ignored them and every x86_64 guest booted
with a single CPU regardless of --cpus. DEFERRED_STRUCT_PAGE_INIT moves
the largest silent stretch of early boot onto all CPUs (x86 guest boot
202 -> 130 ms); measured as a wash on arm64 (VZ and KVM), so only x86
takes it.
Co-authored-by: Hanzo Dev <[email protected]>
The vsock irq thread slept in poll() with a 500 ms timeout and the net rx
thread with 100 ms; reset() flipped the flag and joined, so every KVM
teardown waited out the remainder — the bulk of a 0.5 s tail on plain runs.
A shutdown eventfd in each poll set makes the join immediate.
Co-authored-by: Hanzo Dev <[email protected]>
A plain exec run attached a serial console wired to /dev/null: the guest
paid a PCI/MMIO device probe plus the hvc console registration (~45 ms on
VZ) to print nothing. The serial port now exists only in console or verbose
mode, and the cmdline names a console only then — dmesg still captures
everything. The console device name comes from the backend (hvc0 on VZ and
cloud-hypervisor, ttyAMA0 for the KVM PL011), which also makes --verbose
actually show kernel output on the KVM backend for the first time.
cloud-hypervisor's console is Off without a serial port, and its API/vsock
sockets move to a private per-pid dir cleaned on drop — previously they sat
next to the disk, which collides once throwaway disks share a tmpfs dir.
The vsock connect poll starts at 1 ms instead of a flat 10 ms.
Co-authored-by: Hanzo Dev <[email protected]>
A plain run's work rootfs is unlinked while the VM holds it open and never
read back, yet on ext4 data dirs every boot wrote ~300 MB through the page
cache and teardown evicted those extents again — multi-second writeback
stalls under repeated runs (spark: 0.55-3.3 s wall for the same command).
prepare_vm now learns the disk's fate up front: discard disks clone to
/dev/shm when reflink is unavailable and the tmpfs has room, so the copy is
a memory copy, nothing is written back, and teardown frees pages. prune
sweeps crash leftovers there too. Checkpoint creation and stdio mode keep
their disk in the instance dir as before.
Co-authored-by: Hanzo Dev <[email protected]>
copy_file_range refuses cross-filesystem copies (EXDEV, kernels >= 5.19),
which made any clone that crossed filesystems fall through to fs::copy and
materialize every hole. The sparse copy now switches to pread/pwrite per
extent when copy_file_range refuses, keeping the copy sparse at page-cache
speed. reflink_file exposes the FICLONE-only path so callers can decide
where the working copy lives when the filesystem cannot reflink.
Co-authored-by: Hanzo Dev <[email protected]>
Every platform can now bootstrap from the release: install.sh learns
linux-x86_64, and the OS asset set ships per-arch so assets.rs finds a
tarball on both Linux architectures instead of 404ing on x86_64.
Co-authored-by: Hanzo Dev <[email protected]>
The per-run rootfs clone copied the 4 GB sparse image with fs::copy,
which materializes every hole as written zeros on ext4 (~1.2 s of
zero-writes; spark regressed 0.99 s -> 1.5 s per run). Replace the
Linux path with one shared vm::clone_file: ioctl(FICLONE) reflink
where the filesystem supports it, otherwise SEEK_DATA/SEEK_HOLE +
copy_file_range over data extents with ftruncate keeping holes, with
fs::copy as the last resort. macOS keeps clonefile unchanged.
Co-authored-by: Hanzo Dev <[email protected]>
c-string literals and iterator loops for the copied device modules and
vm-guest, cargo fmt across the workspace, and a README section covering
Linux host prerequisites: /dev/kvm access, the arm64 vhost-vsock ACL,
and the cloud-hypervisor + virtiofsd binaries the x86_64 backend drives.
Co-authored-by: Hanzo Dev <[email protected]>
Serial fds are duplicated when the VirtualMachine is constructed — the
caller-side /dev/null only lives for the length of build(). Disks are
declared image_type=Raw so sector-0 writes (e2fsck, resize2fs in the
initramfs) are allowed, and non-interactive runs write console output
through the child stdout via File mode since Tty mode EBADFs on a
non-terminal stdout.
Co-authored-by: Hanzo Dev <[email protected]>
The 4GB rootfs image is mostly holes; GNU sparse entries keep the
release asset small and let assets.rs restore the holes on extract.
Co-authored-by: Hanzo Dev <[email protected]>
Boot straight to init=/usr/bin/vm-guest from /dev/vda; an initramfs is
only used when --initrd asks for one. Plain run attaches the work disk
without host durability and unlinks it while the VM holds it open —
teardown deletes nothing. The clone is warmed sequentially while VZ
starts, the image ships at the default --disk-size so the clone is never
extended, and the cmdline gains printk.time=1 plus mitigations=off (the
guest is disposable and secrets stay host-side in the proxy).
Co-authored-by: Hanzo Dev <[email protected]>
With no initramfs the guest owns what the initramfs used to do: static
proxy network setup (10.0.0.2 via 10.0.0.1) when a net device is present,
and growing ext4 to fill /dev/vda via the online-resize ioctl, off the
critical path. The target comes from the ext4 superblock, so a disk that
matches the image is an exact no-op.
Co-authored-by: Hanzo Dev <[email protected]>
crates/vm now aliases the platform crate once in lib.rs (vm-darwin on
macOS, vm-linux on aarch64 Linux, vm-ch on x86_64 Linux) and sandbox.rs
imports through the alias. Asset downloads use
hanzo-vm-os-<tag>-<arch>.tar.gz and the CLI upgrade knows linux-x86_64.
Co-authored-by: Hanzo Dev <[email protected]>
Exports the same device vocabulary as vm-darwin/vm-linux. The
VirtualMachine spawns cloud-hypervisor and drives it over the API
socket (vm.create/vm.boot on start, vm.shutdown + vmm.shutdown on
stop), spawns one virtiofsd per shared directory, and serves the
proxy socketpair from an in-process vhost-user-net backend (queue 0
RX, queue 1 TX, 12-byte virtio-net header stripped/prepended at the
socketpair boundary). Guest memory is shared=on for the vhost-user
devices. connect_to_vsock_port speaks the hybrid vsock handshake
(CONNECT <port> / OK <n>) and rewraps the UnixStream fd as TcpStream
for the platform-neutral sandbox API.
Co-authored-by: Hanzo Dev <[email protected]>
debootstrap arch and the vm-guest musl target now follow uname -m
(amd64/x86_64 alongside the existing arm64/aarch64 path).
Co-authored-by: Hanzo Dev <[email protected]>
Same feature set as the arm64 config (virtio-pci, virtio-console, vsock,
netfilter, cgroup2 + k3s set); the arch section swaps GICv3/PL011 for
PVH direct boot, kvmclock, and ACPI. cloud-hypervisor loads the
uncompressed ELF vmlinux via its PVH entry, so the build strips vmlinux
into Image instead of taking arch/*/boot/Image.
Co-authored-by: Hanzo Dev <[email protected]>
k3s programs iptables from inside the guest; bake /etc/hosts and the
hanzo-vm hostname into the image instead of relying on the guest
fallback.
Co-authored-by: Hanzo Dev <[email protected]>
Mount /sys/fs/cgroup as cgroup2 rw at boot, set the hostname to
hanzo-vm, and write /etc/hosts when the rootfs ships without one so
localhost and the hostname resolve.
Co-authored-by: Hanzo Dev <[email protected]>
CONFIG_CPUSETS was a k3s fatal; add the rest of the cgroup2 controller
set, runc needs (keys, seccomp, cgroup bpf), veth/vxlan/bridge-netfilter
and the xt matches kube-proxy programs, plus NFT_COMPAT so iptables-nft
userland works. All built-in; the kernel stays module-free.
Co-authored-by: Hanzo Dev <[email protected]>
fmt, clippy -D warnings and tests on hanzo-build-macos-arm64 with a
cargo check -p vm-linux leg on hanzo-build-linux-arm64; v* tags build
the codesigned CLI and the guest OS image and upload them as release
assets with gh.
Co-authored-by: Hanzo Dev <[email protected]>
Crates keep their history: vm (lib, was shuru-vm), hanzo-vm (CLI package
and binary, was shuru-cli), vm-guest, vm-proto, vm-proxy, vm-sdk,
vm-store, vm-darwin, vm-linux; all at 0.1.0, Apache-2.0, hanzoai/vm.
Config is vm.json, data lives in ~/.hanzo/vm (HANZO_VM_HOME), env vars
are HANZO_VM_*, the guest runs as /usr/bin/vm-guest, and assets download
from hanzoai/vm releases. The TypeScript SDK is @hanzo/vm. NOTICE
credits shuru as the origin. prepare-rootfs.sh builds the image natively
on arm64 Linux with mke2fs -d and re-execs in Docker only on macOS.
Includes rustfmt over the tree and fixes for clippy 1.98 lints.
Co-authored-by: Hanzo Dev <[email protected]>
The www/ Astro site, skills/, .claude/, GitHub workflows and changelogs
belong to the upstream project; CI moves to .hanzo/workflows.
Co-authored-by: Hanzo Dev <[email protected]>