#!/usr/bin/env bash set -eE # Performs chroot cleanup smartly. # Removes all and only non-essential packages, leaving a clean base-devel system. # This script is only intended to be executed implicitly by `librechroot`. # # Copyright (C) 2011-2012 Nicolás Reynolds # Copyright (C) 2012-2013, 2015, 2017-2018 Luke Shumaker # Copyright (C) 2020 bill-auger # # License: GNU GPLv3+ # # 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, either version 3 of the License, or # (at your option) any later version. # # 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 Affero General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # # License Note: # If you don't see m4_include(...) below, but see function definitions # for msg() et al., then this is a generated file, and contains some # code from librelib. See the source distribution for full copyright # information. ## Library Routines ## # Statically include various library routines to avoid having # dependencies on outside files. export TEXTDOMAIN='libretools' export TEXTDOMAINDIR='/usr/share/locale' if type gettext &>/dev/null; then _() { gettext "$@"; } else _() { echo "$@"; } fi ## chcleanup.lib ## m4_include(chcleanup.lib) ## User Interface ## DRYRUN=${DRYRUN:-false} if [[ ! -f /.arch-chroot ]] && ! ${DRYRUN}; then error "(chcleanup): Must be run inside of a chroot" exit 1 fi ## Load Configuration ## # NOTE: The in-chroot pkgconfdir is non-configurable. # This is intentionally hard-coded. source /etc/libretools.d/chroot.conf # sets $CHROOTEXTRAPKG CHROOTPKG=( $( pacman -Sgq base-devel) ) CHROOTPKG+=() # DEBUG: normally empty - mandatory core package-set WHITELIST_PKGS=() # DEBUG: normally empty - optional packages, possibly insane # FIXME: ARM chroots require 'fakeroot-tcp' (BR #2775) #CHROOTPKG+=( fakeroot-tcp ) # armv7h qemu fakeroot_pkg=fakeroot$( [[ "$(uname -m)" == 'armv7l' ]] && echo '-tcp' ) CHROOTPKG=( ${CHROOTPKG[*]/fakeroot/${fakeroot_pkg}/} ) # FIXME: host<->chroot glibc mismatch WHITELIST_PKGS+=( glibc-linux4 ) # BR #3038 # $ wget https://repo.archlinuxcn.org/x86_64/glibc-linux4-2.33-4-x86_64.pkg.tar.zst.sig # $ wget https://repo.archlinuxcn.org/x86_64/glibc-linux4-2.33-4-x86_64.pkg.tar.zst # $ pacman-key --verify glibc-linux4-2.33-4-x86_64.pkg.tar.zst.sig # $ sudo librechroot -n autobuilder install-file glibc-linux4-2.33-4-x86_64.pkg.tar.zst # $ sudo cp glibc-linux4-2.33-4-x86_64.pkg.tar.zst* /var/lib/archbuild/autobuilder/bill-auger/ # $ sudo librechroot -n autobuilder enter # If we're running makepkg if [[ -f ./PKGBUILD ]]; then if [[ ! -f ./.SRCINFO || ./PKGBUILD -nt ./.SRCINFO ]]; then sudo -u "#$(stat -c %u -- ./PKGBUILD)" sh -c 'makepkg --printsrcinfo > .SRCINFO' fi CARCH="$(source /etc/makepkg.conf; printf '%s' "$CARCH")" mapfile -t DEPENDS < <(sed -nE -e "s/^\\s+(|make|check)depends(|_${CARCH}) = //p" -e '/^\s*pkgname/q' < .SRCINFO) else DEPENDS=() fi ## Main Entry ## msg "Cleaning chroot..." # Sync the local repo with pacman (a crude form of `pacman -Sy`) cp /repo/repo.db /var/lib/pacman/sync/repo.db # Setup the temporary directory TEMPDIR="$(mktemp --tmpdir -d "${0##*/}.XXXXXXXXXX")" ERROR_PKGS_FILE="$TEMPDIR/err_pkgs.txt" WHITELIST_PKGS_FILE="$TEMPDIR/pkglist.txt" trap "rm -rf -- ${TEMPDIR@Q}" EXIT # Set up a scratch pacman DB mkdir -- "$TEMPDIR/db" "$TEMPDIR/db/local" "$TEMPDIR/hooks" cp -a -t "${TEMPDIR}/db" -- /var/lib/pacman/sync { echo /usr/share/libalpm/hooks; pacman-conf HookDir; } | while read -r dir; do for hook in "$dir"/*.hook; do ln -sfT -- /dev/null "$TEMPDIR/hooks/${hook##*/}" done done pacman_cmd=(pacman --dbpath="$TEMPDIR/db" --hookdir="$TEMPDIR/hooks") # Do our best to preload the scratch DB with CHROOTPKG and # CHROOTEXTRAPKG packages. This is purely an optimization step. # The safety of this optimization assumes that none of CHROOTPKG, # CHROOTEXTRAPKG, *or their dependancies* are virtual packages. # DEPENDS are not included in this optimization, # because this assumption doesn't hold for them. mapfile -t fresh_pkgs < <("${pacman_cmd[@]}" -Sp --print-format='%n %v' \ -- "${CHROOTPKG[@]}" "${CHROOTEXTRAPKG[@]}") stale_pkgs=() for pkg in "${fresh_pkgs[@]}"; do pkg_name_ver=${pkg/ /-} pkg_name=${pkg% *} pkg_dir=/var/lib/pacman/local/$pkg_name_ver if [[ -d "$pkg_dir" ]]; then cp -a -T -- "$pkg_dir" "$TEMPDIR/db/local/$pkg_name_ver" else stale_pkgs+=($pkg_name) fi done # Collect the minimal set of packages, which are necessary, # in order to build the PKGBUILD recipe # (base+base-devel, user-packages, PKGBUILD dependencies, # and the entire resolved dependency chain). # This is done by installing those into a temporary pacman DB; # then querying the DB for it's complete package list. msg2 "Collecting the minimal set of packages needed ..." if ! "${pacman_cmd[@]}" -S --dbonly --noscriptlet --needed --noconfirm \ -- ${CHROOTPKG[*]} ${CHROOTEXTRAPKG[*]} ${DEPENDS[*]} \ <&- >& "$ERROR_PKGS_FILE" then error "Could not create a full list of packages, exiting." plain "This is likely caused by a dependency that could not be found." sed 's/^/ > /' < "$ERROR_PKGS_FILE" >&2 exit 1 fi # Generate the list of packages which should be installed # Any installed package with an out-dated pkgver; will not be reported as installed # in the temp DB; because the 'pkgver' will not match in the initial pre-load stage. # This can be the case, if the chroot package database was upadted manually, # while enabling the [testing] repo, for example. # These package would not be marked as needed in the whitelist collection stage; # because they are installed, and the '--needed' option ignores upgrades. # Thus, these would not be present on the whitelist, signalling their removal; # which for essential dependencies, would result in a fatal conflict. # This could be avoided by omitting the '--needed' option; # but that could very likely result in a "partially uograded" system. # Another option would be to begin this script with forceful complete system upgrade, # This situation is as likely to be unintentional as it may be intentional; # so it is best to require explicit initiation of a build in this insane configuration # (via the libremakepkg '-I' option), and to exit otherwise, warning the user to upgrade. # If the '-I' option was detected (!SANE), the stale packages are added to the whitelist. "${pacman_cmd[@]}" -Qq > "$WHITELIST_PKGS_FILE" # echo "${WHITELIST_PKGS[@]}" >> "$WHITELIST_PKGS_FILE" cat >> "$WHITELIST_PKGS_FILE" <<<${WHITELIST_PKGS[@]} if (( ${#stale_pkgs[*]} > 0 )); then insane_msg_1="Some (%d) essential packages are out-of-sync with the database." insane_msg_2="Consider upgrading the chroot system before building packages." if ! $SANE; then warning "$insane_msg_1" "${#stale_pkgs[*]}" ; plain "$insane_msg_2" ; plain "(ignoring, per the '-I' option)" ; printf "%s\n" "${stale_pkgs[@]}" >> "$WHITELIST_PKGS_FILE" else error "$insane_msg_1" "${#stale_pkgs[*]}" ; plain "$insane_msg_2" ; plain "If absolutely necessary, this guard may be bypassed via the '-I' option." exit 1 fi fi # Diff installed packages against a clean chroot then remove leftovers packages=($(comm -23 <(pacman -Qq | sort -u) \ <(sort -u "$WHITELIST_PKGS_FILE"))) if [[ ${#packages[@]} = 0 ]]; then msg2 "No packages to remove" else msg2 "Removing %d packages" ${#packages[@]} if ${DRYRUN}; then echo "${packages[*]}" else # Only remove leftovers, -Rcs removes too much pacman --noconfirm -R --nosave "${packages[@]}" fi fi packages=($(comm -13 <(pacman -Qq | sort -u) \ <(sort -u "$WHITELIST_PKGS_FILE"))) if [[ ${#packages[@]} = 0 ]]; then msg2 "No packages to add" else msg2 "Adding %d packages" ${#packages[@]} if ${DRYRUN}; then echo "${packages[*]}" else pacman --noconfirm -S "${packages[@]}" fi fi ${DRYRUN} && warning "exiting per \$DRYRUN" && exit 1 || :