Add README.md

Human-facing overview (what/why/status, build/usage instructions, brief
architecture summary) alongside CLAUDE.md, which stays the dense
agent-facing reference.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
This commit is contained in:
2026-08-21 12:10:16 +00:00
parent 0b1698dc97
commit 38fac22612
+117
View File
@@ -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 <image.tar>
slocker-lite -r|--run <image.tar> [-- <command> [args...]]
slocker-lite -u|--umount <layer-id>
slocker-lite -c|--cleanup <layer-id>
slocker-lite -l|--list-images <directory>
slocker-lite -t|--test
slocker-lite -h|--help
slocker-lite -V|--version
```
| Flag | Description |
| --- | --- |
| `-m, --mount <image.tar>` | Validate and mount an OCI Image Layout tar. |
| `-r, --run <image.tar>` | 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 `-- <command> [args...]` to override. |
| `-u, --umount <layer-id>` | Unmount a previously mounted layer (the ID printed by `--mount`/`--run`, or from `containers-storage layers`). |
| `-c, --cleanup <layer-id>` | 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 <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 <group>` | With `--user`, use this group (name or numeric gid) instead of the user's primary group. |
| `-l, --list-images <dir>` | List OCI Image Layout tars (`*.tar`, `*.tar.*`) found directly in `<dir>`, with their `name:tag`. |
| `-t, --test` | Print which `bwrap --unshare-xxx` namespaces the running kernel supports. |
| `--log-level <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).