Skip to content

Commit e17ee44

Browse files
committed
WIP: Implement rootless execution
1 parent 7e36a5f commit e17ee44

File tree

1 file changed

+130
-63
lines changed

1 file changed

+130
-63
lines changed

repro.in

Lines changed: 130 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,43 @@ DIFFOSCOPE="diffoscope"
2020
orig_argv=("$0" "$@")
2121
src_owner=${SUDO_USER:-$USER}
2222
function check_root() {
23-
(( EUID == 0 )) && return
24-
if type -P sudo >/dev/null; then
25-
exec sudo -- "${orig_argv[@]}"
26-
else
27-
exec su root -c "$(printf ' %q' "${orig_argv[@]}")"
28-
fi
23+
(( EUID == 0 )) && return
24+
if ((rootless_userns)); then
25+
exec become-root unshare --mount "${orig_argv[@]}"
26+
elif type -P sudo >/dev/null; then
27+
exec sudo -- "${orig_argv[@]}"
28+
else
29+
exec su root -c "$(printf ' %q' "${orig_argv[@]}")"
30+
fi
31+
}
32+
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
2960
}
3061

3162
# Use a private gpg keyring
@@ -36,58 +67,58 @@ function gpg() {
3667
function init_gnupg() {
3768
[ ! -d "$BUILDDIRECTORY/_gnupg" ] && mkdir -p "$BUILDDIRECTORY/_gnupg"
3869

39-
# ensure signing key is available
40-
gpg --auto-key-locate nodefault,wkd --locate-keys [email protected]
70+
# ensure signing key is available
71+
gpg --auto-key-locate nodefault,wkd --locate-keys [email protected]
4172
}
4273

4374
# Desc: Sets the appropriate colors for output
4475
function colorize() {
45-
# prefer terminal safe colored and bold text when tput is supported
46-
if tput setaf 0 &>/dev/null; then
47-
ALL_OFF="$(tput sgr0)"
48-
BOLD="$(tput bold)"
49-
BLUE="${BOLD}$(tput setaf 4)"
50-
GREEN="${BOLD}$(tput setaf 2)"
51-
RED="${BOLD}$(tput setaf 1)"
52-
YELLOW="${BOLD}$(tput setaf 3)"
53-
else
54-
ALL_OFF="\e[0m"
55-
BOLD="\e[1m"
56-
BLUE="${BOLD}\e[34m"
57-
GREEN="${BOLD}\e[32m"
58-
RED="${BOLD}\e[31m"
59-
YELLOW="${BOLD}\e[33m"
60-
fi
61-
readonly ALL_OFF BOLD BLUE GREEN RED YELLOW
76+
# prefer terminal safe colored and bold text when tput is supported
77+
if tput setaf 0 &>/dev/null; then
78+
ALL_OFF="$(tput sgr0)"
79+
BOLD="$(tput bold)"
80+
BLUE="${BOLD}$(tput setaf 4)"
81+
GREEN="${BOLD}$(tput setaf 2)"
82+
RED="${BOLD}$(tput setaf 1)"
83+
YELLOW="${BOLD}$(tput setaf 3)"
84+
else
85+
ALL_OFF="\e[0m"
86+
BOLD="\e[1m"
87+
BLUE="${BOLD}\e[34m"
88+
GREEN="${BOLD}\e[32m"
89+
RED="${BOLD}\e[31m"
90+
YELLOW="${BOLD}\e[33m"
91+
fi
92+
readonly ALL_OFF BOLD BLUE GREEN RED YELLOW
6293
}
6394
colorize
6495

6596
# Desc: Message format
6697
function msg() {
67-
local mesg=$1; shift
98+
local mesg=$1; shift
6899
# shellcheck disable=SC2059
69-
printf "${GREEN}==>${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
100+
printf "${GREEN}==>${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
70101
}
71102

72103
# Desc: Sub-message format
73104
function msg2() {
74-
local mesg=$1; shift
105+
local mesg=$1; shift
75106
# shellcheck disable=SC2059
76-
printf "${BLUE} ->${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
107+
printf "${BLUE} ->${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
77108
}
78109

79110
# Desc: Warning format
80111
function warning() {
81-
local mesg=$1; shift
112+
local mesg=$1; shift
82113
# shellcheck disable=SC2059
83-
printf "${YELLOW}==> $(gettext "WARNING:")${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
114+
printf "${YELLOW}==> $(gettext "WARNING:")${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
84115
}
85116

86117
# Desc: Error format
87118
function error() {
88-
local mesg=$1; shift
119+
local mesg=$1; shift
89120
# shellcheck disable=SC2059
90-
printf "${RED}==> $(gettext "ERROR:")${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
121+
printf "${RED}==> $(gettext "ERROR:")${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
91122
}
92123

93124
##
@@ -134,11 +165,42 @@ lock_close() {
134165
function exec_nspawn(){
135166
local container=$1
136167
systemd-nspawn -q \
137-
--as-pid2 \
138-
--register=no \
139-
--pipe \
140-
-E "PATH=/usr/local/sbin:/usr/local/bin:/usr/bin" \
141-
-D "$BUILDDIRECTORY/$container" "${@:2}"
168+
--as-pid2 \
169+
--register=no \
170+
--pipe \
171+
-E "PATH=/usr/local/sbin:/usr/local/bin:/usr/bin" \
172+
-D "$BUILDDIRECTORY/$container" "${@:2}"
173+
}
174+
# Desc: Executes an command inside a given nsjail container
175+
# 1: Container name
176+
# 2: Optional: one --bind=... bindmount option
177+
# 2/3: Command to execute
178+
function exec_nsjail(){
179+
local container=$1
180+
local args=( -Mo --quiet
181+
--disable_clone_newuser
182+
--disable_clone_newnet
183+
--disable_rlimits
184+
--keep_caps
185+
--tmpfsmount /tmp
186+
--bindmount_ro /dev
187+
-E "PATH=/usr/local/sbin:/usr/local/bin:/usr/bin"
188+
--chroot "$BUILDDIRECTORY/$container" --rw
189+
)
190+
## use a no-op forking unshare as pid1
191+
if [[ $2 == --bind=* ]] ; then
192+
nsjail "${args[@]}" --bindmount "${2#--bind=}" -- /usr/bin/unshare -f "${@:3}"
193+
else
194+
nsjail "${args[@]}" -- /usr/bin/unshare -f "${@:2}"
195+
fi
196+
}
197+
198+
function exec_container(){
199+
if ((rootless_userns)); then
200+
exec_nsjail "$@"
201+
else
202+
exec_nspawn "$@"
203+
fi
142204
}
143205

144206
# Desc: Removes the root container
@@ -152,7 +214,7 @@ function cleanup_root_volume(){
152214
function remove_snapshot (){
153215
local build=$1
154216
msg2 "Delete snapshot for $build..."
155-
umount "$BUILDDIRECTORY/$build" || true
217+
umountoverlay "$BUILDDIRECTORY/$build" || true
156218
rm -rf "${BUILDDIRECTORY:?}/${build}"
157219
rm -rf "${BUILDDIRECTORY:?}/${build}_upperdir"
158220
rm -rf "${BUILDDIRECTORY:?}/${build}_workdir"
@@ -169,7 +231,7 @@ function create_snapshot (){
169231
msg2 "Create snapshot for $build..."
170232
mkdir -p "$BUILDDIRECTORY/"{"${build}","${build}_upperdir","${build}_workdir"}
171233
# shellcheck disable=SC2140
172-
mount -t overlay overlay \
234+
mountoverlay \
173235
-o lowerdir="$BUILDDIRECTORY/root",upperdir="$BUILDDIRECTORY/${build}_upperdir",workdir="$BUILDDIRECTORY/${build}_workdir" \
174236
"$BUILDDIRECTORY/${build}"
175237
touch "$BUILDDIRECTORY/$build"
@@ -181,7 +243,7 @@ function create_snapshot (){
181243
function build_package(){
182244
local build=$1
183245
local builddir=${2:-"/startdir"}
184-
exec_nspawn "$build" \
246+
exec_container "$build" \
185247
--bind="$PWD:/srcdest" \
186248
bash <<-__END__
187249
set -e
@@ -205,6 +267,7 @@ function init_chroot(){
205267

206268
# Prepare root chroot
207269
if [ ! -d "$BUILDDIRECTORY"/root ]; then
270+
208271
lock 9 "$BUILDDIRECTORY"/root.lock
209272
msg "Preparing chroot"
210273
trap '{ cleanup_root_volume; exit 1; }' ERR
@@ -218,30 +281,30 @@ function init_chroot(){
218281
printf '%s.UTF-8 UTF-8\n' en_US de_DE > "$BUILDDIRECTORY"/root/etc/locale.gen
219282
printf 'LANG=en_US.UTF-8\n' > "$BUILDDIRECTORY"/root/etc/locale.conf
220283

221-
systemd-machine-id-setup --root="$BUILDDIRECTORY"/root
284+
exec_container root systemd-machine-id-setup
222285
msg2 "Setting up keyring, this might take a while..."
223-
exec_nspawn root pacman-key --init &> /dev/null
224-
exec_nspawn root pacman-key --populate archlinux &> /dev/null
286+
exec_container root pacman-key --init &> /dev/null
287+
exec_container root pacman-key --populate archlinux &> /dev/null
225288

226289
msg2 "Updating and installing base & base-devel"
227-
exec_nspawn root pacman -Syu base-devel --noconfirm
228-
exec_nspawn root pacman -R arch-install-scripts --noconfirm
229-
exec_nspawn root locale-gen
290+
exec_container root pacman -Syu base-devel --noconfirm
291+
exec_container root pacman -R arch-install-scripts --noconfirm
292+
exec_container root locale-gen
230293

231294
printf 'builduser ALL = NOPASSWD: /usr/bin/pacman\n' > "$BUILDDIRECTORY"/root/etc/sudoers.d/builduser-pacman
232-
exec_nspawn root useradd -m -G wheel -s /bin/bash -d /build builduser
295+
exec_container root useradd -m -G wheel -s /bin/bash -d /build builduser
233296
echo "keyserver-options auto-key-retrieve" | install -Dm644 /dev/stdin "$BUILDDIRECTORY/root"/build/.gnupg/gpg.conf
234-
exec_nspawn root chown -R builduser /build/.gnupg
297+
exec_container root chown -R builduser /build/.gnupg
235298
lock_close 9
236299
else
237-
238300
if lock 9 "$BUILDDIRECTORY"/root.lock; then
239-
printf 'Server = %s\n' "$HOSTMIRROR" > "$BUILDDIRECTORY"/root/etc/pacman.d/mirrorlist
240-
exec_nspawn root pacman -Syu --noconfirm
301+
printf 'Server = %s\n' "$HOSTMIRROR" > "$BUILDDIRECTORY"/root/etc/pacman.d/mirrorlist
302+
exec_container root pacman -Syu --noconfirm
241303
lock_close 9
242304
else
243305
msg "Couldn't acquire lock on root chroot, didn't update."
244-
fi
306+
fi
307+
245308
fi
246309
trap - ERR INT
247310
}
@@ -276,7 +339,6 @@ function cmd_check(){
276339
pkgbuild_sha256sum="${buildinfo[pkgbuild_sha256sum]}"
277340
SOURCE_DATE_EPOCH="${buildinfo[builddate]}"
278341

279-
280342
local build="${pkgbase}_$$"
281343

282344
msg2 "Preparing packages"
@@ -304,7 +366,7 @@ function cmd_check(){
304366
sed -i "s/LocalFileSigLevel.*//g" "$BUILDDIRECTORY/$build/etc/pacman.conf"
305367

306368
# Father I have sinned
307-
exec_nspawn "$build" \
369+
exec_container "$build" \
308370
bash <<-__END__
309371
shopt -s globstar
310372
install -d -o builduser -g builduser /startdir
@@ -326,16 +388,16 @@ __END__
326388

327389
msg "Installing packages"
328390
# shellcheck disable=SC2086
329-
exec_nspawn "$build" --bind="$(readlink -e ${cachedir}):/cache" bash -c \
391+
exec_container "$build" --bind="$(readlink -e ${cachedir}):/cache" bash -c \
330392
'yes y | pacman -Udd --overwrite "*" -- "$@"' -bash "${packages[@]}"
331393

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

337399
if [ -n "$uninstall" ]; then
338-
exec_nspawn "$build" pacman -Rdd --noconfirm -- $uninstall
400+
exec_container "$build" pacman -Rdd --noconfirm -- $uninstall
339401
fi
340402

341403
build_package "$build" "$builddir"
@@ -392,18 +454,23 @@ fi
392454

393455
xdg_repro_dir="${XDG_CONFIG_HOME:-$HOME/.config}/archlinux-repro"
394456
if [[ -r "$xdg_repro_dir/repro.conf" ]]; then
395-
# shellcheck source=/dev/null
396-
source "$xdg_repro_dir/repro.conf"
457+
# shellcheck source=/dev/null
458+
source "$xdg_repro_dir/repro.conf"
397459
elif [[ -r "$HOME/.repro.conf" ]]; then
398-
# shellcheck source=/dev/null
399-
source "$HOME/.repro.conf"
460+
# shellcheck source=/dev/null
461+
source "$HOME/.repro.conf"
400462
fi
401463

402464

403-
while getopts :hdoC:P:M: arg; do
465+
while getopts :hdorC:P:M: arg; do
404466
case $arg in
405467
h) print_help; exit 0;;
406468
d) run_diffoscope=1;;
469+
r) rootless_userns=1;
470+
require_userns_tools || exit 1
471+
# TODO: better detection for valid writable build directory
472+
[[ $BUILDDIRECTORY == /var/lib/repro ]] && BUILDDIRECTORY="${XDG_CACHE_HOME:-$HOME/.cache}/archlinux-repro"
473+
;;
407474
*) ;;
408475
esac
409476
done

0 commit comments

Comments
 (0)