Last active
November 27, 2018 10:45
-
-
Save miminar/238344d4daaf55123dd51fcd97ebae72 to your computer and use it in GitHub Desktop.
Downgrade all packages to the latest available version with yum
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
set -euo pipefail | |
IFS=$'\n\t' | |
export LANC=C LC_ALL=C | |
readonly RE_EVRA="^([[:digit:]]+):(.+)-([^-]+)\.([^.-]+)$" | |
readonly RE_UNSUPPORTED='\(systemd\|yum\|selinux-policy\|dbus\|kernel\)' | |
readonly USAGE="$(basename "${BASH_SOURCE[0]}") [-hynk] [-f FILE] | |
Be warned! This script is dangerous and shouldn't be used on a production system. | |
Make sure to have a system backup at hand. | |
Tested on RHEL 7 only!! | |
Downgrade all installed packages to the latest available version. | |
The purpose of this script is to enable for normal package installations after | |
a switch to different RPM repositories with older packages available. | |
To actually perform the downgrade, -y option must be given. | |
Options: | |
-h Print this message and exit. | |
-n Dry run. Only print packages to downgrade. | |
-y Assume yes. Do the downgrade without any confirmation. | |
-k Keep the temporary directory with the list of packages | |
to downgrade that can be used next time. | |
-s Safe. Skip critical packages. Critical packages are those | |
that yum refuses to downgrade by default since their | |
downgrade is not supported (yum, kernel, dbus, etc.). | |
-f FILE Path to the file with the list of packages to downgrade | |
produced by this script. Can be used for subsequent | |
runs once the list of packages is generated and kept | |
with -k. | |
" | |
dry_run=0 | |
assume_yes=0 | |
keep_files=0 | |
to_downgrade="" | |
safe=0 | |
while getopts 'hnykf:s' opt; do | |
case "${opt}" in | |
h) echo "${USAGE}"; exit 0; ;; | |
y) assume_yes=1; ;; | |
n) dry_run=1; ;; | |
k) keep_files=1; ;; | |
f) to_downgrade="${OPTARG}"; ;; | |
s) safe=1; ;; | |
*) | |
echo "Unknown option '$opt'! See help!" >&2 | |
exit 1 | |
;; | |
esac | |
done | |
shift $(($OPTIND - 1)) | |
tmp="$(mktemp -d)" | |
function cleanup() { | |
if [[ "${keep_files}" == 0 ]]; then | |
rm -rf "${tmp}" ||: | |
elif [[ -n "${to_downgrade:-}" ]]; then | |
rm -vf "${tmp}/*.rpm" ||: | |
printf '\nThe list of packages to downgrade is available at "%s".\n' \ | |
"${to_downgrade}" | |
fi | |
} | |
trap cleanup EXIT | |
if ! rpm -q rpmdevtools >/dev/null; then | |
if ! sudo yum install -y rpmdevtools; then | |
# try to install without dependency check | |
pushd "${tmp}" | |
yumdownloader rpmdevtools | |
find -maxdepth 1 -name 'rpmdevtools*.rpm' | \ | |
sort -V | tail -n 1 | xargs sudo rpm --nodeps -Uvh | |
popd | |
fi | |
fi | |
downgrade_args=( --tolerant --skip-broken ) | |
out="/dev/null" | |
[[ "$assume_yes" == 1 ]] && downgrade_args+=( '-y' ) || pkg_handler+=( '--assumeno' ) | |
[[ "$dry_run" == 1 ]] && out="/dev/fd/1" | |
function mkpkglist() { | |
rpm -qa --qf='%{NAME}\n' | xargs sudo repoquery -q --qf='%{NAME}#%{EVRA}' | while IFS= read -r line; do | |
pkgname="${line%%#*}" | |
aevra="${line##*#}" | |
if [[ "${aevra}" =~ $RE_EVRA ]]; then | |
aepoch="${BASH_REMATCH[1]}" | |
aversion="${BASH_REMATCH[2]}" | |
arelease="${BASH_REMATCH[3]}" | |
aarch="${BASH_REMATCH[4]}" | |
else | |
echo "failed to parse evra '${aevra}' of available package! skipping..." >&2 | |
continue | |
fi | |
ievra="$(rpm -q "${pkgname}" --qf='%{EPOCH}:%{VERSION}-%{RELEASE}.%{ARCH}\n' | \ | |
sed 's/^(none):/0:/' | rpmdev-sort | tail -n 1)" | |
if echo "${ievra}" | grep -q 'not installed'; then | |
echo "package '$pkgname' is not installed -> skipping line '$line'" >&2 | |
continue | |
fi | |
if [[ "${ievra}" =~ $RE_EVRA ]]; then | |
iepoch="${BASH_REMATCH[1]}" | |
iversion="${BASH_REMATCH[2]}" | |
irelease="${BASH_REMATCH[3]}" | |
iarch="${BASH_REMATCH[4]}" | |
else | |
echo "failed to parse evra '${ievra}' of installed package! skipping..." >&2 | |
continue | |
fi | |
if [[ "${iarch}" != "${aarch}" ]]; then | |
continue | |
fi | |
rpmdev-vercmp \ | |
"${iepoch}" "${iversion}" "${irelease}" \ | |
"${aepoch}" "${aversion}" "${arelease}" >/dev/null && rc=$? || rc=$? | |
if [[ "$rc" == 11 ]]; then | |
# installed package is newer -> downgrade | |
echo "${aepoch}:${pkgname}-${aversion}-${arelease}.${aarch}" | |
fi | |
done | |
} | |
function get_critical() { | |
grep ":${RE_UNSUPPORTED}" "${to_downgrade}" | |
} | |
function get_noncritical() { | |
grep -v ":${RE_UNSUPPORTED}" "${to_downgrade}" | |
} | |
function downgrade_critical() { | |
# Warning: Dangerous - upgrades of these packages are unsupported! | |
local answer | |
[[ "${safe}" == 1 || -z "$(get_critical)" ]] && return 0 | |
if [[ "$assume_yes" == 0 ]]; then | |
echo "Do you want to downgrade to the following critical packages?" | |
get_critical | sed 's/^/ /' | |
while IFS= read -p "[yes/no]: " -r answer; do | |
echo "${answer}" | grep -iq '^\s*no\?\s*$' && return 0 | |
echo "${answer}" | grep -iq '^\s*y\(es\)\?\s*$' && break | |
done | |
fi | |
pushd "${tmp}" | |
get_critical | xargs -r yumdownloader | |
find -name '*.rpm' | grep "${RE_UNSUPPORTED}" | xargs sudo rpm -Uvh --force --nodeps | |
popd | |
} | |
if [[ -z "${to_downgrade:-}" ]]; then | |
to_downgrade="${tmp}/to_downgrade" | |
mkpkglist | tee "${to_downgrade}" >"${out}" | |
fi | |
[[ "$dry_run" == 1 ]] && exit 0 | |
downgrade_critical | |
get_noncritical | xargs -r sudo yum downgrade "${downgrade_args[@]}" | |
# vim: set et ts=4 sw=4 : |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment