Commit Graph
49 Commits
Author SHA1 Message Date
vhaudiquet 031c6ba113 deps: drop the unused indicatif dependency
The library stopped drawing progress UI; rendering moved to ecr-cli.
2026-10-03 00:06:32 +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 3fb4be667c refactor(download): report download progress through the sink
The direct-tarball and OCI-blob download bars drew to stderr from
inside the library; download_image now emits Progress events with the
content length when the server provides one.
2026-10-03 00:02:11 +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 d481bd9028 feat(progress): add the progress reporting sink
Long-running library operations (downloads, extraction, initramfs
creation) will report their progress through a caller-supplied
Progress sink instead of drawing terminal UI themselves.  NoProgress
keeps library calls silent for consumers that want no output.
2026-10-02 23:46:57 +02:00
vhaudiquet 15d2271812 docs: document layer extraction deferral semantics
CI / Check (push) Successful in 1m11s
CI / Format (push) Successful in 15s
CI / Clippy (push) Successful in 1m13s
CI / Test (push) Successful in 1m22s
Records in SPEC.md how extract_tarball handles directory modes and
hard links the tar order cannot resolve inline; the constraint behind
the fedora:latest directory-hash extraction fix.
2026-09-30 22:00:59 +02:00
vhaudiquet dd653f7ccb fix(extract): defer directory modes and hard links during extraction
tar-rs applies a directory's tar mode as soon as the entry is
extracted, so images shipping read-only directories abort on their own
contents: fedora:latest's dr-xr-xr-x ca-trust tree fails with EACCES
on etc/pki/ca-trust/extracted/pem/directory-hash/002c0b4f.0. Directory
modes are now recorded and applied once after the last layer is
written.

Hard links were canonicalized inline, failing with ENOENT when the
target appears later in the tar, lives in an earlier layer, or exists
nowhere. Links are now applied after the file pass and fall back to a
verbatim symlink with a warning when the target is absent, matching
Docker/podman behaviour for these entries.

Verified against a real fedora:latest layer: extraction previously
aborted at the first directory-hash symlink, now all 438 entries land
with tls-ca-bundle.pem intact.
2026-09-30 22:00:59 +02:00
vhaudiquet f36897abc2 docs: document the riscv64 RVA23 profile CPU requirement
CI / Check (push) Successful in 1m8s
CI / Format (push) Successful in 15s
CI / Clippy (push) Successful in 1m9s
CI / Test (push) Successful in 1m23s
2026-09-21 01:24:11 +02:00
vhaudiquet ba90e0f367 fix(qemu): select the rva23s64 CPU for riscv64 under TCG
Ubuntu builds its riscv64 port against the RVA23 profile (since
25.10), and QEMU's default rv64 CPU does not implement all profile
extensions: Ubuntu binaries die with SIGILL during init and the
kernel panics with 'Attempted to kill init'. Select the rva23s64
profile CPU (QEMU 10.1+) for riscv64 under TCG; profiles are
supersets, so baseline rv64gc rootfses run unchanged on it.

KVM mode keeps -cpu host.
2026-09-21 01:24:11 +02:00
vhaudiquet 12f771d326 docs: document riscv64 kernel flavor and virt machine 2026-09-21 01:03:21 +02:00
vhaudiquet 810bf50814 fix(qemu): select the virt machine for riscv64
qemu-system-riscv64's default machine is spike, not virt: spike has no
PCI bus, so virtio-net-pci failed with "No 'PCI' bus found", and no
16550 UART, so console=ttyS0 output went nowhere. Pass -machine virt
explicitly for riscv64; other architectures keep their emulator's
default.
2026-09-21 01:03:21 +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 de507682c1 fix(chroot): resolve bare command names through PATH before exec
CI / Check (push) Successful in 1m11s
CI / Format (push) Successful in 15s
CI / Clippy (push) Successful in 1m8s
CI / Test (push) Successful in 1m23s
execve(2) does not search PATH, so `ecr alpine -- echo hi` failed with
ENOENT: the old code only used PATH as an existence check and then
still exec'd the bare name, which the kernel resolved relative to the
working directory.  Pre-dates the crate split, but bare names are the
natural CLI usage so it needs to work.

Programs are now resolved execvp-style: a bare name is looked up in
the caller-composed PATH (empty components skipped rather than treated
as the cwd), paths containing '/' are used as-is with a friendlier
error than a raw ENOENT.
2026-09-21 00:16:25 +02:00
vhaudiquet af06264e60 docs: document library crate, exec and rootfs cache apis
README gains a Library section covering prepare, the exec options
(envp, bind targets, arch) and the provisioned-rootfs hot-cell flow.
SPEC documents the workspace layout, the library API, the cache
sidecars (.digest, .provisioned) and the updated execution flow.
AGENTS.md scope paths follow the new crates/ layout.
2026-09-21 00:08:49 +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
vhaudiquet 7137aa15c5 docs: add AGENTS.md with commit and code conventions 2026-09-20 23:08:28 +02:00
vhaudiquet 2c47a5c662 docs: update QEMU mode docs for =PATH syntax and uncompressed initramfs
CI / Check (push) Successful in 1m7s
CI / Format (push) Successful in 14s
CI / Clippy (push) Successful in 1m8s
CI / Test (push) Successful in 1m24s
2026-09-20 22:37:33 +02:00
vhaudiquet 240a66c532 fix: initramfs hard links, streaming archive, verbatim argv transport
- hard links in the rootfs were written as zero-size entries with no
  inode set (all zero), so the kernel could link them to the wrong file;
  assign a synthetic inode per (device, inode) group.
- build the cpio archive by streaming entries to disk instead of holding
  the whole archive in memory.
- the VM command was joined into a single string and shell-expanded in
  the guest, mangling quotes and nested commands; pass the argv
  base64-encoded (ECR_ARGV) and exec it verbatim.
- parse ecr parameters from /proc/cmdline in the init script, set the
  hostname via procfs (arch has no hostname binary), and give the serial
  console a moment to drain before poweroff.
2026-09-20 22:37:33 +02:00
vhaudiquet d64da0671b fix: --kernel =PATH syntax, exit-code cleanup, bind warning in VM mode
- --kernel took an optional value greedily, swallowing the DISTRO
  positional: 'ecr --kernel alpine' failed with a missing-argument error
  and 'ecr --kernel alpine -- cmd' pulled a garbage OCI ref. require
  =PATH syntax for the kernel path instead.
- setup_namespaces returns the child's exit code (or 128+signal) instead
  of calling process::exit, so the extracted rootfs tempdir is cleaned
  up before exiting instead of leaking into /tmp.
- warn that --bind/--bind-rw are ignored when booting with --kernel.
2026-09-20 22:37:11 +02:00
vhaudiquet ef00776414 fix: return Ok after extracting kernel from APK
extract_kernel_from_apk fell through to an unconditional error after a
successful extraction, so every first --kernel run failed after
downloading the 45MB package; the second run worked from cache. Also
remove the downloaded APK even when extraction fails.
2026-09-20 22:36:49 +02:00
vhaudiquet e1d69eaed6 fix: make kernel verbose with --kernel -v 2026-06-19 16:51:24 +02:00
vhaudiquet 3188566b6e feat: download alpine linux-virt kernel for single --kernel 2026-06-19 16:46:23 +02:00
vhaudiquet 1d2031b3ca fix: fmt 2026-06-17 17:42:56 +02:00
vhaudiquet 49343e5811 fix: clippy 2026-06-17 17:42:41 +02:00
vhaudiquet f3aec10618 test: add unit tests for namespace and chroot modules
- Add tests for hostname format and uniqueness
- Add tests for chroot environment setup
- Add tests for environment isolation and PATH configuration
- Verify all 34 tests pass
2026-06-17 17:28:50 +02:00
vhaudiquet 5834630d60 refactor: multiple fixes
- Create unified Arch enum in utils.rs with methods for all naming conventions
- Replace 5 duplicate architecture mapping functions with single source of truth
- Add memory string validation for QEMU
- Add KVM capability verification via ioctl
- Fix TOCTOU race in resolv.conf writing using O_EXCL
- Extract magic numbers to named constants
- Use constants for stack size, buffer sizes, hostname entropy
2026-06-17 17:24:41 +02:00
vhaudiquet 6bd6f2cf77 feat: enable kvm with --kernel if relevant 2026-06-17 16:42:34 +02:00
vhaudiquet 09661ec9e0 feat: use uncompressed initramfs 2026-06-17 16:35:28 +02:00
vhaudiquet 503578d648 feat: terminate when init process exits (--kernel)
Add an init script to the initramfs that traps shell exit and triggers
poweroff via /proc/sysrq-trigger. This ensures QEMU terminates cleanly
when the user exits the shell, rather than hanging indefinitely.

Changes:
- Add /init script to initramfs that runs as PID 1
- Use -no-reboot QEMU flag to exit on guest poweroff
- Simplify kernel command line using environment variables
- Init script handles hostname, device creation, and poweroff on exit
2026-06-17 14:03:04 +02:00
vhaudiquet 7a37f99030 feat: detect shell for --kernel
- Move detect_shell() to new src/utils.rs for reuse
- Use detected shell in QEMU VM mode (bash when available)
- Update chroot.rs and qemu_vm.rs to use shared function
2026-06-17 11:13:05 +02:00
vhaudiquet d52310c0f4 feat: set unique hostname in QEMU VM mode
Generate hostname like "ecr-vm-<hex>" at boot time via kernel command
line, leaving initramfs cacheable. Set via echo to /etc/hostname and
hostname command before spawning shell.
2026-06-17 10:52:00 +02:00
vhaudiquet b3ffa89faa fix: handle hard links in initramfs creation
- Track inodes to avoid duplicating data for hard-linked files
- Use fast compression level for faster initramfs creation
2026-06-17 10:40:10 +02:00
vhaudiquet 3e3af3dab8 feat: add progressbar for initramfs compression 2026-06-17 10:11:04 +02:00
vhaudiquet 4475dff141 feat: add progressbar for extraction and initramfs creation 2026-06-17 00:11:12 +02:00
vhaudiquet a81e699619 fix: enable job control for shell with --kernel 2026-06-16 23:38:09 +02:00
vhaudiquet 8875bcc92a feat: do not display kernel logs with --kernel 2026-06-16 22:07:28 +02:00
vhaudiquet 931a6dcfd5 feat: use cpio crate for initramfs creation
Add the `cpio` crate as dependency, removing e2fsprogs external dependency.
2026-06-16 20:31:25 +02:00
vhaudiquet 4f44af4449 feat: add --kernel flag for QEMU system emulation mode
Add --kernel <PATH> option to boot extracted rootfs in a QEMU virtual
machine instead of namespace/chroot mode. The rootfs is converted to an
ext4 disk image using mke2fs and booted with the provided kernel.
2026-06-16 19:16:34 +02:00
vhaudiquet c163f89cb2 readme: add animated GIF with usage
CI / Format (push) Successful in 9s
CI / Check (push) Successful in 40s
CI / Clippy (push) Successful in 19s
CI / Test (push) Successful in 34s
2026-06-15 17:17:29 +02:00
vhaudiquet 3a0d78b048 fix: correctly propagate exit codes without printing them
CI / Clippy (push) Successful in 23s
CI / Format (push) Failing after 10m51s
CI / Check (push) Failing after 12m5s
CI / Test (push) Failing after 13m28s
2026-06-15 17:01:59 +02:00
vhaudiquet 91d6225946 ci: add ci pipeline
CI / Check (push) Failing after 11m45s
CI / Test (push) Failing after 11m59s
CI / Clippy (push) Failing after 13m13s
CI / Format (push) Failing after 14m14s
2026-06-09 19:47:23 +02:00
vhaudiquet b12a8db34b readme: README.md 2026-06-09 19:15:38 +02:00
vhaudiquet 04ed1f25ae feat: basic functionality 2026-06-09 19:09:57 +02:00
vhaudiquet a3339f9d34 feat: hello, world! 2026-06-08 18:44:10 +02:00
vhaudiquet 5cb0428876 initial commit: SPEC.md, implementation spec 2026-06-08 18:40:31 +02:00