#!/bin/sh
# coding: utf-8
#
# UPGRADE-SYSTEM -- Command for upgrading and sanitizing a Debian system.
#
# HOMEPAGE
# https://tracker.debian.org/pkg/upgrade-system
#
# LICENSE
# SPDX-License-Identifier: GPL-2.0-or-later
#
# AUTHORS
# SPDX-FileCopyrightText: 2004-2026 Martin-Éric Racine <martin-eric.racine@iki.fi>
# SPDX-FileCopyrightText: 2004,2012 Christoph Schindler <hop@30hopsmax.at>
# SPDX-FileCopyrightText: 2003-2004 Martin Zdrahal <martin.zdrahal@konflux.at>
#
# DEPENDS
# apt         (required): (>= 1.3.0: --autoremove)(>= 0.7.0: --fix-policy)
# coreutils   (required): cut,rm,sort,tty,uniq.
# dpkg        (required): dpkg,dpkg-query.
# findutils   (required): find.
# grep        (required): grep.
# mawk        (required): awk.
# ncurses-bin (required): tput.
# RECOMMENDS
# deborphan   (optional): (>= 1.5-13: --libdevel).
# debsums     (optional): debsums.
# SUGGESTS
# unattended-upgrades (optional): unattended-upgrade.
#
# 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 ###
########################
if command -v tput >/dev/null && tput setaf 1 >/dev/null 2>&1; then
	BOLD=$(tput bold)
	RED=${BOLD}$(tput setaf 1)
	GREEN=${BOLD}$(tput setaf 2)
	YELLOW=${BOLD}$(tput setaf 3)
	RESET=$(tput sgr0)
else
	BOLD=""
	RED=""
	GREEN=""
	YELLOW=""
	RESET=""
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
: "${UPDATEOPTS:=--quiet --quiet --yes --allow-releaseinfo-change-codename --allow-releaseinfo-change-version}"
: "${UPGRADEOPTS:=--purge --option APT::Get::Build-Dep-Automatic=true --auto-remove --fix-broken dist-upgrade}"
: "${ORPHANOPTS:=--libdevel --guess-all --no-guess-debug}"
: "${CLEANOPTS:=clean}"
: "${FLAUSCH:=}"
echo "${BOLD}I: Upgrade-System options found:${RESET}"
echo " Update options: $UPDATEOPTS"
echo "Upgrade options: $UPGRADEOPTS"
if command -v deborphan >/dev/null; then
	echo " Orphan options: $ORPHANOPTS"
else
	echo " Orphan options: ${YELLOW}Command not found. Options ignored.${RESET}"
fi
echo "  Clean options: $CLEANOPTS"
############################
### UPDATE PACKAGE LISTS ###
############################
echo "${BOLD}1) Updating package lists:${RESET}"
if ! apt-get $UPDATEOPTS 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 --yes 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
####################################################################
### FLAUSCH'S SUPER CRUFT LIQUIDATOR -- USE WITH EXTREME CAUTION ###
####################################################################
# Enabled whenever the FLAUSCH environment variable is set anywhere.
case $FLAUSCH in
"")
	# Do nothing.
	;;
*)
	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 $(debsums --list-missing --silent) 2>/dev/null | 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 --yes 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:"
		apt-mark auto $(dpkg -l | grep -i -e dummy -e transitional | awk '{print $2}' | cut --delimiter : --fields 1 | sort) | awk '{print $1}'
		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
####################################################################
#############################
### PURGE ORPHAN PACKAGES ###
#############################
echo "${BOLD}3) Checking for orphan packages:${RESET}"
REMOVABLE=$(apt-get --purge --option APT::Get::Build-Dep-Automatic=true --simulate --yes autoremove | awk '/^Purg / {print $2}')
# deborphan kludge (Debian #672829 and Ubuntu LP #940374).
while [ "${DEBORPHANS-undef}" != "$DEBORPHANS_OLD" ]; do
	DEBORPHANS_OLD=$DEBORPHANS
	if command -v deborphan >/dev/null; then
		DEBORPHANS=$(deborphan $ORPHANOPTS)
	else
		DEBORPHANS=""
	fi
	ORPHANS=${REMOVABLE:+$REMOVABLE }$DEBORPHANS
	REMOVABLE=""
	case $ORPHANS in
	"")
		echo "I: No orphan package to purge."
		;;
	*)
		echo "I: Purging orphan packages..."
		if ! apt-get --purge --option APT::Get::Build-Dep-Automatic=true autoremove $ORPHANS; then
			exit 1
		fi
		;;
	esac
done
#######################
### CLEAN APT CACHE ###
#######################
echo "${BOLD}4) Cleaning package cache.${RESET}"
if ! apt-get $CLEANOPTS; then
	echo "${RED}E: Package cache could not be cleaned.${RESET}"
	exit 1
else
	echo "I: Package cache cleaned."
fi
echo "${BOLD}I: System upgrade completed.${RESET}"
#EOF
