bwrap mode
Stand in for bubblewrap under Steam's pressure-vessel without user namespaces.
The same binary speaks bubblewrap's command line when it is invoked through a
name ending in bwrap, busybox style. A symlink is the whole setup:
ln -s proot bwrap
./bwrap --version
# bubblewrap 0.9.0 (proot-next)Steam's pressure-vessel tries its bundled bwrap first, which fails without
user namespaces, then uses $BWRAP or a bwrap on PATH. Point either at
the symlink:
export BWRAP=$HOME/.local/bin/bwrapSteam's own steam-runtime-check-requirements probes the same variable with
--bind / / true, which is why the start-up check that insists on user
namespaces is satisfied too.
What the options become
There are no namespaces, so bwrap's description of a container is materialized
as a fresh private root directory under $XDG_RUNTIME_DIR and a set of
bindings:
| bwrap | proot-next |
|---|---|
--bind, --dev-bind, --ro-bind, the -try variants, --bind-fd, --ro-bind-fd | Bindings. Read-only ones refuse writes with EROFS, including mkdir, unlink, rename and chmod. |
--remount-ro | Marks the matching binding read-only. |
--proc, --dev | Bindings of the host's /proc and /dev. |
--dir, --tmpfs, --mqueue, --symlink, --file, --bind-data, --ro-bind-data, --perms, --chmod | Created in the private root. --size is ignored. |
--chdir, --setenv, --unsetenv, --clearenv, --argv0, --args | As in bwrap. |
--seccomp, --add-seccomp-fd | The program is installed in the child after proot-next's own filter. ERRNO outranks notification, so a caller's denials still apply. |
--info-fd, --json-status-fd | {"child-pid": N} after the fork; the status fd also gets {"exit-code": N}. |
--die-with-parent | Already the behavior. |
--new-session | setsid() in the child. |
--hostname | Reported by uname(2). Dropped in a nested launch when the outer launch has no -k or --hostname. |
--unshare-*, --share-net, --uid, --gid, --cap-*, --userns*, --pidns, --lock-file, --sync-fd, --block-fd, --as-pid-1 | Accepted and ignored. |
--overlay-* | Refused. |
The private root is removed when the command exits. PROOT_BWRAP_KEEP=1
keeps it for inspection.
Inside a guest
A program running under proot-next that execs bwrap, whether the symlink
above, a copy of proot-next, or the rootfs's own bubblewrap, gets the
container it described as a nested view served by the supervisor already
there: no second supervisor, no namespaces, the same materialized root and
bindings composed through the caller's own view. GTK's glycin image loaders
(every GTK 4 application, FileZilla's icons through its file chooser) run
inside their sandbox root this way instead of falling back to running
unsandboxed. The match on the name bwrap can be turned off with
PROOT_NEST=0.
Steam
install-steam.sh sets a Debian-based amd64 image or system up for it: Steam
from Valve's repository, its keys checked against their fingerprints, the
32-bit libraries the client and games need, the release binary for the
architecture, checked against the SHA-256 the release publishes, in
/usr/local/lib/proot-next with bwrap beside it, and Steam's launcher
diverted so that every way of starting Steam exports BWRAP to it. The
bwrap name stays off PATH, so nothing but Steam picks it up. Run it as
root, or under fakeroot in a rootless image build:
curl -fsSL https://raw.githubusercontent.com/selkies-project/proot-next/main/install-steam.sh | sudo shPROOT_NEXT_VERSION=0.3.9 installs that release rather than the latest, and
PREFIX moves /usr/local. Run again, it rewires an earlier install,
proot-bwrap's included, to the binary it installs. The selkies KDE desktop
images carry it.
With BWRAP set, the Steam client's own browser helper, every Steam Linux
Runtime container and every Proton launch run through proot-next. The things
that made the helper impossible under PRoot are handled: a supervisor must not
look like a debugger, so TracerPid is hidden and breakpoints are resumed; and
/proc/<pid>/fd/N links re-open the object they name, which Chromium's shared
memory depends on.