Skip to content

Instantly share code, notes, and snippets.

@jikamens
Last active June 26, 2023 15:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jikamens/206e8abf7f5ff381e79951c313da78d8 to your computer and use it in GitHub Desktop.
Save jikamens/206e8abf7f5ff381e79951c313da78d8 to your computer and use it in GitHub Desktop.
#!/bin/bash -e
# This script reads a dpkg.log fragment, identifies all of the
# packages upgraded in it, finds and downloads the pre-upgrade
# versions of those packages from Launchpad, and installs them,
# downgrading the system to approximately the state it was in before
# the packages were upgraded.
#
# To use it, copy the section of /var/log/dpkg.log containing the
# upgrade you want to undo into a new file, remove the lines
# corresponding to packages that you _don't_ want to downgrade, and
# then feed the rest into this script either on stdin or by specifying
# the log file name with `--log-file`. You need to either run the
# script as root or specify `--package-dir` to write the downloaded
# packages into a directory you have write access to and
# `--download-only` to download without running `apt-get install` on
# the package files (since you can't do that if you're not root). If
# you do the latter then you can run `sudo apt-get install` afterward,
# specifying the paths to the downloaded deb files. Note that
# `apt-get` won't recognize them as files to be installed unless
# there's at least one slash in their names, so if you're in the same
# directory as the files prefix their names with "./".
#
# Run the script with `--help` for more usage information or just read
# the usage message below.
#
# This is especially useful if you are running a pre-release version
# of Ubuntu, because when they release new versions of prerelease
# packages they delete the old ones from the release repositories, so
# you can't just use apt or apt-get to download and install the old
# version. This script's "secret sauce" is fetching the old versions
# from Launchpad instead of relying on them being in the apt
# repository.
#
# N.B. I don't know if old package builds that aren't actually in a
# final release stay in Launchpad forever or eventually get reaped,
# and if the latter than how long they are saved before being reaped.
# Therefore, it's possible that depending on how long after an upgrade
# you run the script, it won't be able to fetch all the old packages
# to downgrade.
#
# This script was written by Jonathan Kamens <jik@kamens.us>. I
# release it into the public domain. Feel free to do whatever you want
# with it.
whoami="$(basename "$0")"
pkg_dir="/var/cache/apt/archives"
usage="Usage: $whoami [--help] [--download-only] [--package-dir directory]
[--force] [--log-file dpkg-log]
--download-only Download packages without installing them
--package-dir directory Store packages in specified directory
(default $pkg_dir)
--force Install packages even if some can't be downloaded
--log-file dpkg-log Read specified log file instead of stdin"
launchpad_base="https://launchpad.net/ubuntu/+archive/primary/+files"
download_only=false
force=false
log_file=
while [ -n "$1" ]; do
case "$1" in
--help) echo "$usage"; exit ;;
--download-only) shift; download_only=true ;;
--package-dir*)
pkg_dir="$2"
shift; shift
if [ ! -d "$pkg_dir" ]; then
echo "'$pkg_dir' is not a directory" 1>&2
echo "$usage" 1>&2
exit 1
fi
;;
--force) shift; force=true ;;
--log-file)
log_file="$2"
shift; shift
if [ ! -f "$log_file" ]; then
echo "'$log_file' is not a regular file" 1>&2
echo "$usage" 1>&2
exit 1
fi
;;
*)
echo "Unrecognized command-line argument '$1'" 1>&2
echo "$usage" 1>&2
exit 1
;;
esac
done
if touch $pkg_dir/writable.$$ 2>/dev/null; then
rm -f $pkg_dir/writable.$$
else
echo "Can't write to '$pkg_dir'" 1>&2
exit 1
fi
to_install=""
while read log_line; do
set $log_line
shift # date
shift # time
if [ "$1" != "upgrade" ]; then
continue
fi
shift # action
pkg_name=${1%%:*}
arch=${1##*:}
shift # pkg_name:arch
old_version=${1##*:}
deb_name="${pkg_name}_${old_version}_${arch}.deb"
if [ -f "$pkg_dir/$deb_name" ]; then
echo "Already have $deb_name"
else
if ! curl --location --silent --output "$pkg_dir/$deb_name" \
"$launchpad_base/$deb_name"; then
echo "Failed to download $deb_name" 1>&2
if ! $force; then
exit 1
fi
continue
else
echo "Downloaded $deb_name"
fi
fi
to_install="$to_install $pkg_dir/$deb_name"
done < <(cat $log_file)
if ! $download_only; then
echo "Installing..."
apt-get install $to_install
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment