Skip to content

Instantly share code, notes, and snippets.

@miminar
Last active November 27, 2018 10:45
Show Gist options
  • Save miminar/238344d4daaf55123dd51fcd97ebae72 to your computer and use it in GitHub Desktop.
Save miminar/238344d4daaf55123dd51fcd97ebae72 to your computer and use it in GitHub Desktop.
Downgrade all packages to the latest available version with yum
#!/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