devtools/makechrootpkg.in

428 lines
12 KiB
Plaintext
Raw Normal View History

#!/bin/bash
# License: GNU GPLv2
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
m4_include(lib/common.sh)
m4_include(lib/archroot.sh)
source /usr/share/makepkg/util/config.sh
shopt -s nullglob
2014-12-29 06:33:41 +01:00
default_makepkg_args=(--syncdeps --noconfirm --holdver --skipinteg)
makepkg_args=("${default_makepkg_args[@]}")
verifysource_args=()
chrootdir=
passeddir=
makepkg_user=
declare -a install_pkgs
declare -i ret=0
keepbuilddir=0
update_first=0
clean_first=0
run_namcap=0
run_checkpkg=0
temp_chroot=0
bindmounts_ro=()
bindmounts_rw=()
copy=$USER
[[ -n ${SUDO_USER:-} ]] && copy=$SUDO_USER
[[ -z "$copy" || $copy = root ]] && copy=copy
src_owner=${SUDO_USER:-$USER}
2010-02-13 19:47:16 +01:00
usage() {
echo "Usage: ${0##*/} [options] -r <chrootdir> [--] [makepkg args]"
2010-02-13 19:47:16 +01:00
echo ' Run this script in a PKGBUILD dir to build a package inside a'
echo ' clean chroot. Arguments passed to this script after the'
echo ' end-of-options marker (--) will be passed to makepkg.'
2010-02-13 19:47:16 +01:00
echo ''
echo ' The chroot dir consists of the following directories:'
echo ' <chrootdir>/{root, copy} but only "root" is required'
echo ' by default. The working copy will be created as needed'
echo ''
echo 'The chroot "root" directory must be created via the following'
echo 'command:'
echo ' mkarchroot <chrootdir>/root base-devel'
2010-02-13 19:47:16 +01:00
echo ''
echo 'This script reads {SRC,SRCPKG,PKG,LOG}DEST, MAKEFLAGS and PACKAGER'
echo 'from makepkg.conf(5), if those variables are not part of the'
echo 'environment.'
echo ''
echo "Default makepkg args: ${default_makepkg_args[*]}"
2010-02-13 19:47:16 +01:00
echo ''
echo 'Flags:'
echo '-h This help'
echo '-c Clean the chroot before building'
2014-12-29 06:25:58 +01:00
echo '-C <dir> Set pacman cache to pass to arch-nspawn'
echo '-d <dir> Bind directory into build chroot as read-write'
echo '-D <dir> Bind directory into build chroot as read-only'
2010-02-13 19:47:16 +01:00
echo '-u Update the working copy of the chroot before building'
echo ' This is useful for rebuilds without dirtying the pristine'
echo ' chroot'
echo '-r <dir> The chroot dir to use'
echo '-I <pkg> Install a package into the working copy of the chroot'
echo '-l <copy> The directory to use as the working copy of the chroot'
echo ' Useful for maintaining multiple copies'
echo " Default: $copy"
echo '-n Run namcap on the package'
echo '-C Run checkpkg on the package'
echo '-T Build in a temporary directory'
echo '-U Run makepkg as a specified user'
2010-02-13 19:47:16 +01:00
exit 1
}
# {{{ functions
# Usage: sync_chroot $chrootdir $copydir [$copy]
sync_chroot() {
local chrootdir=$1
local copydir=$2
local copy=${3:-$2}
if [[ "$chrootdir/root" -ef "$copydir" ]]; then
error 'Cannot sync copy with itself: %s' "$copydir"
return 1
fi
# Get a read lock on the root chroot to make
# sure we don't clone a half-updated chroot
slock 8 "$chrootdir/root.lock" \
"Locking clean chroot [%s]" "$chrootdir/root"
stat_busy "Synchronizing chroot copy [%s] -> [%s]" "$chrootdir/root" "$copy"
if is_btrfs "$chrootdir" && ! mountpoint -q "$copydir"; then
subvolume_delete_recursive "$copydir" ||
die "Unable to delete subvolume %s" "$copydir"
btrfs subvolume snapshot "$chrootdir/root" "$copydir" >/dev/null ||
die "Unable to create subvolume %s" "$copydir"
else
mkdir -p "$copydir"
rsync -a --delete -q -W -x "$chrootdir/root/" "$copydir"
fi
stat_done
# Drop the read lock again
lock_close 8
# Update mtime
touch "$copydir"
}
# Usage: delete_chroot $copydir [$copy]
delete_chroot() {
local copydir=$1
local copy=${1:-$2}
stat_busy "Removing chroot copy [%s]" "$copy"
if is_subvolume "$copydir" && ! mountpoint -q "$copydir"; then
subvolume_delete_recursive "$copydir" ||
die "Unable to delete subvolume %s" "$copydir"
else
# avoid change of filesystem in case of an umount failure
rm --recursive --force --one-file-system "$copydir" ||
die "Unable to delete %s" "$copydir"
fi
# remove lock file
rm -f "$copydir.lock"
stat_done
}
install_packages() {
local -a pkgnames
local ret
pkgnames=("${install_pkgs[@]##*/}")
cp -- "${install_pkgs[@]}" "$copydir/root/"
arch-nspawn "$copydir" "${bindmounts_ro[@]}" "${bindmounts_rw[@]}" \
bash -c 'yes y | pacman -U -- "$@"' -bash "${pkgnames[@]/#//root/}"
ret=$?
rm -- "${pkgnames[@]/#/$copydir/root/}"
return $ret
}
prepare_chroot() {
(( keepbuilddir )) || rm -rf "$copydir/build"
local builduser_uid builduser_gid
builduser_uid="$(id -u "$makepkg_user")"
builduser_gid="$(id -g "$makepkg_user")"
builduser_gids="$(id -G "$makepkg_user")"
local install="install -o $builduser_uid -g $builduser_gid"
local x
# We can't use useradd without chrooting, otherwise it invokes PAM modules
# which we might not be able to load (i.e. when building i686 packages on
# an x86_64 host).
sed -e '/^builduser:/d' -i "$copydir"/etc/{passwd,shadow,group}
printf >>"$copydir/etc/group" 'builduser:x:%d:\n' "$builduser_gid"
for gid in $builduser_gids; do
if [[ $gid != $builduser_gid ]]; then
group_name=$(getent group "$gid" | cut -d: -f1)
printf >>"$copydir/etc/group" "$group_name:x:%d:builduser\n" "$gid"
fi
done
printf >>"$copydir/etc/passwd" 'builduser:x:%d:%d:builduser:/build:/bin/bash\n' "$builduser_uid" "$builduser_gid"
printf >>"$copydir/etc/shadow" 'builduser:!!:%d::::::\n' "$(( $(date -u +%s) / 86400 ))"
$install -d "$copydir"/{build,startdir,{pkg,srcpkg,src,log}dest}
for x in BUILDDIR=/build PKGDEST=/pkgdest SRCPKGDEST=/srcpkgdest SRCDEST=/srcdest LOGDEST=/logdest
do
grep -q "^$x" "$copydir/etc/makepkg.conf" && continue
echo "$x" >>"$copydir/etc/makepkg.conf"
done
cat > "$copydir/etc/sudoers.d/builduser-pacman" <<EOF
builduser ALL = NOPASSWD: /usr/bin/pacman
EOF
chmod 440 "$copydir/etc/sudoers.d/builduser-pacman"
# This is a little gross, but this way the script is recreated every time in the
# working copy
{
printf '#!/bin/bash\n'
declare -f _chrootbuild
declare -p SOURCE_DATE_EPOCH 2>/dev/null || true
printf '_chrootbuild "$@" || exit\n'
if (( run_namcap )); then
declare -f _chrootnamcap
printf '_chrootnamcap || exit\n'
fi
} >"$copydir/chrootbuild"
chmod +x "$copydir/chrootbuild"
}
# These functions aren't run in makechrootpkg,
# so no global variables
_chrootbuild() {
# No coredumps
ulimit -c 0
# shellcheck source=/dev/null
. /etc/profile
# Beware, there are some stupid arbitrary rules on how you can
# use "$" in arguments to commands with "sudo -i". ${foo} or
# ${1} is OK, but $foo or $1 isn't.
# https://bugzilla.sudo.ws/show_bug.cgi?id=765
2014-12-29 06:32:15 +01:00
mkdir /build/.distcc
chown builduser /build/.distcc
sudo --preserve-env=SOURCE_DATE_EPOCH -iu builduser DISTCC_IO_TIMEOUT=1200 DISTCC_DIR='/build/.distcc' bash -c 'cd /startdir; makepkg "$@"' -bash "$@"
ret=$?
case $ret in
0|14)
return 0;;
*)
return $ret;;
esac
}
_chrootnamcap() {
pacman -S --needed --noconfirm namcap
for pkgfile in /startdir/PKGBUILD /pkgdest/*; do
echo "Checking ${pkgfile##*/}"
sudo -u builduser namcap "$pkgfile" 2>&1 | tee "/logdest/${pkgfile##*/}-namcap.log"
done
}
download_sources() {
setup_workdir
chown "$makepkg_user:" "$WORKDIR"
# Ensure sources are downloaded
sudo -u "$makepkg_user" --preserve-env=GNUPGHOME \
env SRCDEST="$SRCDEST" BUILDDIR="$WORKDIR" \
2014-12-29 06:33:00 +01:00
makepkg -A --config="$copydir/etc/makepkg.conf" --verifysource -o "${verifysource_args[@]}" ||
die "Could not download sources."
}
move_logfiles() {
local l
for l in "$copydir"/logdest/*; do
[[ $l == */logpipe.* ]] && continue
chown "$src_owner" "$l"
mv "$l" "$LOGDEST"
done
}
move_products() {
local pkgfile
for pkgfile in "$copydir"/pkgdest/*; do
chown "$src_owner" "$pkgfile"
mv "$pkgfile" "$PKGDEST"
# Fix broken symlink because of temporary chroot PKGDEST /pkgdest
if [[ "$PWD" != "$PKGDEST" && -L "$PWD/${pkgfile##*/}" ]]; then
ln -sf "$PKGDEST/${pkgfile##*/}"
fi
2010-02-13 19:47:16 +01:00
done
move_logfiles
for s in "$copydir"/srcpkgdest/*; do
chown "$src_owner" "$s"
mv "$s" "$SRCPKGDEST"
# Fix broken symlink because of temporary chroot SRCPKGDEST /srcpkgdest
if [[ "$PWD" != "$SRCPKGDEST" && -L "$PWD/${s##*/}" ]]; then
ln -sf "$SRCPKGDEST/${s##*/}"
fi
done
}
# }}}
2014-12-29 06:25:58 +01:00
while getopts 'hcuC:r:I:l:nCTD:d:U:' arg; do
case "$arg" in
c) clean_first=1 ;;
D) bindmounts_ro+=("--bind-ro=$OPTARG") ;;
d) bindmounts_rw+=("--bind=$OPTARG") ;;
u) update_first=1 ;;
2014-12-29 06:25:58 +01:00
C) cache_dir="-c $OPTARG" ;;
r) passeddir="$OPTARG" ;;
I) install_pkgs+=("$OPTARG") ;;
l) copy="$OPTARG" ;;
n) run_namcap=1; makepkg_args+=(--install) ;;
C) run_checkpkg=1 ;;
T) temp_chroot=1; copy+="-$$" ;;
U) makepkg_user="$OPTARG" ;;
h|*) usage ;;
esac
done
[[ ! -f PKGBUILD && -z "${install_pkgs[*]}" ]] && die 'This must be run in a directory containing a PKGBUILD.'
[[ -n $makepkg_user && -z $(id -u "$makepkg_user") ]] && die 'Invalid makepkg user.'
makepkg_user=${makepkg_user:-${SUDO_USER:-$USER}}
check_root SOURCE_DATE_EPOCH,GNUPGHOME,SRCDEST,SRCPKGDEST,PKGDEST,LOGDEST,MAKEFLAGS,PACKAGER
# Canonicalize chrootdir, getting rid of trailing /
chrootdir=$(readlink -e "$passeddir")
[[ ! -d $chrootdir ]] && die "No chroot dir defined, or invalid path '%s'" "$passeddir"
[[ ! -d $chrootdir/root ]] && die "Missing chroot dir root directory. Try using: mkarchroot %s/root base-devel" "$chrootdir"
if [[ ${copy:0:1} = / ]]; then
copydir=$copy
else
copydir="$chrootdir/$copy"
fi
# Pass all arguments after -- right to makepkg
makepkg_args+=("${@:$OPTIND}")
# See if -R or -e was passed to makepkg
for arg in "${@:$OPTIND}"; do
case ${arg%%=*} in
--skip*|--holdver) verifysource_args+=("$arg") ;;
--repackage|--noextract) keepbuilddir=1 ;;
--*) ;;
-*R*|-*e*) keepbuilddir=1 ;;
esac
done
umask 0022
ORIG_HOME=$HOME
IFS=: read -r _ _ _ _ _ HOME _ < <(getent passwd "${SUDO_USER:-$USER}")
load_makepkg_config
HOME=$ORIG_HOME
# Use PKGBUILD directory if these don't exist
[[ -d $PKGDEST ]] || PKGDEST=$PWD
[[ -d $SRCDEST ]] || SRCDEST=$PWD
[[ -d $SRCPKGDEST ]] || SRCPKGDEST=$PWD
[[ -d $LOGDEST ]] || LOGDEST=$PWD
# Lock the chroot we want to use. We'll keep this lock until we exit.
lock 9 "$copydir.lock" "Locking chroot copy [%s]" "$copy"
if [[ ! -d $copydir ]] || (( clean_first )); then
sync_chroot "$chrootdir" "$copydir" "$copy"
fi
(( update_first )) && arch-nspawn "$copydir" \
"${bindmounts_ro[@]}" "${bindmounts_rw[@]}" \
pacman -Syuu --noconfirm
if [[ -n ${install_pkgs[*]:-} ]]; then
install_packages
ret=$?
# If there is no PKGBUILD we are done
[[ -f PKGBUILD ]] || exit $ret
fi
if [[ "$(id -u "$makepkg_user")" == 0 ]]; then
error "Running makepkg as root is not allowed."
exit 1
fi
download_sources
prepare_chroot
2014-12-29 06:25:58 +01:00
if arch-nspawn $cache_dir "$copydir" \
--setenv=TERM=xterm \
--bind="${PWD//:/\\:}:/startdir" \
--bind="${SRCDEST//:/\\:}:/srcdest" \
"${bindmounts_ro[@]}" "${bindmounts_rw[@]}" \
/chrootbuild "${makepkg_args[@]}"
then
mapfile -t pkgnames < <(sudo -u "$makepkg_user" bash -c 'source PKGBUILD; printf "%s\n" "${pkgname[@]}"')
move_products
else
(( ret += 1 ))
move_logfiles
fi
(( temp_chroot )) && delete_chroot "$copydir" "$copy"
if (( ret != 0 )); then
if (( temp_chroot )); then
die "Build failed"
else
die "Build failed, check %s/build" "$copydir"
fi
else
if (( run_checkpkg )); then
msg "Running checkpkg"
makechrootpkg: sync databases for checkpkg off-site Use pacman's --dbpath feature to sync fresh databases inside an isolated location and split up the database sync and package location calls to remove the need of weird grep calls. It isn't nice of makechrootpkg to modify the host database state just by building packages. No foreign program shall automatically modify the host database other than by the explicit will of a system maintainer, which is the major reason this changes get incorporated. However, there is certain indoctrinated believe that using -Sy is the prime evil. In fact it has been declared as a social rule to a technical problem of not getting into potential partial upgrade states. This is not a proper loophole less solution as there are multiple ways and use cases that lead to such a state, like aborting a -Syu on the prompt for whatever reason, what really matters is that it is not a technically bullet proof solution to solve the problem. Databases shall have the freedom to be as up to date as databases or their owner wishes, allowing querying on latest database state without fear. The only loophole-less contract that _really_ is from importance is always using -Su instead of plain -S to install packages. Installing packages is what actually brings one into a potential partial upgrade state and by using -Su an outstanding upgrade is forced when installing a new package. This properly solves all edge cases in a technical manner instead of declaring people who abort the prompt of -Syu to be the problem. In fact, using this simple contract allows whatever system maintenance workflow a host owner wants to follow, which may still be to always use -Syu and deal with system upgrades explicitly instead of the time when installing new packages, but the -Su contract is the real safe guard to guarantee no edge case can ever slip in. This magically also opens up the freedom to people who wish to use -Sy to simply query on up to date data as the currently indoctrinated "never do -Sy" stone plates not only are not rock solid in technical terms but also make certain use cases simply impossible and hence cripple the functionality without at the very least being fully loophole free. Signed-off-by: Levente Polyak <anthraxx@archlinux.org>
2019-12-03 01:14:35 +01:00
mapfile -t remotepkgs < <(pacman --config "$copydir"/etc/pacman.conf \
--dbpath "$copydir"/var/lib/pacman \
-Sddp "${pkgnames[@]}")
makechrootpkg: sync databases for checkpkg off-site Use pacman's --dbpath feature to sync fresh databases inside an isolated location and split up the database sync and package location calls to remove the need of weird grep calls. It isn't nice of makechrootpkg to modify the host database state just by building packages. No foreign program shall automatically modify the host database other than by the explicit will of a system maintainer, which is the major reason this changes get incorporated. However, there is certain indoctrinated believe that using -Sy is the prime evil. In fact it has been declared as a social rule to a technical problem of not getting into potential partial upgrade states. This is not a proper loophole less solution as there are multiple ways and use cases that lead to such a state, like aborting a -Syu on the prompt for whatever reason, what really matters is that it is not a technically bullet proof solution to solve the problem. Databases shall have the freedom to be as up to date as databases or their owner wishes, allowing querying on latest database state without fear. The only loophole-less contract that _really_ is from importance is always using -Su instead of plain -S to install packages. Installing packages is what actually brings one into a potential partial upgrade state and by using -Su an outstanding upgrade is forced when installing a new package. This properly solves all edge cases in a technical manner instead of declaring people who abort the prompt of -Syu to be the problem. In fact, using this simple contract allows whatever system maintenance workflow a host owner wants to follow, which may still be to always use -Syu and deal with system upgrades explicitly instead of the time when installing new packages, but the -Su contract is the real safe guard to guarantee no edge case can ever slip in. This magically also opens up the freedom to people who wish to use -Sy to simply query on up to date data as the currently indoctrinated "never do -Sy" stone plates not only are not rock solid in technical terms but also make certain use cases simply impossible and hence cripple the functionality without at the very least being fully loophole free. Signed-off-by: Levente Polyak <anthraxx@archlinux.org>
2019-12-03 01:14:35 +01:00
if ! wait $!; then
warning "Skipped checkpkg due to missing repo packages"
exit 0
fi
makechrootpkg: sync databases for checkpkg off-site Use pacman's --dbpath feature to sync fresh databases inside an isolated location and split up the database sync and package location calls to remove the need of weird grep calls. It isn't nice of makechrootpkg to modify the host database state just by building packages. No foreign program shall automatically modify the host database other than by the explicit will of a system maintainer, which is the major reason this changes get incorporated. However, there is certain indoctrinated believe that using -Sy is the prime evil. In fact it has been declared as a social rule to a technical problem of not getting into potential partial upgrade states. This is not a proper loophole less solution as there are multiple ways and use cases that lead to such a state, like aborting a -Syu on the prompt for whatever reason, what really matters is that it is not a technically bullet proof solution to solve the problem. Databases shall have the freedom to be as up to date as databases or their owner wishes, allowing querying on latest database state without fear. The only loophole-less contract that _really_ is from importance is always using -Su instead of plain -S to install packages. Installing packages is what actually brings one into a potential partial upgrade state and by using -Su an outstanding upgrade is forced when installing a new package. This properly solves all edge cases in a technical manner instead of declaring people who abort the prompt of -Syu to be the problem. In fact, using this simple contract allows whatever system maintenance workflow a host owner wants to follow, which may still be to always use -Syu and deal with system upgrades explicitly instead of the time when installing new packages, but the -Su contract is the real safe guard to guarantee no edge case can ever slip in. This magically also opens up the freedom to people who wish to use -Sy to simply query on up to date data as the currently indoctrinated "never do -Sy" stone plates not only are not rock solid in technical terms but also make certain use cases simply impossible and hence cripple the functionality without at the very least being fully loophole free. Signed-off-by: Levente Polyak <anthraxx@archlinux.org>
2019-12-03 01:14:35 +01:00
# download package files if any non-local location exists
for remotepkg in "${remotepkgs[@]}"; do
if [[ $remotepkg != file://* ]]; then
msg2 "Downloading current versions"
arch-nspawn "$copydir" pacman --noconfirm -Swdd "${pkgnames[@]}"
mapfile -t remotepkgs < <(pacman --config "$copydir"/etc/pacman.conf \
--dbpath "$copydir"/var/lib/pacman \
-Sddp "${pkgnames[@]}")
break
fi
done
makechrootpkg: sync databases for checkpkg off-site Use pacman's --dbpath feature to sync fresh databases inside an isolated location and split up the database sync and package location calls to remove the need of weird grep calls. It isn't nice of makechrootpkg to modify the host database state just by building packages. No foreign program shall automatically modify the host database other than by the explicit will of a system maintainer, which is the major reason this changes get incorporated. However, there is certain indoctrinated believe that using -Sy is the prime evil. In fact it has been declared as a social rule to a technical problem of not getting into potential partial upgrade states. This is not a proper loophole less solution as there are multiple ways and use cases that lead to such a state, like aborting a -Syu on the prompt for whatever reason, what really matters is that it is not a technically bullet proof solution to solve the problem. Databases shall have the freedom to be as up to date as databases or their owner wishes, allowing querying on latest database state without fear. The only loophole-less contract that _really_ is from importance is always using -Su instead of plain -S to install packages. Installing packages is what actually brings one into a potential partial upgrade state and by using -Su an outstanding upgrade is forced when installing a new package. This properly solves all edge cases in a technical manner instead of declaring people who abort the prompt of -Syu to be the problem. In fact, using this simple contract allows whatever system maintenance workflow a host owner wants to follow, which may still be to always use -Syu and deal with system upgrades explicitly instead of the time when installing new packages, but the -Su contract is the real safe guard to guarantee no edge case can ever slip in. This magically also opens up the freedom to people who wish to use -Sy to simply query on up to date data as the currently indoctrinated "never do -Sy" stone plates not only are not rock solid in technical terms but also make certain use cases simply impossible and hence cripple the functionality without at the very least being fully loophole free. Signed-off-by: Levente Polyak <anthraxx@archlinux.org>
2019-12-03 01:14:35 +01:00
msg2 "Checking packages"
sudo -u "$makepkg_user" checkpkg --rmdir --warn "${remotepkgs[@]/#file:\/\//}"
fi
true
fi