#!/bin/sh
# coding: utf-8
#
## UPGRADE-SYSTEM -- Command for upgrading and sanitizing a Debian system.
#
## HOMEPAGE
#  http://q-funk.iki.fi/debian
#
## AUTHORS
#  Copyright © 2004-2020 Martin-Éric Racine <martin-eric.racine@iki.fi>
#  Copyright © 2004,2012 Christoph Schindler <hop@30hopsmax.at>
#  Copyright © 2003-2004 Martin Zdrahal <martin.zdrahal@konflux.at>
#
## LICENSE
#  GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>
#
## DEPENDENCIES
#D:apt:apt-get (main/important) (>= 0.6.45: autoclean)(>= 0.7.0: --fix-policy).
#S:command-not-found:update-command-not-found (main/optional).
#  coreutils:cut,rm,sort,tty (main/required).
#D:deborphan:deborphan (universe/optional) (>= 1.7: --libdevel).
#R:debsums:debsums (universe/optional).
#  dpkg:dpkg,dpkg-query (main/required).
#  findutils:find (main/required).
#  grep:grep (main/required).
#  mawk:awk (main/required).
#  ncurses-bin:tput (main/required).
#  util-linux:setterm (main/required).
#
## CHANGES
#  2018-03-29   Run update-alternatives --all   v1.8 [MER]
#  2014-08-03   List DPKG config backups.       v1.7 [MER]
#  2012-03-10   Make all APT checks quiet.      v1.6 [MER]
#  2012-03-06   Add autoremove to orphan purge. v1.5 [CS]
#  2011-12-21   Add APT --fix-policy install.   v1.5 [MER]
#  2011-03-03   Add debsum reinstallation.      v1.4 [MER]
#  2010-05-02   Add crude prompt colorization.  v1.3 [MER]
#  2009-08-08   Add obsolete config purge.      v1.2 [MER]
#  2009-06-21   Add uninstalled packages purge. v1.1 [MER]
#  2005-12-04   Use APT instead of DPKG purge.  v1.0 [MER]
#  2005-05-29   Add non-interactive detection.  v0.9 [MER]
#  2004-09-04   Make orphan purge recursive.    v0.8 [CS]
#  2004-08-19   Add APT exit code check.        v0.7 [MER]
#  2004-06-07   Add CLEANOPTS to config.        v0.6 [MER]
#  2004-03-31   Create config file.             v0.5 [MER]
#  2004-03-24   Add -y to dist-upgrade.         v0.4 [MER]
#  2004-03-15   Add --guess-doc --libdevel.     v0.3 [MER]
#  2004-03-09   Rename to upgrade-system.       v0.2 [MER]
#  2004-02-16   Initial release.                v0.1 [MZ]
##
#########################################
### INSERT EVENTUAL LOCALISATION HERE ###
#########################################
DISPLAY=
LANGUAGE=
LC_ALL=C
TEXTDOMAIN=upgrade-system
export DISPLAY LANGUAGE LC_ALL TEXTDOMAIN
###########################################################
### SOURCE CONFIGURED OPTIONS AND SET FALLBACK DEFAULTS ###
###########################################################
if [ -f /etc/upgrade-system.conf ]
then
	. /etc/upgrade-system.conf
fi
: ${CLEANOPTS:="clean"}
: ${UPGRADEOPTS:="--fix-broken --purge --show-upgraded dist-upgrade"}
: ${ORPHANOPTS:="--guess-all --libdevel"}
: ${FLAUSCH:=""}
########################
### SET SHELL COLORS ###
########################
BOLD=$(setterm -bold on)
RESET=$(setterm -default)
##DEPENDS: util-linux (main/required).
if [ -x /usr/bin/tput ] && tput setaf 1 >/dev/null 2>&1;
then
	RED=${BOLD}$(tput setaf 1)
	GREEN=${BOLD}$(tput setaf 2)
	YELLOW=${BOLD}$(tput setaf 3)
else
	RED=${BOLD}
	GREEN=${BOLD}
	YELLOW=${BOLD}
fi
##DEPENDS: ncurses-bin (main/required).
############################
### SET DEBCONF FRONTEND ###
############################
tty --silent
##DEPENDS: coreutils (main/required).
if [ $? != 0 ]
then
	echo "${GREEN}N: Non-Interactive upgrade selected.${RESET}"
	NOTTY="--option DPkg::Options::=--force-confdef --quiet --yes"
	DEBIAN_FRONTEND="noninteractive"
	export DEBIAN_FRONTEND
fi
############################
### UPDATE PACKAGE LISTS ###
############################
echo "${BOLD}1) Updating package lists:${RESET}"
apt-get $NOTTY --quiet --quiet update
##DEPENDS: apt (main/important).
if [ $? != 0 ]
then
	echo "${RED}E: Some package lists could not be updated.${RESET}"
	exit 1
else
	echo "I: Package lists updated."
fi
########################
### UPGRADE PACKAGES ###
########################
echo "${BOLD}2) Checking for upgradable packages:${RESET}"
UPGRADABLE=$(apt-get $NOTTY --simulate $UPGRADEOPTS | awk '/^Inst / {print $2}')
##DEPENDS: apt (main/important), mawk (main/required).
case $UPGRADABLE in
	"")
		echo "I: No upgradable package to install."
		break
		;;
	*)
		echo  "I: Installing upgradable packages..."
		apt-get $NOTTY $UPGRADEOPTS
		##DEPENDS: apt (main/important).
		if [ $? != 0 ]
		then
			echo "${RED}E: Some packages could not be upgraded.${RESET}"
			exit 2
		fi
		;;
esac
dpkg --configure --pending
if [ $? != 0 ]
then
	echo "${RED}E: Some packages could not be upgraded.${RESET}"
	exit 2
fi
#############################
### PURGE ORPHAN PACKAGES ###
#############################
echo "${BOLD}3) Checking for orphan packages:${RESET}"
REMOVABLE=$(apt-get $NOTTY --purge --simulate autoremove | awk '/^Purg / {print $2}')
##DEPENDS: apt (main/important), mawk (main/required).
# deborphan kludge (Debian #672829 and Ubuntu LP #940374).
while [ "${DEBORPHANS-undef}" != "$DEBORPHANS_OLD" ]
do
	DEBORPHANS_OLD=$DEBORPHANS
	DEBORPHANS=$(deborphan $ORPHANOPTS)
	if [ $? != 0 ]
	then
		echo "${RED}E: Checking failed unexpectedly. Run 'dpkg --configure --pending' as root.${RESET}"
		exit 3
	fi
	##DEPENDS: deborphan (universe/optional).
	ORPHANS=${REMOVABLE:+$REMOVABLE }$DEBORPHANS
	REMOVABLE=""
	case $ORPHANS in
		"")
			echo "I: No orphan package to purge."
			break
			;;
		*)
			echo "I: Purging orphan packages..."
			apt-get $NOTTY --purge autoremove $ORPHANS
			##DEPENDS: apt (main/important).
			### Escape dangerous purges automatically.
			if [ $? != 0 ]
			then
			        break
			fi
			;;
	esac
done
####################################################################
### FLAUSCH'S SUPER CRUFT LIQUIDATOR -- USE WITH EXTREME CAUTION ###
####################################################################
# Enabled whenever the FLAUSCH environment variable is set anywhere.
case $FLAUSCH in
	"")
		break
		;;
	*)
		echo "${YELLOW}W: FLAUSCH LOOP ENABLED. USE WITH EXTREME CAUTION!${RESET}"
		###############################################
		### REINSTALL PACKAGES WITH MISSING DEBSUMS ###
		###############################################
		if [ -x /usr/bin/debsums ]
		then
			echo "${BOLD}Checking for packages with missing debsums:${RESET}"
			while true
			do
				DEBSUM=$(dpkg-query --search 2>/dev/null $(debsums --list-missing --silent) | cut --delimiter : --fields 1 | uniq)
				##DEPENDS: dpkg (main/required), debsums (universe/optional), coreutils (main/required).
				case $DEBSUM in
					"")
						echo "I: No package with missing debsums to reinstall."
						break
						;;
					*)
						echo "I: Reinstalling packages with missing debsums..."
						apt-get $NOTTY --reinstall install $DEBSUM
						##DEPENDS: apt (main/important).
						### Escape dangerous purges automatically.
						if [ $? != 0 ]
						then
			        			break
						fi
						;;
				esac
			done
		fi
		############################################
		### FIX DEPENDENCIES TO MATCH APT POLICY ###
		############################################
		echo "${BOLD}Checking for missing dependencies:${RESET}"
		FIXABLE=$(apt-get $NOTTY --auto-remove --fix-policy --purge --simulate install | awk '/^Inst / {print $2}')
		##DEPENDS: apt (main/important), mawk (main/required).
		case $FIXABLE in
			"")
				echo "I: No missing dependency to install."
				break
				;;
			*)
				echo  "I: Installing missing dependencies..."
				apt-get $NOTTY --auto-remove --fix-policy --purge --option Debug::pkgDepCache::AutoInstall=true install
				##DEPENDS: apt (main/important).
				if [ $? != 0 ]
				then
					echo "${RED}E: Some dependencies could not be installed.${RESET}"
					exit 4
				fi
				;;
		esac
		##################################
		### PURGE UNINSTALLED PACKAGES ###
		##################################
		echo "${BOLD}Checking for uninstalled packages:${RESET}"
		while true
		do
			ORPHANS=$(dpkg-query --list | grep '^rc' | awk '{print $2}')
			##DEPENDS: dpkg (main/required), grep (main/required), mawk (main/required).
			case $ORPHANS in
				"")
					echo "I: No uninstalled package to purge."
					break
					;;
				*)
					echo "I: Purging uninstalled packages..."
					dpkg --purge $ORPHANS
					##DEPENDS: dpkg (main/required).
					### Escape dangerous purges automatically.
					if [ $? != 0 ]
					then
			        		break
					fi
					;;
			esac
		done
		###############################
		### REMOVE OBSOLETE CONFIGS ###
		###############################
		echo "${BOLD}Checking for obsolete configurations:${RESET}"
		ORPHANS=$(dpkg-query --show --showformat='${Conffiles}\n' | grep obsolete | awk '{print $1}')
		##DEPENDS: dpkg (main/required), grep (main/required), mawk (main/required).
		case $ORPHANS in
			"")
				echo "I: No obsolete configuration to purge."
				break
				;;
			*)
				echo "I: Removing obsolete configurations..."
				unique(){
					dpkg-query --search $ORPHANS | cut --delimiter : --fields 1 | uniq
					##DEPENDS: dpkg (main/required), coreutils (main/required), coreutils (main/required).
					}
				UNIQUE=$(unique)
				NUMBER=$(unique | wc --lines)
				echo "I: Number of packages affected: $NUMBER."
				dpkg-query --search $ORPHANS
				##DEPENDS: dpkg (main/required).
				echo "${YELLOW}W: BEWARE OF FALSE POSITIVES! DELETE WITH EXTREME CAUTION!${RESET}"
				rm --interactive $ORPHANS
				##DEPENDS: coreutils (main/required).
				apt-get $NOTTY --reinstall install ${UNIQUE}
				##DEPENDS: apt (main/important).
				### Escape dangerous purges automatically.
				if [ $? != 0 ]
				then
				        break
				fi
				;;
		esac
		#############################################################################
		### RUN 'update-alternatives --config' ON DEAD LINKS TO ENFORCE A CLEANUP ###
		#############################################################################
		echo "${BOLD}Checking for dead symbolic links in /etc/alternatives:${RESET}"
		ORPHANS=$(find -L /etc/alternatives -type l | cut --delimiter / --fields 4)
		case $ORPHANS in
			"")
				echo "I: No dead symbolic link to clean up."
				break
				;;
			*)
				echo "I: Dead symbolic links to clean up:"
				for link in $ORPHANS
					do
						update-alternatives --config $link
					done
				;;
		esac
		#####################################################
		### LIST CONFIGS THAT REQUIRE MANUAL INTERVENTION ###
		#####################################################
		echo "${BOLD}Checking for configuration backups:${RESET}"
		ORPHANS=$(find /etc/ -name '*.dpkg-*')
		##DEPENDS: findutils (main/required), grep (main/required).
		case $ORPHANS in
			"")
				echo "I: No configuration backup to examine."
				break
				;;
			*)
				echo "I: Configuration backups to examine:"
				if [ -n "$(echo $ORPHANS | grep bak)" ]
				then
					echo "Backups of obsolete configurations:"
					find /etc/ -name '*.dpkg-bak' | sort
				fi
				if [ -n "$(echo $ORPHANS | grep old)" ]
				then
					echo "Backups of relevant configurations:"
					find /etc/ -name '*.dpkg-old' | sort
				fi
				if [ -n "$(echo $ORPHANS | grep dist)" ]
				then
					echo "Backups of upstream configurations:"
					find /etc/ -name '*.dpkg-dist' | sort
				fi
				;;
		esac
	echo "${GREEN}N: FLAUSCH LOOP COMPLETED.${RESET}"
esac
####################################################################
#######################
### CLEAN APT CACHE ###
#######################
echo "${BOLD}4) Cleaning package cache.${RESET}"
apt-get $NOTTY $CLEANOPTS
##DEPENDS: apt (main/important).
echo "I: System upgrade completed."
#EOF
