afca0f7ce3
Long-only (no free short letter left), lists each volume's name and host directory, tab-aligned the same way -l/--list-images already aligns name:tag output -- list_volumes_command() reuses that scheme. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
151 lines
6.7 KiB
Markdown
151 lines
6.7 KiB
Markdown
# 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. Named volumes (`-v/--volume`) can be created and are persisted in the config
|
|
file, but aren't consumed by `-r/--run` yet. Image-declared networking
|
|
(`ExposedPorts`/`Env` from the image config, and the image's own separately-declared
|
|
`Volumes`) 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 -v|--volume <name> <directory>
|
|
slocker-lite --list-volumes
|
|
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`. |
|
|
| `-v, --volume <name> <dir>` | Create a named volume mapped to a host directory (created if missing), recorded in the config file's `volumes` section. Fails if the name or directory is already used by an existing volume. |
|
|
| `--list-volumes` | List all named volumes (see `-v/--volume`) with their host directory. |
|
|
| `-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
|
|
|
|
# Create a named volume backed by a host directory
|
|
./buildDir/slocker-lite -v mydata ~/slocker-volumes/mydata
|
|
|
|
# List all named volumes
|
|
./buildDir/slocker-lite --list-volumes
|
|
```
|
|
|
|
## Configuration
|
|
|
|
Persistent settings can be kept in a local YAML config file at
|
|
`$XDG_CONFIG_HOME/slocker-lite/config.yaml` (falling back to
|
|
`$HOME/.config/slocker-lite/config.yaml` if `XDG_CONFIG_HOME` isn't set). The file is
|
|
organized into sections:
|
|
|
|
```yaml
|
|
global:
|
|
log-level: debug
|
|
volumes:
|
|
mydata: /home/user/slocker-volumes/mydata
|
|
```
|
|
|
|
`global.log-level` is the only standing preference supported today (one-shot
|
|
commands like `--mount`/`--run`/`--user` don't belong in a config file). An explicit
|
|
`--log-level` on the command line always overrides the config file. The `volumes`
|
|
section is managed by `-v/--volume` (see above) rather than hand-edited — it's not
|
|
consumed by `-r/--run` yet. A missing config file is fine either way (nothing is
|
|
overridden, and one gets created the first time `-v/--volume` is used).
|
|
|
|
## 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).
|