summaryrefslogtreecommitdiff
path: root/src/chroot-tools/chcleanup.in
blob: 79c9664215dc9f6cbac47f84041e3b313763fe06 (plain)
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
#!/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 <fauno@parabola.nu>
# Copyright (C) 2012-2013, 2015, 2017-2018 Luke Shumaker <lukeshu@parabola.nu>
# Copyright (C) 2020 bill-auger <bill-auger@programmer.net>
#
# 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 <http://www.gnu.org/licenses/>.
#
#
# 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 additions
WHITELIST_PKGS=() # DEBUG: normally empty - allowed outdated packages, possibly insane
PHANTOM_PKGS=()   # DEBUG: normally empty - fake dependencies, possibly insane
PHANTOM_PKGS=( ${PHANTOM_PKGS[*]/#/--assume-installed=} )

# 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

readonly CHROOTPKG WHITELIST_PKGS PHANTOM_PKGS DEPENDS


## 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 ${PHANTOM_PKGS} \
                        -- ${CHROOTPKG[*]} ${CHROOTEXTRAPKG[*]} ${DEPENDS[*]}          \
                        <&- >& "$ERROR_PKGS_FILE"
then
	error "Could not create a full list of packages, exiting."
	plain "Ensure that the chroot is up-to-date."
	plain "Otherwise, this is likely caused by an unsatisfied dependency."
	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 updated 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"
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 ${PHANTOMS} -S "${packages[@]}"
	fi
fi


${DRYRUN} && warning "exiting per \$DRYRUN" && exit 1 || :