Building
Building from source, the static binaries, the tests and the documentation.
Toolchain
A stable Rust toolchain, edition 2024, Rust 1.89 or newer. The only crate
dependency is libc, and there are no native libraries to install.
rustup component add rustfmt clippy
cargo build --release # target/release/proot, dynamically linked to the host glibcStatic binaries
Releases ship static musl binaries. .cargo/config.toml configures both
targets to link self-contained with Rust's own linker, using the C runtime
objects rustup ships, so no cross toolchain is needed on either side:
rustup target add x86_64-unknown-linux-musl aarch64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl
cargo build --release --target aarch64-unknown-linux-muslThe aarch64 binary cross-builds on an x86_64 machine the same way. It cannot be run there: ptrace does not work under emulation, so the aarch64 tests run on aarch64 hardware, which is also what CI does.
Tests
tests/probes/build.sh # 32-bit probes, static musl
scripts/fetch-rootfs.sh ghcr.io/linuxserver/proot-apps:debian-trixie rootfs
PROOT_TEST_ROOTFS=$PWD/rootfs cargo test # unit tests + tests/e2e.rs
cargo clippy --all-targets -- -D warnings
pre-commit run --all-files # rustfmt, clippy, codespelltests/e2e.rs drives the built binary: the smoke script, a real rootfs,
bwrap mode, debugger transparency, /proc link semantics, unix sockets
through bindings, the i386 socketcall client and the 32-bit ARM probe.
Each case is a failure first seen in a real application. Cases whose rootfs
or probe is absent skip with a message. A change to translation, exec or the tracer
is also run against a desktop application through proot-apps and, when it
touches bwrap mode, against Steam in a selkies container with
tests/steam/check.sh:
tests/steam/check.sh --home ~/steamtest --display :20 --force \
--games ~/steamtest/games --wgltri ~/steamtest/games/wgltri.exe --client /usr/lib/steam/bin_steam.shIt runs the soldier, sniper and scout-on-soldier containers, OpenGL and Vulkan
for both architectures against what the host itself renders with, an X11
client, a native benchmark, a Windows OpenGL program through GE-Proton from
~/Steam/compatibilitytools.d, and the client's sign-in window. --games
names a root holding usr/bin/glmark2 and usr/bin/vkcube under the home,
where the containers see it (dpkg-deb -x of the packages will do); a binary
the runtime cannot load is skipped. GE-Proton runs in the runtime its manifest
asks for, as Steam runs it: GE-Proton 10 in sniper, GE-Proton 11 in Steam
Linux Runtime 4, which an anonymous steamcmd cannot fetch. --home
holds the runtimes as Steam installs them under Steam/steamapps/common.
--force makes pressure-vessel take the stand-in even where its own
bubblewrap works, and --proot names a build to check rather than the
installed one. The Windows program is built from tests/steam/wgltri.c:
x86_64-w64-mingw32-gcc -O2 -o wgltri.exe tests/steam/wgltri.c -lopengl32 -lgdi32 -luser32 -mconsoleA host without 32-bit driver libraries leaves native 32-bit games on llvmpipe while 64-bit Proton keeps the GPU; on NVIDIA that is the container runtime's doing, whose CDI specification carries no 32-bit libraries (nvidia-container-toolkit#2035). The check compares each architecture's renderer in the container with the host's, which tells that apart from a fault of bwrap mode.
Diagnostics while developing
| Variable | Effect |
|---|---|
PROOT_VERBOSE=1..3 | Errors and fatal signals with the module and address they happened at, every translation, every ptrace stop. |
PROOT_EXEC_DEBUG=1 | Each rewritten execve with the loader and argv it was given. |
PROOT_BWRAP_KEEP=1 | Keep the private root bwrap mode materializes. |
Documentation
The usage site is a fumadocs application in docs/,
the code reference is rustdoc, and the docs workflow publishes both to one
GitHub Pages site. Locally:
cd docs && npm install && npm run dev # http://localhost:3000
npm run build # static export in docs/out
cargo doc --no-deps --open # code referenceNo lockfile is kept. The one transitive dependency that has to stay put,
mdast-util-to-markdown, is pinned through overrides in package.json.