Skip to content

Commit d78e845

Browse files
committed
WIP: Implement rootless execution
1 parent cefd9a3 commit d78e845

File tree

1 file changed

+92
-18
lines changed

1 file changed

+92
-18
lines changed

repro.in

Lines changed: 92 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,44 @@ orig_argv=("$0" "$@")
2121
src_owner=${SUDO_USER:-$USER}
2222
function check_root() {
2323
(( EUID == 0 )) && return
24-
if type -P sudo >/dev/null; then
24+
if ((rootless_userns)); then
25+
exec become-root unshare --mount "${orig_argv[@]}"
26+
elif type -P sudo >/dev/null; then
2527
exec sudo -- "${orig_argv[@]}"
2628
else
2729
exec su root -c "$(printf ' %q' "${orig_argv[@]}")"
2830
fi
2931
}
3032

33+
function require_userns_tools() {
34+
if command -v become-root >/dev/null \
35+
&& command -v nsjail >/dev/null \
36+
&& command -v fuse-overlayfs >/dev/null
37+
then
38+
return 0
39+
fi
40+
warning "nsjail, fuse-overlayfs and become-root are necessary for rootless operation"
41+
warning "https://github.com/giuseppe/become-root"
42+
warning "https://github.com/containers/fuse-overlayfs"
43+
warning "https://github.com/google/nsjail"
44+
return 1
45+
}
46+
47+
function mountoverlay() {
48+
if ((rootless_userns)); then
49+
fuse-overlayfs "$@"
50+
else
51+
mount -t overlayfs overlayfs "$@"
52+
fi
53+
}
54+
function umountoverlay() {
55+
if ((rootless_userns)); then
56+
fusermount -u "$@"
57+
else
58+
umount "$@"
59+
fi
60+
}
61+
3162
# Use a private gpg keyring
3263
function gpg() {
3364
command gpg --homedir="$BUILDDIRECTORY/gnupg" "$@"
@@ -103,6 +134,44 @@ function exec_nspawn(){
103134
-D "$BUILDDIRECTORY/$container" "${@:2}"
104135
}
105136

137+
138+
# Desc: Executes an command inside a given nsjail container
139+
# 1: Container name
140+
# 2: Optional: one --bind=... bindmount option
141+
# 2/3: Command to execute
142+
function exec_nsjail(){
143+
local container=$1
144+
local args=( -Mo --quiet
145+
--disable_clone_newuser
146+
--disable_clone_newnet
147+
--disable_rlimits
148+
--keep_caps
149+
--tmpfsmount /tmp
150+
--bindmount_ro /dev
151+
-E "PATH=/usr/local/sbin:/usr/local/bin:/usr/bin"
152+
--chroot "$BUILDDIRECTORY/$container" --rw
153+
)
154+
## use a no-op forking unshare as pid1
155+
if [[ $2 == --bind=* ]] ; then
156+
echo "!Execute: " nsjail "${args[@]}" --bindmount "${2#--bind=}" -- /usr/bin/unshare -f "${@:3}" >>/tmp/log
157+
nsjail "${args[@]}" --bindmount "${2#--bind=}" -- /usr/bin/unshare -f "${@:3}"
158+
else
159+
echo "!Execute: " nsjail "${args[@]}" -- /usr/bin/unshare -f "${@:2}" >>/tmp/log
160+
nsjail "${args[@]}" -- /usr/bin/unshare -f "${@:2}"
161+
fi
162+
}
163+
164+
function exec_container(){
165+
if ((rootless_userns)); then
166+
exec_nsjail "$@"
167+
else
168+
exec_nspawn "$@"
169+
fi
170+
}
171+
172+
173+
174+
106175
# Desc: Removes the root container
107176
function cleanup_root_volume(){
108177
warning "Removing root container..."
@@ -114,7 +183,7 @@ function cleanup_root_volume(){
114183
function remove_snapshot (){
115184
local build=$1
116185
msg2 "Delete snapshot for $build..."
117-
umount "$BUILDDIRECTORY/$build" || true
186+
umountoverlay "$BUILDDIRECTORY/$build" || true
118187
rm -rf "${BUILDDIRECTORY:?}/${build}"
119188
rm -rf "${BUILDDIRECTORY:?}/${build}_upperdir"
120189
rm -rf "${BUILDDIRECTORY:?}/${build}_workdir"
@@ -131,7 +200,7 @@ function create_snapshot (){
131200
msg2 "Create snapshot for $build..."
132201
mkdir -p "$BUILDDIRECTORY/"{"${build}","${build}_upperdir","${build}_workdir"}
133202
# shellcheck disable=SC2140
134-
mount -t overlay overlay \
203+
mountoverlay \
135204
-o lowerdir="$BUILDDIRECTORY/root",upperdir="$BUILDDIRECTORY/${build}_upperdir",workdir="$BUILDDIRECTORY/${build}_workdir" \
136205
"$BUILDDIRECTORY/${build}"
137206
touch "$BUILDDIRECTORY/$build"
@@ -143,7 +212,7 @@ function create_snapshot (){
143212
function build_package(){
144213
local build=$1
145214
local builddir=${2:-"/startdir"}
146-
exec_nspawn "$build" \
215+
exec_container "$build" \
147216
--bind="$PWD:/srcdest" \
148217
bash <<-__END__
149218
set -e
@@ -180,23 +249,23 @@ function init_chroot(){
180249
printf '%s.UTF-8 UTF-8\n' en_US de_DE > "$BUILDDIRECTORY"/root/etc/locale.gen
181250
printf 'LANG=en_US.UTF-8\n' > "$BUILDDIRECTORY"/root/etc/locale.conf
182251

183-
systemd-machine-id-setup --root="$BUILDDIRECTORY"/root
252+
exec_container root systemd-machine-id-setup
184253
msg2 "Setting up keyring, this might take a while..."
185-
exec_nspawn root pacman-key --init &> /dev/null
186-
exec_nspawn root pacman-key --populate archlinux &> /dev/null
254+
exec_container root pacman-key --init &> /dev/null
255+
exec_container root pacman-key --populate archlinux &> /dev/null
187256

188257
msg2 "Updating and installing base & base-devel"
189-
exec_nspawn root pacman -Syu base-devel --noconfirm
190-
exec_nspawn root pacman -R arch-install-scripts --noconfirm
191-
exec_nspawn root locale-gen
258+
exec_container root pacman -Syu base-devel --noconfirm
259+
exec_container root pacman -R arch-install-scripts --noconfirm
260+
exec_container root locale-gen
192261

193262
printf 'builduser ALL = NOPASSWD: /usr/bin/pacman\n' > "$BUILDDIRECTORY"/root/etc/sudoers.d/builduser-pacman
194-
exec_nspawn root useradd -m -G wheel -s /bin/bash -d /build builduser
263+
exec_container root useradd -m -G wheel -s /bin/bash -d /build builduser
195264
echo "keyserver-options auto-key-retrieve" | install -Dm644 /dev/stdin "$BUILDDIRECTORY/root"/build/.gnupg/gpg.conf
196-
exec_nspawn root chown -R builduser /build/.gnupg
265+
exec_container root chown -R builduser /build/.gnupg
197266
else
198267
printf 'Server = %s\n' "$HOSTMIRROR" > "$BUILDDIRECTORY"/root/etc/pacman.d/mirrorlist
199-
exec_nspawn root pacman -Syu --noconfirm
268+
exec_container root pacman -Syu --noconfirm
200269
fi
201270

202271
trap - ERR INT
@@ -247,7 +316,7 @@ function cmd_check(){
247316
} >> "$BUILDDIRECTORY/$build/etc/makepkg.conf"
248317

249318
# Father I have sinned
250-
exec_nspawn "build" \
319+
exec_container "build" \
251320
bash <<-__END__
252321
shopt -s globstar
253322
install -d -o builduser -g builduser /startdir
@@ -273,16 +342,16 @@ __END__
273342
msg2 "Finished preparing packages"
274343
msg "Installing packages"
275344
# shellcheck disable=SC2086
276-
exec_nspawn build --bind="$(readlink -e ${cachedir}):/cache" bash -c \
345+
exec_container build --bind="$(readlink -e ${cachedir}):/cache" bash -c \
277346
'yes y | pacman -Udd --overwrite "*" -- "$@"' -bash "${packages[@]}"
278347

279348
read -r -a buildinfo_packages <<< "$(buildinfo -f installed "${pkg}")"
280349
uninstall=$(comm -13 \
281350
<(printf '%s\n' "${buildinfo_packages[@]}" | rev | cut -d- -f4- | rev | sort) \
282-
<(exec_nspawn build --bind="$(readlink -e ${cachedir}):/cache" pacman -Qq | sort))
351+
<(exec_container build --bind="$(readlink -e ${cachedir}):/cache" pacman -Qq | sort))
283352

284353
if [ -n "$uninstall" ]; then
285-
exec_nspawn build pacman -Rdd --noconfirm -- $uninstall
354+
exec_container build pacman -Rdd --noconfirm -- $uninstall
286355
fi
287356

288357
build_package "build" "$builddir"
@@ -346,10 +415,15 @@ elif [[ -r "$HOME/.repro.conf" ]]; then
346415
fi
347416

348417

349-
while getopts :hdoC:P:M: arg; do
418+
while getopts :hdorC:P:M: arg; do
350419
case $arg in
351420
h) print_help; exit 0;;
352421
d) run_diffoscope=1;;
422+
r) rootless_userns=1;
423+
require_userns_tools || exit 1
424+
# TODO: better detection for valid writable build directory
425+
[[ $BUILDDIRECTORY == /var/lib/repro ]] && BUILDDIRECTORY="${XDG_CACHE_HOME:-$HOME/.cache}/archlinux-repro"
426+
;;
353427
*) ;;
354428
esac
355429
done

0 commit comments

Comments
 (0)