-
Notifications
You must be signed in to change notification settings - Fork 38
/
Copy pathrepro.in
executable file
·350 lines (298 loc) · 10.6 KB
/
repro.in
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
#!/bin/bash
set -eE -o pipefail
BUILDDIRECTORY=/var/lib/repro
BOOTSTRAPMIRROR=https://mirror.archlinux.no/iso/latest
readonly bootstrap_img=archlinux-bootstrap-"$(date +%Y.%m)".01-"$(uname -m)".tar.gz
CONFIGDIR='REPRO_CONFIG_DIR'
# HOSTMIRROR=$(curl -s 'https://www.archlinux.org/mirrorlist/?protocol=https' | awk '/^#Server/ {print $3; exit}')
## Hardcoded until further notice
HOSTMIRROR="http://mirror.neuf.no/archlinux/\$repo/os/\$arch"
# IMGDIRECTORY=$(mktemp -dt .arch_img)
IMGDIRECTORY="/tmp/arch_img"
mkdir -p $IMGDIRECTORY
DIFFOSCOPE="diffoscope"
# Desc: Escalates privileges
orig_argv=("$0" "$@")
src_owner=${SUDO_USER:-$USER}
function check_root() {
(( EUID == 0 )) && return
if type -P sudo >/dev/null; then
exec sudo -- "${orig_argv[@]}"
else
exec su root -c "$(printf ' %q' "${orig_argv[@]}")"
fi
}
# Desc: Sets the appropriate colors for output
function colorize() {
# prefer terminal safe colored and bold text when tput is supported
if tput setaf 0 &>/dev/null; then
ALL_OFF="$(tput sgr0)"
BOLD="$(tput bold)"
BLUE="${BOLD}$(tput setaf 4)"
GREEN="${BOLD}$(tput setaf 2)"
RED="${BOLD}$(tput setaf 1)"
YELLOW="${BOLD}$(tput setaf 3)"
else
ALL_OFF="\e[0m"
BOLD="\e[1m"
BLUE="${BOLD}\e[34m"
GREEN="${BOLD}\e[32m"
RED="${BOLD}\e[31m"
YELLOW="${BOLD}\e[33m"
fi
readonly ALL_OFF BOLD BLUE GREEN RED YELLOW
}
colorize
# Desc: Message format
function msg() {
local mesg=$1; shift
# shellcheck disable=SC2059
printf "${GREEN}==>${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
}
# Desc: Sub-message format
function msg2() {
local mesg=$1; shift
# shellcheck disable=SC2059
printf "${BLUE} ->${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
}
# Desc: Warning format
function warning() {
local mesg=$1; shift
# shellcheck disable=SC2059
printf "${YELLOW}==> $(gettext "WARNING:")${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
}
# Desc: Error format
function error() {
local mesg=$1; shift
# shellcheck disable=SC2059
printf "${RED}==> $(gettext "ERROR:")${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
}
# Desc: Executes an command inside a given nspawn container
# 1: Container name
# 2: Command to execute
function exec_nspawn(){
local container=$1
systemd-nspawn -q --as-pid2 --register=no -D "$BUILDDIRECTORY/$container" "${@:2}"
}
# Desc: Removes the root container
function cleanup_root_volume(){
warning "Removing root container..."
rm -rf "$BUILDDIRECTORY/root"
}
# Desc: Removes a given snapshot
# 1: Snapshot name
function remove_snapshot (){
local build=$1
msg2 "Delete snapshot for $build..."
umount "$BUILDDIRECTORY/$build" || true
rm -rf "${BUILDDIRECTORY:?}/{${build},${build}_upperdir,${build}_workdir}"
trap - ERR INT
}
# Desc: Creates a snapshot of the root container
# 1: name of container
function create_snapshot (){
local build=$1
trap '{ remove_snapshot $build ; exit 1; }' ERR INT
msg2 "Create snapshot for $build..."
mkdir -p "$BUILDDIRECTORY/"{"${build}","${build}_upperdir","${build}_workdir"}
# shellcheck disable=SC2140
mount -t overlay overlay \
-o lowerdir="$BUILDDIRECTORY/root",upperdir="$BUILDDIRECTORY/${build}_upperdir",workdir="$BUILDDIRECTORY/${build}_workdir" \
"$BUILDDIRECTORY/${build}"
touch "$BUILDDIRECTORY/$build"
}
# Desc: Build a package inside a container
# 1: Container name
# 2: Container buildpath
function build_package(){
local build=$1
local builddir=${2:-"/build"}
exec_nspawn "$build" \
--bind="$PWD:/srcdest" \
bash <<-__END__
set -e
install -d -o builduser -g builduser /pkgdest
install -d -o builduser -g builduser /srcpkgdest
mkdir -p $builddir
cd $builddir
sudo -u builduser SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH PKGDEST=/pkgdest SRCPKGDEST=/srcpkgdest makepkg -sc --noconfirm
__END__
mkdir -p "./$build"
for pkgfile in "$BUILDDIRECTORY/$build"/pkgdest/*; do
mv "$pkgfile" "./$build/"
done
chown -R "$src_owner" "./$build"
}
# Desc: Sets up a container with the correct files
function init_chroot(){
set -e
if [ ! -d "$BUILDDIRECTORY" ]; then
mkdir -p $BUILDDIRECTORY
fi
# Prepare root chroot
if [ ! -d "$BUILDDIRECTORY"/root ]; then
msg "Preparing chroot"
trap '{ cleanup_root_volume; exit 1; }' ERR
trap '{ cleanup_root_volume; trap - INT; kill -INT $$; }' INT
msg2 "Extracting image into container..."
mkdir -p $BUILDDIRECTORY/root
tar xvf "$IMGDIRECTORY/$bootstrap_img" -C "$BUILDDIRECTORY/root" --strip-components=1 > /dev/null
printf 'Server = %s\n' "$HOSTMIRROR" > "$BUILDDIRECTORY"/root/etc/pacman.d/mirrorlist
printf '%s.UTF-8 UTF-8\n' en_US de_DE > "$BUILDDIRECTORY"/root/etc/locale.gen
printf 'LANG=en_US.UTF-8\n' > "$BUILDDIRECTORY"/root/etc/locale.conf
cp "$makepkg_conf" "$BUILDDIRECTORY"/root/etc/makepkg.conf
cp "$pacman_conf" "$BUILDDIRECTORY"/root/etc/pacman.conf
systemd-machine-id-setup --root="$BUILDDIRECTORY"/root
msg2 "Setting up keyring, this might take a while..."
# exec_nspawn root pacman-key --init #&> /dev/null
# exec_nspawn root pacman-key --populate archlinux #&> /dev/null
# exec_nspawn root pacman-key --refresh #&> /dev/bull
msg2 "Updating and installing base & base-devel"
exec_nspawn root pacman -Syu base-devel --noconfirm
exec_nspawn root pacman -R arch-install-scripts --noconfirm
exec_nspawn root locale-gen
printf 'builduser ALL = NOPASSWD: /usr/bin/pacman\n' > "$BUILDDIRECTORY"/root/etc/sudoers.d/builduser-pacman
exec_nspawn root useradd -m -G wheel -s /bin/bash builduser
echo "keyserver-options auto-key-retrieve" | install -Dm644 /dev/stdin "$BUILDDIRECTORY/root"/home/builduser/.gnupg/gpg.conf
exec_nspawn root chown -R builduser /home/builduser/.gnupg
cp "$makepkg_conf" "$BUILDDIRECTORY"/root/etc/makepkg.conf
cp "$pacman_conf" "$BUILDDIRECTORY"/root/etc/pacman.conf
else
printf 'Server = %s\n' "$HOSTMIRROR" > "$BUILDDIRECTORY"/root/etc/pacman.d/mirrorlist
cp "$makepkg_conf" "$BUILDDIRECTORY"/root/etc/makepkg.conf
cp "$pacman_conf" "$BUILDDIRECTORY"/root/etc/pacman.conf
exec_nspawn root pacman -Syu --noconfirm
fi
trap - ERR INT
}
# Desc: Reproduces a package
# 1: Location of package
function cmd_check(){
local pkg="${1}"
local cachedir="cache"
if [[ ! -f "${pkg}" ]]; then
error "no pkg.tar.xz given"
exit 1
fi
trap - ERR INT
msg "Starting build..."
create_snapshot "build" 0
build="build"
declare -A buildinfo
while IFS=$'=' read -r key value; do
[[ "${key}" = [#!]* ]] || [[ "${key}" = "" ]] || buildinfo["${key}"]="${value}"
done <<< "$(buildinfo -ff "${pkg}")"
packager="${buildinfo[packager]}"
builddir="${buildinfo[builddir]}"
_pkgver="${buildinfo[pkgver]}"
pkgrel=${_pkgver##*-}
pkgver=${_pkgver%-*}
pkgbase=${buildinfo[pkgbase]}
pkgbuild_sha256sum="${buildinfo[pkgbuild_sha256sum]}"
SOURCE_DATE_EPOCH="${buildinfo[builddate]}"
printf 'PACKAGER=%s' "${packager@Q}" > "$BUILDDIRECTORY/build/home/builduser/.makepkg.conf"
# Father I have sinned
exec_nspawn "build" \
bash -x <<-__END__
shopt -s globstar
mkdir -p $builddir
chown builduser:builduser $builddir
pacman -S asp --noconfirm
asp checkout $pkgbase
cd $pkgbase
history=\$(git grep --all-match -e pkgver=$pkgver -e pkgrel=$pkgrel \$(git rev-list --all -- repos/) -- repos/)
cut -d: -f1 <<< \$history | head -1 | xargs git checkout
file_path=\$(cut -d: -f2 <<< \$history | head -1 | xargs dirname)
pkgbuild_checksum=\$(sha256sum -b \$file_path/PKGBUILD)
pkgbuild_checksum=\${pkgbuild_checksum%% *}
if [ \$pkgbuild_checksum != $pkgbuild_sha256sum ]; then
# TODO: use warning or exit
echo "Warning PKGBUILD checksum does not match"
fi
mv ./\$file_path/* $builddir
pacman -Rs asp --noconfirm
__END__
msg2 "Preparing packages"
mkdir -p "${cachedir}"
mapfile -t packages < <(buildinfo -d "${cachedir}" "${pkg}")
msg2 "Finished preparing packages"
msg "Installing packages"
# shellcheck disable=SC2086
exec_nspawn build --bind="$(readlink -e ${cachedir}):/cache" pacman -U ${packages[*]} --noconfirm
build_package "build" "$builddir"
remove_snapshot "build"
msg "Comparing hashes..."
if diff -q -- "$pkg" ./build/"$(basename "$pkgfile")" > /dev/null ; then
msg "Package is reproducible!"
else
error "Package is not reproducible"
if ((run_diffoscope)); then
PYTHONIOENCODING=utf-8 $DIFFOSCOPE "$pkg" ./build/"$(basename "$pkg")" || true
fi
fi
}
# Desc: Fetches a bootstrap image and verifies the signature
function get_bootstrap_img() {
trap '{ rm "$IMGDIRECTORY/$bootstrap_img" ; exit 1; }' ERR
if [ ! -e "$IMGDIRECTORY/$bootstrap_img" ]; then
msg "Downloading bootstrap image..."
curl -o "$IMGDIRECTORY/$bootstrap_img" "$BOOTSTRAPMIRROR/$bootstrap_img"
curl -o "$IMGDIRECTORY/$bootstrap_img.sig" "$BOOTSTRAPMIRROR/$bootstrap_img.sig"
if ! gpg --verify "$IMGDIRECTORY/$bootstrap_img.sig" "$IMGDIRECTORY/$bootstrap_img"; then
error "Can't verify image"
exit 1
fi
fi
trap - ERR
}
# Desc: Prints the help section
function print_help() {
cat <<__END__
Usage:
repro [options]
General Options:
-h Print this help message
-d Run diffoscope if packages are not reproducible
-P <file> Specify pacman.conf to build with
-M <file> Specify makepkg.conf to build with
__END__
}
hash buildinfo 2>/dev/null || { error "Require buildinfo in path! Aborting..."; exit 1; }
# Default options
pacman_conf=$CONFIGDIR/pacman.conf
makepkg_conf=$CONFIGDIR/makepkg.conf
run_diffoscope=0
command_args=()
repro_conf=${repro_conf:-$configdir/repro.conf}
if [[ -r $repro_conf ]]; then
# shellcheck source=/dev/null
source "$repro_conf"
fi
xdg_repro_dir="${xdg_config_home:-$home/.config}/repro"
if [[ "$repro_conf" = "$configdir/repro.conf" ]]; then
if [[ -r "$xdg_repro_dir/repro.conf" ]]; then
# shellcheck source=/dev/null
source "$xdg_repro_dir/repro.conf"
elif [[ -r "$home/.repro.conf" ]]; then
# shellcheck source=/dev/null
source "$home/.repro.conf"
fi
fi
while getopts :hdoC:P:M: arg; do
case $arg in
h) print_help; exit 0;;
P) pacman_conf=$OPTARG;;
M) makepkg_conf=$OPTARG;;
d) run_diffoscope=1;;
*) ;;
esac
done
# Save command args (such as path to .pkg.tar.xz file)
shift $((OPTIND-1))
# shellcheck disable=SC2178
command_args="$*"
set -- "${command_args[@]}"
get_bootstrap_img
check_root
init_chroot
cmd_check "$@"