summaryrefslogtreecommitdiff
path: root/src/chroot-tools/chcleanup.in
blob: e5e7668cc18bf781d67733a8c8fb17253a8f787a (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
#!/usr/bin/env bash
set -eE
# Copyright (C) 2011-2012 Nicolás Reynolds <fauno@parabola.nu>
# Copyright (C) 2012-2013, 2015, 2017-2018 Luke Shumaker <lukeshu@parabola.nu>
#
# 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.
#
# 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/>.

# Performs chroot cleanup smartly, it only removes the unneeded packages or
# leaves you with a clean system

# 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

# Begin chcleanup.lib ##########################################################
m4_include(chcleanup.lib)
# End 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 ###########################################################
CHROOTPKG=(base-devel)
# Note: the in-chroot pkgconfdir is non-configurable, this is
# intentionally hard-coded.
source /etc/libretools.d/chroot.conf
# 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="$(. /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 #########################################################################

msg "Cleaning chroot..."

# Sync the local repo with pacman (a limited form of `pacman -Sy`)
cp /repo/repo.db /var/lib/pacman/sync/repo.db

# Setup the temporary directory
TEMPDIR="$(mktemp --tmpdir -d "${0##*/}.XXXXXXXXXX")"
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=(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.  We
# don't include DEPENDS in this optimization, because this assumption
# doesn't hold for them.
while read -r pkg; do
	if [[ -d /var/lib/pacman/local/$pkg ]]; then
		cp -a -T -- "/var/lib/pacman/local/$pkg" "$TEMPDIR/db/local/$pkg"
	fi
done < <("${pacman[@]}" -Sp --print-format='%n-%v' -- "${CHROOTPKG[@]}" "${CHROOTEXTRAPKG[@]}")

# Get the full list of packages needed by dependencies, including the base system
msg2 "Creating a full list of packages..."
for var in CHROOTPKG CHROOTEXTRAPKG DEPENDS; do
	declare -n pkgsref="$var"
	if [[ $var = DEPENDS ]]; then
		mapfile -t pkgs < <("${pacman[@]}" -T -- "${pkgsref[@]}")
	else
		pkgs=("${pkgsref[@]}")
	fi
	if (( ${#pkgs[@]} == 0 )); then
		continue
	fi
	"${pacman[@]}" -S --dbonly --noscriptlet --needed --noconfirm -- "${pkgs[@]}" <&- >& "$TEMPDIR/pacman.txt" || ret=$?
	if (( ret != 0 )); 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/^/ > /' <"$TEMPDIR/pacman.txt" >&2
		exit $ret
	fi
done
"${pacman[@]}" -Qq >"$TEMPDIR/pkglist.txt"

# Diff installed packages against a clean chroot then remove leftovers
packages=($(comm -23 <(pacman -Qq | sort -u) \
                     <(sort -u "$TEMPDIR/pkglist.txt")))
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 "$TEMPDIR/pkglist.txt")))
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