11 Commits
Author SHA1 Message Date
vhaudiquet 593ce20f4f feat(error): type the public library API with ecr::Error
anyhow at the public boundary left library consumers unable to match
on failure modes: every mistake was an opaque error, and the ones
worth branching on (host prerequisites, unsupported architectures)
could only be recognized by string matching.

Public functions now return Result<T, ecr::Error> (thiserror) with
semantic variants for those conditions: user namespaces unavailable,
binfmt_misc unregistered, architecture not served by the registry,
missing subuid/subgid entry.  anyhow stays the internal error type;
everything else converts to Error::Other, which transparently wraps
the full context chain.  Errors raised inside the namespace child
cross the process boundary as formatted chains and also surface as
Other; the error pipe wraps them in anyhow before formatting so the
chain still survives.  The CLI maps the error back into anyhow, so
its output is unchanged.
2026-10-04 21:20:51 +02:00
vhaudiquet 22f1999470 feat(cli): start the command in the mapped current directory
The default --bind overlay mounted the current directory at
/root/<basename>, but the command still started in /root: the
library's working-directory fallback only considers read-write
binds.  Pass the mapped target as the explicit working_dir so
relative paths keep meaning what they meant on the host.

Bind sources are canonicalized when the targets are built
instead of at mount time.  This fixes --bind . and other
basename-less relative paths, which cli_mount_target rejected,
and lets the --bind/--bind-rw dedup match relative and absolute
spellings of the same directory.
2026-10-03 00:53:58 +02:00
vhaudiquet fe0b99ee6b refactor(verbose): replace the global flag and veprintln! with log
Diagnostics now go through the log facade: the library no longer
decides where output goes, and consumers plug in their own sink (the
CLI installs a stderr logger printing warnings always and ecr debug
diagnostics under -v; dependency records stay filtered out).  The
kernel 'quiet' flag moves from the global verbosity flag to an
explicit QemuConfig field.
2026-10-03 00:13:34 +02:00
vhaudiquet b3ad0ab3e0 feat(cli): render library progress with indicatif
The CLI owns the terminal now: TermProgress turns the library's
Progress events into bars and spinners on stderr (hidden by indicatif
when stderr is not a terminal).  Named transfers (OCI layers, kernel,
tarball) render as byte bars, extraction and initramfs as file-count
spinners.
2026-10-03 00:06:22 +02:00
vhaudiquet 1cfcf0e433 refactor(qemu): report initramfs progress through the sink
create_initramfs emits Progress events instead of drawing a spinner
inside the library; the cpio walker counts the entries it visits so
the verbose summary no longer reads the count off a progress bar.
2026-10-03 00:05:47 +02:00
vhaudiquet 71768f71d7 refactor(kernel): report kernel download progress through the sink
get_default_kernel emits Progress events for the APK transfer instead
of drawing a bar to stderr from inside the library.
2026-10-03 00:03:26 +02:00
vhaudiquet 82d2ed3382 refactor(extract): report extraction progress through the sink
The extraction spinner drew straight to stderr from inside the
library, so every consumer got terminal UI it never asked for.
extract_tarball and its helpers now count extracted files themselves
and emit Progress events; rendering belongs to the caller.
2026-10-02 23:51:42 +02:00
vhaudiquet 5dd9dff76a fix(kernel): fall back to linux-lts where linux-virt is absent
Alpine does not build the linux-virt flavor for riscv64, so --kernel
without a path failed there with 'linux-virt package not found'. Scan
the APKINDEX once for both flavors and prefer linux-virt, taking
linux-lts when the virt flavor is missing.

Also store the cached kernel decompressed: riscv64 and aarch64 ship
their image gzipped (Image.gz), and QEMU's riscv -kernel loader
understands only ELF, uImage and raw images, so the gzipped image hung
at boot after the OpenSBI banner.
2026-09-21 01:03:18 +02:00
vhaudiquet b6ddd85525 feat(rootfs): cache-aware rootfs preparation with persist hook
Add ecr::rootfs with the full rootfs lifecycle behind the library:

- RootfsCache::prepare resolves an image reference, downloads through
  the tarball cache (with the OCI :latest digest freshness check) and
  extracts into a scratch directory tracked by PreparedRootfs.
- PreparedRootfs::persist packs the current rootfs back into its cache
  entry (compressed to match the entry's extension, symlinks and
  permissions preserved) and marks it with a .provisioned sidecar.
- RootfsCache::prepare_provisioned composes both into the hot-cell
  flow: provision once, and every later call sharing the cache skips
  the download and the provisioning step.

extract: an "oci-" cache entry without a layers.manifest is a
persisted provisioned rootfs; extract it as a plain archive.

The CLI now drives prepare and drops its inline cache/orchestration
code and the dirs/tempfile dependencies.  The binfmt check moves ahead
of the download so foreign-arch runs fail before pulling an image.
2026-09-21 00:06:20 +02:00
vhaudiquet 4f669fb5ec feat(exec): exec API with caller envp, bind targets and arch
Add ecr::exec: run a command inside a prepared rootfs in fresh
user/PID/mount/UTS namespaces, with the caller composing the full
environment (ExecOptions::env), the bind targets (BindTarget, with
explicit absolute mount points inside the rootfs) and the target
architecture (binfmt_misc is verified for foreign arches).

mount::setup_mounts now takes &[BindTarget] instead of parallel
read-only/read-write path lists; chroot::run_chroot takes the envp and
a resolved working directory, and chroot::default_env composes the
previous hardcoded environment as a starting point for callers.

The CLI maps its flags onto the new API; behavior is unchanged
(overlay at /root/<basename>, rw bind at /mnt/<basename>, cwd default).
2026-09-20 23:34:54 +02:00
vhaudiquet b6e5b4f006 refactor: split ecr into library and cli crates
The root package becomes a virtual workspace: `crates/ecr` holds the
library (package name `ecr`) and `crates/ecr-cli` the command line
front-end, which keeps installing the `ecr` binary. No behavior change.

The library must not depend on CLI types, so mount::setup_mounts now
takes the `no_bind` flag instead of a `&Args`.
2026-09-20 23:29:03 +02:00