Commit Graph
8 Commits
Author SHA1 Message Date
vhaudiquet b4dafccae7 feat(cli): add ecrou pull
Fetch or refresh an image's cache entry without booting it. The
command is the CLI face of the run/pull split: runs never contact a
registry on a cached image, pull does - reporting the entry path
and whether anything transferred. --no-cache forces a redownload.
SPEC documents the command and the run/pull trust model.
2026-10-30 01:04:24 +01:00
vhaudiquet 4a0c613ecb feat(cli): docker-style run flags and local rootfs references
The run interface read like a library's. `ecrou run -it alpine` now
hands the guest a terminal: with no console output file, libkrun wires
the guest console to the process's own stdio and raw-modes the tty.
The parent saves and restores the termios around the run, because
libkrun's own restore lives in the child's exit observers, which a
SIGKILLed machine never runs. A tty run refuses to start without a
terminal on stdin and stdout, and --capture refuses to combine with
-t: the payload's streams cannot land in files and on the terminal at
once.

-i without -t keeps the machine's stdin open through pipes: libkrun
exposes the non-tty streams as virtio console ports (krun-stdin,
krun-stdout, krun-stderr), and the guest init points the payload's
stdio at whichever port nodes exist. `echo hi | ecrou run -i alpine --
cat` crosses the machine; a stream the host left on a tty has no port
node and stays on the console.

-v (--volume HOST:GUEST[:ro]) replaces --share, and -w aliases
--workdir. The global -v/--verbose loses its short flag to make room;
verbose stays reachable by its long form.

A reference starting with ./, ../ or / names a local rootfs archive:
read in place on every run, never downloaded, never cached as a
pristine entry. Provisioning over one persists into a derived cache
entry keyed by size and mtime, so the caller's file is never
overwritten and an edited archive lands in a fresh entry.
2026-10-29 18:08:30 +01:00
vhaudiquet 878ce29b8d feat(cli): provision layers from a spec file
A provision command baked by hand - `ecrou provision alpine -- apk
add ...` - cannot be reviewed or shared: the artifact worth keeping is
a diff in git, not a shell history line. A spec file is that diff.
`ecrou provision --spec image.yaml` parses base, packages and a run
block, derives the provisioning script from the base's own package
manager (apt, apk, dnf, discovered at provision time on the running
machine), and bakes the result into a cache entry keyed by the spec's
content hash: the same file never provisions twice, an edited file
never reuses the old layer, and the key hashes the raw bytes so the
layer is stable across ecrou versions.

`--layer NAME` names the entry, and `ecrou run --layer NAME` boots it
directly - no image resolution, no download; the layer is whatever
the provision call persisted under that name. A keyed entry has no
tarball of its own, so the base downloads only on the first
provisioning and every later run extracts the finished layer.
2026-10-29 18:06:50 +01:00
vhaudiquet d9f9c79dc4 feat(cli): ecrou provision bakes a command into a cache entry
prepare_provisioned and persist were sitting there with no consumer:
the machinery for "install once, every later boot carries it" that
one-shot machines cannot do on their own - packages, users, anything
short of a share - and no way to reach it.

ecrou provision IMAGE -- COMMAND is the missing surface. It boots a
machine over a freshly prepared rootfs, runs the command, and
persists the tree into the cache entry, marked provisioned; a marked
entry is extracted as-is on every later call, the command skipped.
A failing command leaves the entry unprovisioned for the next
attempt, and --no-cache re-downloads before re-provisioning.

The engine behind run splits out as vm::run_prepared: the boot half
of run, taking the rootfs the provisioner is about to persist. run
itself is prepare plus that.

One cache fix the provision flow surfaced: a digest refresh replaced
the tarball but left the old .provisioned marker, so a fresh pristine
image would present as provisioned and skip provisioning for good.
The marker now dies with the entry it belonged to.
2026-10-28 16:20:37 +01:00
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 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 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 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