Last active
June 21, 2024 01:10
-
-
Save jikamens/206e8abf7f5ff381e79951c313da78d8 to your computer and use it in GitHub Desktop.
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
#!/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