diff --git a/README.md b/README.md new file mode 100644 index 0000000..2a54580 --- /dev/null +++ b/README.md @@ -0,0 +1,117 @@ +# slocker-lite + +`slocker-lite` mounts an [OCI Image Layout](https://github.com/opencontainers/image-spec/blob/main/image-layout.md) +tar (the format produced by `skopeo`, `podman save --format oci-archive`, or a modern +`docker save`) and runs a sandboxed command against it — without `podman`, `docker`, +or kernel overlayfs. + +## Why + +The target environment is Android with a stock kernel: `podman`/`docker` don't run +there (missing namespace support), and there's no kernel overlayfs. `slocker-lite` +works around both: it imports image layers into `containers-storage` and mounts them +with `fuse-overlayfs` (userspace, no kernel overlayfs needed), then sandboxes the run +with `bwrap` in "degraded mode" — using only whichever `--unshare-xxx` namespaces the +running kernel actually supports, instead of requiring the full set. + +## Status + +Early-stage. Mounting, running, and dropping privileges to a specific user/group all +work. Volumes and image-declared networking (`Volumes`/`ExposedPorts`/`Env` from the +image config) are parsed but not yet applied, and there's no background/daemonized +run mode yet. + +## Requirements + +Build-time: +- Meson + a C++20 compiler +- `fmt`, `libarchive`, `nlohmann_json`, `yaml-0.1` (libyaml) +- `spdlog` (uses the system package if found, otherwise fetched automatically via + the vendored `subprojects/spdlog.wrap`) +- `catch2`, only if the `enable_tests` Meson option is on (default: on) + +Runtime: +- `containers-storage` +- `fuse-overlayfs` +- `bwrap` (bubblewrap) +- `nsenter` (only needed for non-root runs — see "How it works" below) + +## Build + +```sh +meson setup buildDir +meson compile -C buildDir +meson test -C buildDir +``` + +This also builds `buildDir/slocker-lite-priv-drop`, a small statically-linked helper +that `-r --user`/`--group` needs at runtime (see "How it works"). + +## Usage + +``` +slocker-lite -m|--mount +slocker-lite -r|--run [-- [args...]] +slocker-lite -u|--umount +slocker-lite -c|--cleanup +slocker-lite -l|--list-images +slocker-lite -t|--test +slocker-lite -h|--help +slocker-lite -V|--version +``` + +| Flag | Description | +| --- | --- | +| `-m, --mount ` | Validate and mount an OCI Image Layout tar. | +| `-r, --run ` | Mount, run `bwrap` in the foreground, then unmount and clean up on exit. Defaults to the image's own `Entrypoint`/`Cmd` (or `/bin/sh` if neither is set); pass `-- [args...]` to override. | +| `-u, --umount ` | Unmount a previously mounted layer (the ID printed by `--mount`/`--run`, or from `containers-storage layers`). | +| `-c, --cleanup ` | Delete a layer and its ancestor chain from local storage (unmount it first). | +| `-n, --no-nsenter` | With `--run`, bind the mount directly instead of `nsenter`-ing into `fuse-overlayfs`'s namespace. Automatic when running as root; use this to force it off otherwise. | +| `--user ` | With `--run`, run the command as this user (name or numeric uid) instead of the image's own declared user (or root, if it declares none). Resolved against the image's own `/etc/passwd`. Only takes effect when `--run` executes as root. | +| `--group ` | With `--user`, use this group (name or numeric gid) instead of the user's primary group. | +| `-l, --list-images ` | List OCI Image Layout tars (`*.tar`, `*.tar.*`) found directly in ``, with their `name:tag`. | +| `-t, --test` | Print which `bwrap --unshare-xxx` namespaces the running kernel supports. | +| `--log-level ` | Set log verbosity (`trace`, `debug`, `info`, `warn`, `error`, `critical`, `off`). | +| `-h, --help` | Print usage and exit. | +| `-V, --version` | Print version information and exit. | + +### Examples + +```sh +# Mount an image and inspect it (prints the merged mount path) +./buildDir/slocker-lite -m myimage.tar + +# Mount, run the image's default command, then unmount and clean up +./buildDir/slocker-lite -r myimage.tar + +# Run a specific command instead +./buildDir/slocker-lite -r myimage.tar -- /bin/sh -c 'echo hello' + +# Run as a specific user (as root only) +sudo ./buildDir/slocker-lite -r myimage.tar --user git + +# List every OCI image tar in a directory +./buildDir/slocker-lite -l ./images +``` + +## How it works + +Image layers are imported into `containers-storage` (parent-chained) and the +resulting top layer is mounted via `fuse-overlayfs`; `-r/--run` then sandboxes the +requested command with `bwrap`. Because `containers-storage mount` runs rootless by +re-execing into a private user+mount namespace, `-r/--run` normally has to `nsenter` +into that namespace to reach the mount — except when running as root, where the mount +is already directly visible and `--unshare-user` is skipped entirely (a fresh user +namespace isn't needed for root's own privilege, and forces an unrelated +supplementary-group bug in that case). Running as root also unlocks `--user`/ +`--group`: since `bwrap --uid`/`--gid` require a user namespace that isn't available +there, `slocker-lite` instead bind-mounts a separate, statically-linked helper +(`slocker-lite-priv-drop`) into the sandbox and routes the command through it to drop +privileges before exec. + +See `CLAUDE.md` for the full architecture writeup (file-by-file breakdown, the +reasoning behind each of the above, and known gaps). + +## License + +GPL-2.0-or-later. See [`COPYING`](COPYING).