#!/bin/sh
# coding: utf-8
#
## UPGRADE-SYSTEM -- Command for upgrading and sanitizing a Debian system.
#
## HOMEPAGE
#  https://tracker.debian.org/pkg/upgrade-system
#
## AUTHORS
#  Copyright © 2004-2021 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>
#
## DEPENDS
#  apt: apt-get (important)(>= 1.3.0: --autoremove)(>= 0.7.0: --fix-policy)
#  coreutils: cut,rm,sort,tty,uniq (required).
#  deborphan: deborphan (optional)(>= 1.5-13: --libdevel).
#  dpkg: dpkg,dpkg-query (required).
#  findutils: find (required).
#  grep: grep (required).
#  mawk: awk (required).
#  ncurses-bin: tput (required).
#  util-linux: setterm (required).
## RECOMMENDS
#  debsums: debsums (optional).
## SUGGESTS
#  unattended-upgrades: unattended-upgrade (optional).
#
## CHANGES
#  2021-10-19   Major 'shellcheck' refactoring. v1.9 [MER]
#  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
########################
### SET SHELL COLORS ###
########################
BOLD=$(setterm -bold on)
RESET=$(setterm -default)
if command -v tput >/dev/null && 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
#####################################
### EXIT ON NON-INTERACTIVE SHELL ###
#####################################
if ! tty --silent;
then
	echo "${RED}E: Non-Interactive upgrade prevented.${RESET}"
	exit 1
fi
###########################################################
### SOURCE CONFIGURED OPTIONS AND SET FALLBACK DEFAULTS ###
###########################################################
if command -v unattended-upgrade >/dev/null;
then
	echo "${BOLD}I: Unattended-Upgrade options found:${RESET}"
	apt-config dump | grep Upgrade:: | sort | uniq | cut -d: -f 3-
fi
if [ -f /etc/upgrade-system.conf ]
then
	. /etc/upgrade-system.conf
fi
: "${FLAUSCH:=}"
: "${UPGRADEOPTS:=--auto-remove --fix-broken --purge dist-upgrade}"
: "${ORPHANOPTS:=--libdevel --guess-all --no-guess-debug}"
: "${CLEANOPTS:=clean}"
echo "${BOLD}I: Upgrade-System options found:${RESET}"
echo "Upgrade options: $UPGRADEOPTS"
echo " Orphan options: $ORPHANOPTS"
echo "  Clean options: $CLEANOPTS"
############################
### UPDATE PACKAGE LISTS ###
############################
echo "${BOLD}1) Updating package lists:${RESET}"
if ! apt-get --quiet --quiet update;
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 --simulate dist-upgrade | awk '/^Inst / {print $2}')
case $UPGRADABLE in
	"")
		echo "I: No upgradable package to install."
		;;
	*)
		echo  "I: Installing upgradable packages..."
		if command -v unattended-upgrade >/dev/null;
		then
			if ! unattended-upgrade --verbose;
			then
				echo "${RED}E: Some packages could not be upgraded.${RESET}"
				exit 1
			fi
		fi
		if ! apt-get $UPGRADEOPTS;
		then
			echo "${RED}E: Some packages could not be upgraded.${RESET}"
			exit 1
		fi
		;;
esac
if ! dpkg --configure --pending;
then
	echo "${RED}E: Some packages could not be upgraded.${RESET}"
	exit 1
fi
#############################
### PURGE ORPHAN PACKAGES ###
#############################
echo "${BOLD}3) Checking for orphan packages:${RESET}"
REMOVABLE=$(apt-get --purge --simulate autoremove | awk '/^Purg / {print $2}')
# deborphan kludge (Debian #672829 and Ubuntu LP #940374).
while [ "${DEBORPHANS-undef}" != "$DEBORPHANS_OLD" ]
do
	DEBORPHANS_OLD=$DEBORPHANS
	DEBORPHANS=$(deborphan $ORPHANOPTS)
	ORPHANS=${REMOVABLE:+$REMOVABLE }$DEBORPHANS
	REMOVABLE=""
	case $ORPHANS in
		"")
			echo "I: No orphan package to purge."
			;;
		*)
			echo "I: Purging orphan packages..."
			if ! apt-get --purge autoremove $ORPHANS;
			then
			        exit 1
			fi
			;;
	esac
done
####################################################################
### FLAUSCH'S SUPER CRUFT LIQUIDATOR -- USE WITH EXTREME CAUTION ###
####################################################################
# Enabled whenever the FLAUSCH environment variable is set anywhere.
case $FLAUSCH in
	"")
		;;
	*)
		echo "${YELLOW}W: FLAUSCH LOOP ENABLED. USE WITH EXTREME CAUTION.${RESET}"
		###############################################
		### REINSTALL PACKAGES WITH MISSING DEBSUMS ###
		###############################################
		if command -v debsums >/dev/null;
		then
			echo "${BOLD}Checking for packages with missing debsums:${RESET}"
			DEBSUM=$(dpkg-query --search 2>/dev/null $(debsums --list-missing --silent) | cut --delimiter : --fields 1 | uniq)
			case $DEBSUM in
				"")
					echo "I: No package with missing debsums to reinstall."
					;;
				*)
					echo "I: Reinstalling packages with missing debsums..."
					if ! apt-get --reinstall install $DEBSUM;
					then
						exit 1
					fi
					;;
			esac
		fi
		############################################
		### FIX DEPENDENCIES TO MATCH APT POLICY ###
		############################################
		echo "${BOLD}Checking for missing package dependencies:${RESET}"
		FIXABLE=$(apt-get --fix-policy --simulate install | awk '/^Inst / {print $2}')
		case $FIXABLE in
			"")
				echo "I: No missing dependency to install."
				;;
			*)
				echo  "I: Installing missing dependencies..."
				apt-get --fix-policy --option Debug::pkgDepCache::AutoInstall=true install
				# Do NOT exit on error. NO is a valid answer (i.e. someone might purposely
				# want to avoid installing what APT suggests) but it produces an error.
				;;
		esac
		##################################
		### PURGE UNINSTALLED PACKAGES ###
		##################################
		echo "${BOLD}Checking for uninstalled packages:${RESET}"
		while true
		do
			ORPHANS=$(dpkg-query --list | grep '^rc' | awk '{print $2}')
			case $ORPHANS in
				"")
					echo "I: No uninstalled package found."
					break
					;;
				*)
					echo "I: Purging uninstalled packages..."
					apt-get --purge autoremove $ORPHANS
					# Do NOT exit on error. NO is a valid answer (i.e. someone might
					# want to keep the configuration files) but it produces an error.
					;;
			esac
		done
		#############################################################################
		### RUN 'update-alternatives --config' ON DEAD LINKS TO ENFORCE A CLEANUP ###
		#############################################################################
		echo "${BOLD}Checking for dead symbolic links in /etc/alternatives:${RESET}"
		LINKS=$(find -L /etc/alternatives -type l | cut --delimiter / --fields 4)
		case $LINKS in
			"")
				echo "I: No dead symbolic link found."
				;;
			*)
				echo "I: Dead symbolic links to clean up:"
				for link in $LINKS
					do
						if ! update-alternatives --config $link;
						then
							exit 1
						fi
					done
				;;
		esac
		##################################
		### LIST TRANSITIONAL PACKAGES ###
		##################################
		echo "${BOLD}Checking for obsolete transitional packages:${RESET}"
		ORPHANS=$(dpkg -l | grep -i -e dummy -e transitional)
		case $ORPHANS in
			"")
				echo "I: No obsolete transitional package found."
				;;
			*)
				echo "I: These obsolete transitional packages were found:"
				dpkg -l | grep -i -e dummy -e transitional | awk '{print $2}' | cut --delimiter : --fields 1 | sort
				echo "I: Filing bug reports against all packages involved might be a good idea."
				;;
		esac
		###############################
		### REMOVE OBSOLETE CONFIGS ###
		###############################
		echo "${BOLD}Checking for obsolete configurations:${RESET}"
		ORPHANS=$(dpkg-query --show --showformat='${Conffiles}\n' | grep obsolete | awk '{print $1}')
		case $ORPHANS in
			"")
				echo "I: No obsolete configuration found."
				;;
			*)
				echo "I: These configurations now belong to the following packages:"
				dpkg-query --search $ORPHANS | sort
				echo "I: They were initially installed by the following packages:"
				dpkg-query --show --showformat='${Package}\n${Conffiles}\n' | awk '/^[^ ]/{pkg=$1}/ obsolete$/{print pkg":",$1}' | sort
				echo "I: Filing bug reports against all packages involved might be a good idea."
				;;
		esac
		#####################################################
		### LIST CONFIGS THAT REQUIRE MANUAL INTERVENTION ###
		#####################################################
		echo "${BOLD}Checking for altered configurations:${RESET}"
		ORPHANS=$(find /etc/ -type f -name '*.dpkg*' -o -name '*.ucf*')
		case $ORPHANS in
			"")
				echo "I: No altered configuration found."
				;;
			*)
				echo "I: Altered configurations to examine:"
				find /etc/ -type f -name '*.dpkg*' -o -name '*.ucf*' | sort
				;;
		esac
	echo "${GREEN}N: FLAUSCH LOOP COMPLETED.${RESET}"
esac
####################################################################
#######################
### CLEAN APT CACHE ###
#######################
echo "${BOLD}4) Cleaning package cache.${RESET}"
apt-get $CLEANOPTS
echo "I: System upgrade completed."
#EOF
