Repository navigation
feat(install): Install into a home directory without root - #2
Merged
Merged
Conversation
ananos
force-pushed
the
feat/rootless-containerd
branch
from
September 21, 2026 21:08
ceabe45 to
3107b7b
Compare
ananos
force-pushed
the
feat/install-user-mode
branch
from
September 21, 2026 21:08
194a0a4 to
67a6eea
Compare
ananos
force-pushed
the
feat/rootless-containerd
branch
from
September 21, 2026 21:10
3107b7b to
1d90bff
Compare
ananos
force-pushed
the
feat/install-user-mode
branch
from
September 21, 2026 21:10
67a6eea to
c685345
Compare
ananos
force-pushed
the
feat/rootless-containerd
branch
from
September 21, 2026 22:04
1d90bff to
c42790f
Compare
ananos
force-pushed
the
feat/install-user-mode
branch
from
September 21, 2026 22:04
c685345 to
761fd08
Compare
ananos
marked this pull request as ready for review
September 21, 2026 22:09
install.sh needed root because the layout was fixed at build time: the generated wrappers, configs and units carry /var/lib/brig/data as literal paths, so the only way into a home directory was to rebuild the bundle for that prefix. A published tarball could not serve an unprivileged user at all. Nothing else stood in the way. No binary in the bundle carries the path -- brig, urunc, nerdctl and containerd all come back empty -- so exactly nine generated text files decide where a tree thinks it lives, and install.sh can rewrite them after unpacking. pins.env now records the layout it was built with, and retarget_tree greps for it afterwards: a tenth file added later fails the install instead of half-working on the host. A non-root caller now gets a user install by default, and --system / --user force it either way: - the tree in ~/.local/share/brig, launchers in ~/.local/bin, socket in $XDG_RUNTIME_DIR - no group, no thin pool (losetup and dmsetup are not available in a user namespace, so a user install is overlayfs only), and no /etc/urunc symlink, which the user unit replaces with URUNC_CONFIG_FILE - the last stage runs brig-rootless-setup.sh, which already did the per-user work for a shared install - a preflight for the host prerequisites, split between what stops the install and what brig-rootless-setup.sh asks sudo for once - brig-uninstall.sh keys off the stamp, so a user install comes down as that user, taking the user unit, the per-user config and the containerd data root with it Verified end to end on an Ubuntu 24.04 host with a stub sudo that refuses every call: the install completes, brig doctor is green from the home prefix, and a guest boots on 6.18.0urunc with a project bind-mounted at /work, read and written from inside the VM. The shim, virtiofsd and qemu all run as the invoking user out of the home prefix. An unprepared prefix is refused and exits 1 rather than half-installing. The preflight also has to ask the right question about the devices. Testing /dev/kvm for read and write tests it from a login shell, where this user's kvm group still applies -- and that group is precisely what the user namespace drops, so the check passes on exactly the hosts whose first sandbox then dies with EPERM. It now looks for what the setup grants: this user named in the ACL, or a mode that covers everyone. setfacl joins the list of things only an admin can supply, beside newuidmap, since the grant is made with it. And the warning counts what it found instead of always saying "two", which otherwise reads as a second problem the reader has failed to notice. Signed-off-by: Anastassios Nanos <ananos@nofire.ai>
ananos
force-pushed
the
feat/install-user-mode
branch
from
September 21, 2026 22:10
761fd08 to
7910264
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR #1 leaves the prefix baked into the generated configs, so a published tarball
cannot serve an unprivileged user: the only route into a home directory is to
rebuild the bundle for that prefix. This is the
install.sh --usermode its"Out of scope" section points at.
It is mostly a text rewrite. No binary in the bundle carries the path --
brig,urunc,nerdctlandcontainerdall come back empty fromstrings | grep /var/lib/brig-- so exactly nine generated files decide where atree thinks it lives:
bin/brig-ctl,bin/brig-pool-up,bin/brig-rootless-setup.sh,etc/brig-env.sh,etc/containerd.toml,etc/nerdctl.toml,etc/urunc.tomland the two units underetc/systemd/.pins.envnow records the layout the bundle was built with, andretarget_treerewrites those files after unpacking, then greps for the old prefix and fails if
any of it survived. A tenth file added later stops the install rather than
half-working on a host.
A non-root caller gets a user install by default, and
--system/--userforce it either way. The tree goes to
~/.local/share/brig, the launchers to~/.local/bin, the socket to$XDG_RUNTIME_DIR. No group, no thin pool(
losetupanddmsetupare not available in a user namespace, so a userinstall is overlayfs only), and no
/etc/uruncsymlink, which the user unitalready replaces with
URUNC_CONFIG_FILE. The last stage runsbrig-rootless-setup.sh, so the per-user work stays in the one place thatalready did it.
brig-uninstall.shkeys off the stamp, so a home install comesdown as its own user, taking the user unit, the per-user config and the
containerd data root with it.
The preflight is in two halves. What nothing can fix without an admin stops the
install: no subuid range, no
newuidmap, no systemd user session. Whatbrig-rootless-setup.shasks sudo for once is reported and left to it, theAppArmor profile and the device ACLs. Failing hard on those blocked the install
before the script that fixes them had been unpacked.
Tested
On an Ubuntu 24.04 amd64 host, with a stub
sudothat refuses every call andlogs it, so "no root" is measured rather than assumed. That log came back empty
across the install, the run and the uninstall.
files, and
brig doctorcomes up green from the home prefixLinux urunc 6.18.0urunc, with theproject bind-mounted at
/work,virtiofs rwin the guest, read from andwritten to, and the guest's write landing on the host owned by the invoking
user
prefix, served by a containerd on the user's own config
brig-ctl uninstallas that user removes the tree, the per-user config, thecontainerd data root, the launchers and the user unit, and leaves a system
install on the same host running
it warns before unpacking anything, stops at the sudo it cannot make, and
exits 1
CI's lint steps pass locally:
sh -nandshellcheck -s shoverinstall.sh,build-bundle.sh, the generated uninstaller and the generated setup. The bundlejob gains one assertion, that
pins.envrecordsPREFIX,DATA_DIRandRUN_DIR, since the retarget reads them from there.Based on #1
Stacked on
feat/rootless-containerd, becauseinstall.sh --userrunsbrig-rootless-setup.sh, which arrives in that branch. Rebase ontomainonce#1 merges, or squash the two if one commit reads better.
Out of scope
qemu still comes from the host rather than the bundle, so a user with no root at
all needs it installed alongside the host prerequisites above. A home install is
also one copy of the tree per user where a root install is shared, so
brig-ctl rootlessagainst a shared install stays the better trade with rootand several users.