Commit Graph
21 Commits
Author SHA1 Message Date
vhaudiquet 23fcd713b6 feat(vm): capture the payload's output into a host directory
The console is one stream, kernel and guest mixed, so a consumer that
needs the payload's stdout and stderr apart - rhema's sandbox seam
reports them as separate fields - could not get it. With capture set,
the run adds the directory as an internal rw share (guest path
/.ecrou-io, reserved) and the init wraps the payload in a redirection
group pointing at it: the streams land host-side as DIR/stdout and
DIR/stderr, written live, owned by the host user, and there when run()
returns. The console stays on stdout for diagnostics; the CLI exposes
the flag as --capture.
2026-10-28 13:37:33 +01:00
vhaudiquet db2658f385 docs: drop the removed --arch flag from the CLI table 2026-10-28 13:20:08 +01:00
vhaudiquet 74c3744d39 fix(cli): remove the --arch flag
KVM runs only the host architecture, so the flag could not mean what
it suggested: a mismatched value downloaded a foreign rootfs that
extracted cleanly and died at boot with a cryptic guest-side exec
error. The flag's real job, picking the image variant to fetch, is
the library's; RunOptions.arch keeps that role and an empty value
still resolves to the host architecture.
2026-10-28 13:20:02 +01:00
Valentin Haudiquet 29cd6af609 docs: the networking section describes what is wired and how fast it is 2026-10-28 01:00:16 +01:00
Valentin Haudiquet 6955b5ebdb feat(krun): explicit vsock with AF_INET hijacked for TSI
The engine materializes a heuristics-driven TSI by default; disable
it and add an explicit vsock hijacking AF_INET so guest networking is
deterministic rather than sniffed. Throughput is unchanged in this
build (TLS crawls at tens of KB/s against a CDN either way) - that is
the new ln driver's own pace and belongs upstream; the flag at least
pins down which path the guest takes.
2026-10-28 01:00:08 +01:00
Valentin Haudiquet 7adbf41f59 fix(rootfs): prefer the uplink resolver, drop loopback nameservers
Copying /etc/resolv.conf verbatim gives the guest systemd-resolved's
stub at 127.0.0.53, and loopback never crosses the engine's socket
proxy: the name lookup dies inside the guest. Source the uplink
config from /run/systemd/resolve first and strip 127.x nameserver
lines from whatever is copied.
2026-10-28 01:00:08 +01:00
Valentin Haudiquet 7d9dc6650e fix(rootfs): give fresh rootfs files the host's resolver
The engine proxies guest sockets to the host's stack, so the host's
resolv.conf works in the machine as-is, loopback stub included. The
images ship no usable resolver, so without this every fetch inside
the guest fails to resolve.
2026-10-28 00:43:56 +01:00
Valentin Haudiquet a91c1b6b51 refactor(vm): the engine owns a LogLevel, not UI booleans
RunOptions carried quiet/verbose, which are flags, not engine state.
They are now a LogLevel enum (Quiet/Normal/Verbose) mapped onto
libkrun's filter; the CLI maps its flags onto it. The forked child's
last-gasp boot diagnostics also stop going straight to stderr: they
land in the console file the parent streams, with stderr kept for the
case where the console was never configured. lib.rs now states the
crate contract: no UI in the library, progress through the sink,
logging through the facade, typed errors out.
2026-10-28 00:43:56 +01:00
Valentin Haudiquet a27879dbe7 fix(vm): a single quoted payload argument runs through the image's shell
'ecrou run alpine -- "apk add x && y"' reached the guest as one argv
element and execvp'd a program named after the whole line. One
argument containing whitespace is a quoted shell line: hand it to the
image's shell as 'sh -c'. Argv vectors stay literal, including ones
that begin with sh -c.
2026-10-28 00:43:56 +01:00
Valentin Haudiquet 2e91db9acc docs: where the snap's libraries come from until distro packaging catches up 2026-10-28 00:18:58 +01:00
Valentin Haudiquet 934f0482db docs: how the machine process, exit codes, and stdio really work 2026-10-28 00:09:59 +01:00
Valentin Haudiquet d9de1a1f0a feat(vm): the machine runs as a child; the console streams through a file
The exit-code contract needed a rethink once the real libkrun was in
the loop: on a normal payload exit the VMM ends its own process with
the payload's code, so run() forks and the child enters the VM loop.
The parent reaps it and the code flows to the consumer; the control
file stays as the boot-failure diagnostic, and the guest script no
longer powers off - the init blob does that when the script exits,
since the libkrunfw kernel has no sysrq.

The console cannot ride stdio: with a tty on fd 0 libkrun stops a
background caller with SIGTTOU, and without one it routes guest bytes
through its logger. So the console goes to a file in the scratch that
the parent streams to real stdout, with a final drain after the reap;
the child unwires fd 0 to /dev/null so the terminal is never touched
and a Ctrl-C to the process group still reaches the guest. PDEATHSIG
keeps T4: a killed consumer is a killed machine. The hostname and
workdir now ride the set_exec config natively (the init blob applies
them); log level follows quiet/verbose.

Also in the CLI: the command after '--' was parsed but never copied
into RunOptions, so every run executed the image's default shell.
2026-10-28 00:09:21 +01:00
Valentin Haudiquet 6a222d474d feat(cli): run and probe commands
run maps the flags onto RunOptions, promotes the first read-write share
to the workspace, inherits stdio, and exits with the guest's code; probe
reports what a run would find (KVM, libkrun loadability with the
remediation, nested virt, cache writability) and fails when a run
would. T6, the no-mount audit, lives here where the binary is built,
ignored behind ECROU_BIN for CI.
2026-10-27 22:38:49 +01:00
Valentin Haudiquet 348c4dd93c docs: the guest init owns the workdir 2026-10-27 22:36:31 +01:00
Valentin Haudiquet 5c9b1b00c2 feat(vm): one-shot machines through libkrun
run() probes KVM, prepares the rootfs through the cache, generates the
guest init into the scratch, wires a libkrun context to it, and blocks
in krun_start_enter on this thread; the exit code comes back through
the control file, and a killed consumer kills the machine with the
process. Shares are validated and tagged in one place so the script and
the virtiofs setup stay ordered; anything overlapping the scratch
rootfs is refused, since the guest could otherwise rewrite its own
init. The guest environment is composed from scratch; the host
environment never crosses.
2026-10-27 22:36:31 +01:00
Valentin Haudiquet cc9151eb56 feat(guest): the guest init script
Generated into the scratch rootfs before boot: mounts each virtiofs
share (ro through the mount option), runs the payload as a child so the
init survives to write its exit code to the control file, then powers
off through sysrq, which works without a poweroff binary in the rootfs.
Shell quoting keeps payload argv and share paths free of interpolation.
2026-10-27 22:32:13 +01:00
Valentin Haudiquet 18d81c2e7d feat(krun): dlopen bindings to the stable ABI
Eleven calls from the stable-1.19.x headers plus krun_free_ctx, loaded
at runtime so a missing install names the remediation instead of failing
to link. Symbol lookup goes through a macro that carries the signature
from libkrun.h, every unsafe block states its invariant, and negative
returns map onto errno errors. The KVM probe ports ecr's open-then-
KVM_GET_API_VERSION check and reports KvmUnavailable with the full
remediation text.
2026-10-27 22:31:35 +01:00
Valentin Haudiquet 9855776847 feat: port the image machinery from ecr
ecrou's workspace starts from ecr's confinement-neutral half: image
reference resolution, the content cache with digest freshness, layer
extraction with whiteouts and deferred directory modes, provisioned
hot cells, and the progress/error/config plumbing. Adaptations: the
cache lives in ~/.cache/ecrou, the namespace-era error variants and
utils constants are gone, KvmUnavailable arrives ahead of its caller,
rootfs loses its exec() delegation, and uname(2) goes through libc
now that nix is no longer a dependency. The CLI crate is a stub until
its commit.
2026-10-27 22:27:24 +01:00
Valentin Haudiquet 2bda5f8554 docs: conventions for working in ecrou 2026-10-27 22:13:49 +01:00
Valentin Haudiquet acf040e5b5 docs: make libkrun the v1 engine
QEMU was the committed v1 engine with libkrun later; that order is
flipped. libkrun deletes the initramfs pack, the kernel fetch, slirp
config, and the uid mapping problem, and its VMM runs in-process, which
collapses the kill chain. Verified against the stable-1.19.x headers:
eleven-call v1 surface, krun_set_workdir native, mapped volumes dead on
Linux (tagged virtiofs plus guest init mount instead), KVM required
with QEMU as the later TCG fallback. Snap spike now stages libkrun
plus libkrunfw instead of qemu-system.
2026-10-27 21:41:30 +01:00
Valentin Haudiquet 5f9b2a251b docs: the ecrou specification
ecrou descends from ecr: the microVM runtime that survives strict snap
confinement. The founding rule (no host mounts, no namespaces, every
crossing a file or a socket) is stated with its syscall budget, sourced
from snapd's seccomp allowlist; execution model, exit-code propagation
through a private control share, kill-chain cancellation, the spike-snap
gate, and seven falsification tests round out the v1 design.
2026-10-27 21:30:13 +01:00