#!/bin/bash
# slackware-upgrade-system - full upgrade of a Slackware installation
# Copyright (C) 2019 Sergey Poznyakoff.
#
# Slackware-upgrade-system 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, or (at your option)
# any later version.
#
# Slackware-upgrade-system 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with slackware-upgrade-system. If not, see
# .
set -e
# Configuration directory
: ${SUS_CONFDIR:=/etc/slackware-upgrade-system}
# Slackware root directory
rooturl=
# Log file name
logfile=
# 'y', if dry-run mode is requested
dry_run=
# If 'y', don't ask anything, assume "y" as answer to all quieries
assume_y=
# Output verbosity: '' - normal, 'v' - verbose, 'q' - quiet
verbosity=
# If 'y', install all packages
install_all=
# Names of the series that should be installed in addition to already installed
# packages. Ignored, if install_all is set.
install_series=
# Names of additional packages.
install_packages=
# Name of the keep-list file.
keep_file=
# Internal variables
remote=
tempdir=${TMP:-/tmp}/slackupg.$$
installed_list=$tempdir/installed.list
avail_index=$tempdir/avail.index
avail_list=$tempdir/avail.list
series_names="a
ap
d
e
f
k
l
n
t
tcl
x
xap
xfce
y
"
function tempdir_create() {
u=$(umask)
umask 077
mkdir $tempdir
umask $u
}
function tempdir_remove() {
rm -rf $tempdir
}
function usage() {
cat <&2 "$0: $*"
if [ -n "$logfile" ]; then
echo >$logfile "$0: $*"
fi
}
function abend() {
error "$@"
tempdir_remove
exit 1
}
function package_file_name() {
pkg=$(awk -vname=$1 '$1==name { print $2 }' $avail_index)
if [ -z "$pkg" ]; then
error "package $name not found in index (should not happen!)"
else
echo $pkg
fi
}
function package_name_md5sum() {
pkg=$(awk -vname=$1 '$1==name { print $3 }' $avail_index)
}
function check_package_md5sum() {
if [ -n "$checksums" ]; then
awk -vcname="$1" -vdname="$2" \
'$2==cname { print $1 " " dname }' $checksums | \
md5sum --status --check
fi
}
function all_package_names() {
grep -v '.*/kde[^/]*/' $avail_index | cut -d ' ' -f 1
}
function series_package_names() {
grep '.*/'"$1/" $avail_index | cut -d ' ' -f 1
}
function catfile() {
echo $rooturl/${1#./}
}
function download_curl {
curl $CURL_OPTIONS -L -sS -o$1 $2
}
function download_wget {
if ! wget $WGET_OPTIONS -nv -o wget.log -O$1 $2; then
grep -i "failed\|error" wget.log
/bin/false
fi
}
function dnfunc_init() {
if [ -z "$dnfunc" ]; then
if wget --version >/dev/null 2>&1; then
dnfunc=download_wget
elif curl --version >/dev/null 2>&1; then
dnfunc=download_curl
else
abend "neither curl nor wget is installed"
fi
fi
}
function download() {
local name=$(basename $1)
local url=$(catfile $1)
if $dnfunc $name $url; then
echo $name
fi
}
function getfile() {
local name=$(if [ -n "$remote" ]; then
download $1
else
catfile $1
fi)
if [ -n "$2" ]; then
ascname=$(if [ -n "$remote" ]; then
download $1.asc
else
catfile $1.asc
fi)
if [ -n "$ascname" ] \
&& ${GPG:-gpg} --verify $ascname $name 2>/dev/null; then
:
else
error "gpg verification failed for $name"
return
fi
fi
if [ -n "$checksums" ] && ! check_package_md5sum $1 $name; then
error "ERROR: $1: checksum failed"
name=
fi
echo $name
}
function dropfile() {
if [ -n "$remote" ]; then
rm $1
fi
}
function upgrade_local() {
if [ -n "$dry_run" ]; then
echo "upgradepkg $@"
elif [ "$verbosity" = 'v' ]; then
upgradepkg "$@" | \
tee -a $logfile | \
sed -n -e '/^| Upgrading/s/^| //p'
else
upgradepkg "$@" >> $logfile
fi
}
function upgrade_package() {
local name=$1
shift
if [ -n "$dry_run" ]; then
echo "upgradepkg $@ $name"
else
file=$(getfile $name)
if [ -n "$file" ]; then
upgrade_local $@ $file
dropfile $file
fi
fi
}
function version_gt() {
test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1"
}
while getopts "ahknp:qs:vy" OPTION
do
case $OPTION in
a) install_all=y;;
h) usage
exit 0;;
k) keep_file=$OPTARG;;
n) dry_run=y;;
p) install_packages="$install_packages $OPTARG";;
q) verbosity=q;;
v) verbosity=v;;
s) install_series="$install_series $OPTARG";;
y) assume_y=y;;
*) usage >&2
exit 1
esac
done
shift $(($OPTIND - 1))
case $# in
0) ;;
1) rooturl=$1;;
*) error "unexpected arguments"
usage >&2
exit 1
esac
if [ $(id -u) != "0" ]; then
abend "must be root"
fi
# Sanity check
if [ ! -s /etc/os-release ]; then
abend "/etc/os-release doesn't exist"
fi
. /etc/os-release
if [ "$ID" != "slackware" ]; then
abend "this doesn't seem to be a Slackware installation"
fi
case "$(uname -m)" in
i?86) ARCH=;;
x86_64) ARCH=64;;
*) abend "architecture $(uname -m) is not yet supported"
esac
tempdir_create
cd $tempdir
mirrors_url=https://mirrors.slackware.com/slackware
if [ -z "$rooturl" ]; then
dnfunc_init
if ! $dnfunc index.html $mirrors_url; then
abend "exiting"
exit 1
fi
version_rx=$(echo $VERSION | sed -e 's/\./\\./g')
new_version=$(cat index.html |\
tr '<' '\n' |\
sed -n -r \
-e 's/.*^a href="slackware'$ARCH'-([[:digit:].]+)\/?".*/\1/p'|\
sed -n -e "/$version_rx/{" -en -ep -e '}' )
if [ -n "$new_version" ]; then
rooturl="$mirrors_url/slackware$ARCH-$new_version"
error "info: using $rooturl as distribution top-level URL"
else
abend "can't find distribution newer than $VERSION; please supply URL if you have any"
fi
fi
# Check if rooturl is local or remote
case $rooturl in
/*) unset remote
;;
http://*|https://*|ftp://*|ftps://*)
dnfunc_init
remote=1
;;
*) abend "root directory must be absolute file name or URL"
esac
if [ -z "$remote" ]; then
# Check if rooturl exists and contains the necessary files and directories
if [ ! -d $rooturl ]; then
abend "$rooturl does not exist"
fi
fi
# Check if rooturl contains all we need
error "info: verifying distribution..."
#
# Download CHECKSUMS.md5. So far it is the only file that is gpg-checked.
# For the rest we rely on MD5 sums.
checksums=$(getfile CHECKSUMS.md5 gpg)
if [ -z "$checksums" ]; then
abend "CHECKSUMS.md5 not found in $rooturl"
fi
announce=$(tail +13 $checksums | \
sed -n -r\
-e 's/^[0-9a-fA-F]+[[:space:]]+(\.\/ANNOUNCE\.[[:digit:]_]+)$/\1/p')
if [ -z "$announce" ]; then
abend "ANNOUNCE not found in $rooturl"
fi
file=$(getfile $announce)
if [ -z "$file" ]; then
abend "file $announce not found in $rooturl"
fi
newversion=$(echo "$announce" | sed -e 's/\.\/ANNOUNCE\.//' -e 's/_/./g')
if [ -z "$newversion" ]; then
abend "cannot determine new version"
fi
# Create list and index of available files
tail +13 $checksums | \
sed -r \
-n \
-e 's/^[0-9a-fA-F]+[[:space:]]+(\.\/slackware(64)?\/.*\/(.*)-[^-]+-(i386|x86(_64)?|arm|noarch|fw)-[[:digit:]]+(_.*)?\.t.z)$/\3 \1/p' | \
tee $avail_index | awk '{print $1}' | sort > $avail_list
# Initialize log file name
logstem=/var/log/slackware-upgrade-system-$VERSION-$newversion
logfile=$logstem.log
remove_report=$logstem.removed
# Check if pkgdir exists and contains the necessary files and directories
for series in $series_names
do
n=$(sed -n -r -e 's/^[^[:space:]]+[[:space:]]+(\.\/slackware(64)?\/'$series'\/.*\.t.z)$/\1/p' $avail_index | head -1)
if [ -z "$n" ]; then
abend "no files in series $series"
fi
file=$(getfile $n)
if [ -z "$file" ]; then
abend "exiting"
fi
dropfile $file
done
# Build a list of installed packages
ls /var/log/packages |\
sed -r -e 's/-[^-]+-(i386|x86(_64)?|arm|noarch|fw)-[[:digit:]]+(_.*)?//' |\
sort > $installed_list
# Build a list of packages to install
(if [ -n "$install_all" ]; then
all_package_names
else
comm -1 -2 $installed_list $avail_list
fi
for s in $install_series
do
series_package_names $s
done
if [ -n "$install_packages" ]; then
echo $install_packages
fi) | sort -u > candidates.$$
# Build a list of packages that should be removed after install
comm -2 -3 $installed_list candidates.$$ > remove.list.$$
if [ -s "$keep_file" ]; then
grep -v '^#' $keep_file | \
tr -s '\n' | sort | comm -2 -3 remove.list.$$ - > remove.list
rm remove.list.$$
else
mv remove.list.$$ remove.list
fi
# Create $remove_report and the list of installation candidates.
if [ -f "$SUS_CONFDIR/$VERSION-$newversion.repl" ]; then
error "info: reading $SUS_CONFDIR/$VERSION-$newversion.repl"
# Update candidates and save the replacement map in a temporary.
awk '{ sub(/#.*$/,""); sub(/[[:space:]]+$/,"") }
/\\$/ { sub(/\\$/,""); p = p $0; next }
NF == 0 { if (p) print p; p = ""; next }
{ print p $0; p="" }' $SUS_CONFDIR/$VERSION-$newversion.repl | \
sort +0 -1 | \
tee rename.$$ | \
join - remove.list | \
cut -d ' ' -f 2- | \
tr ' ' '\n' | \
cat - candidates.$$ | \
sort -u > candidates
# Use the temporary to remove the original package names from the report.
awk '{print $1}' rename.$$ | \
comm -13 - remove.list > $remove_report
# Clean up
rm rename.$$
else
cp remove.list $remove_report
mv candidates.$$ candidates
fi
# Disable interrupts during critical section
trap '' HUP INT QUIT ABRT
if [ -s $remove_report -a "$verbosity" != 'q' ]; then
(cat <> $logfile
fi
echo "$0: see $remove_report for the list of packages that have been removed"
fi
echo "$0: upgrade finished; see $logfile for details"
tempdir_remove
conffiles=$logstem.conffiles
find /etc /usr/lib*/ /usr/share/vim -name "*.new" 2>/dev/null | \
sort > $conffiles
if [ ! -s "$conffiles" ]; then
rm $conffiles
fi
if [ "$verbosity" != 'q' ]; then
echo "IMPORTANT!"
if [ -s "$conffiles" ]; then
cat <