#!/usr/bin/env bash

# A script to upgrade a RHEL derivative (Fedora, CentOS, ...). If you have ansible or puppet:
# this is way more simple with an automation framework. This script works with the paradigm
# that you only have a bash available and you want to see what happens without being murdered
# by gazillions of output lines.

# This script avoids combining options, e.g. dnf -C -q instead of dnf -Cq, since
# dnf is perfectly capable of POSIX standards, but yum fails at combined parameters.
# So we'll leave it this way in case of conversion of this script to yum or extension
# to be capable of both. (tl;dr no combined options because yum sucks at POSIX standards)

# LOGGING UNIT
# We are not ignoring logging frameworks like many software out there. We'll be using logger(1) to
# log what we are doing, and it's up to you to redirect it to a specific file if you need to.
# Define the name of the logging unit/tag here:
LOGUNIT="dnfu"

# DETAILED LOG
# To not spam the logging centre with all of our debug, we will create a temporary file nonetheless.
# The script will log its destination in the end, so we can make use of mktemp(1) here.
DLOG="$(mktemp -p /tmp dnfu.XXXXXX)"||exit 1
chmod a+r "$DLOG"||exit 1



LOGGER="$(which logger 2>/dev/null||/usr/bin/logger)"
LCMD="$LOGGER -plocal0.info -it $LOGUNIT"
LWCMD="$LOGGER -plocal0.warning -it $LOGUNIT"
LECMD="$LOGGER -plocal0.err -it $LOGUNIT"
ROK="\033[666D[ \033[32mOK\033[0m ]\033[u\033[K"
RWRN="\033[666D[\033[33mWARN\033[0m]\033[u\033[K"
RERR="\033[666D[\033[31mFAIL\033[0m]\033[u\033[K"
RINF="\033[666D[\033[37mINFO\033[0m]\033[u\033[K"
echo>"$DLOG"

UPDAVAIL=1
SCMD="$(command -v snap 2>/dev/null)"
UCMD="$(command -v dnf 2>/dev/null)"
[ "$?" -ne 0 ]&&UCMD="$(command -v yum 2>/dev/null)"
[ -z "$UCMD" ]&&printf "Neither yum nor dnf found!\n" >&2&&exit 1
TSCMD="$(command -v ts||echo '/usr/bin/ts')"
[ ! -x "$TSCMD" ]&&printf "%b not executable, please install moreutils." "$TSCMD" >&2&&exit 1
TSPARMS=( '[%Y-%m-%d %H:%M:%S]' )
RETVAL=0

function radd {
	[ -z "$1" ]&&return 1
	printf "\033[666D\033[u\033[K \033[37m(%b)\033[0m" "$1"
}
function rbeg {
	[ -z "$1" ]&&return 1
	printf "[....] %b.\033[s.." "$1"
}
function rok {
	case "$1" in
		"") printf "%b\n" "$ROK";;
		*)
			printf "%b" "$ROK"
			radd "$1"
			printf "\n"
		;;
	esac
}
function rwrn {
	case "$1" in
		"") printf "%b\n" "$RWRN";;
		*)
			printf "%b" "$RWRN"
			radd "$1"
			printf "\n"
		;;
	esac
}
function rerr {
	case "$1" in
		"") printf "%b\n" "$RERR";;
		*)
			printf "%b" "$RERR"
			radd "$1"
			printf "\n"
		;;
	esac
}
function rinf {
	case "$1" in
		"") printf "%b\n" "$RINF";;
		*)
			printf "%b" "$RINF"
			radd "$1"
			printf "\n"
		;;
	esac
}
function supgrade {
	#printf "Listing updates...:\n"
	#sudo "${UCMD}" -C check-upgrade 2>&1|sed "s/^/$(date --rfc-3339=seconds) /">>"$DLOG"
	#case "${PIPESTATUS[0]}" in
	#	0) $LCMD "No updates found.";;
	#	100) $LCMD "Updates found.";;
	#	*) $LECMD "check-upgrade went wrong!";RETVAL=1;return 1;;
	#esac
	USTART="$(date +%s)"
	rbeg "Upgrading system packages"
	sudo "${UCMD}" -y upgrade 2>&1|"${TSCMD}" "${TSPARMS[@]}">>"$DLOG"
	case "${PIPESTATUS[0]}" in
		0)

			UFIN="$(date +%s)"
			$LCMD "Upgrade OK.";rok "Update took $((UFIN-USTART)) seconds."
		;;
		1)
			UFIN="$(date +%s)"
			$LWCMD "Error on upgrade, but handled by dnf."
			rwrn "error occurred but handled by dnf (duration: $((UFIN-USTART)) seconds)"
			case "RETVAL" in
				0) RETVAL=255;;
			esac
		;;
		255)
			UFIN="$(date +%s)"
			$LWCMD "Upgrade finished, pending updates remaining."
			rwrn "Upgrade done but updates remaining (duration: $((UFIN-USTART)) seconds)"
			case "$RETVAL" in
				0) RETVAL=255;;
			esac
		;;
		*)
			$LECMD "Upgrade failed."
			rerr
			RETVAL=1;return 1
		;;
	esac
	unset UFIN USTART
}
function snrefresh {
	rbeg "Refreshing snapd snaps"
	sudo "${SCMD}" refresh 2>&1|"${TSCMD}" "${TSPARMS[@]}">>$DLOG
	case "${PIPESTATUS[0]}" in
		0) $LCMD "Snaps refreshed.";rok;;
		*) $LECMD "Error on refreshing snaps.";rerr;RETVAL=1;return 1;;
	esac
}

CSTART="$(date +%s)"
rbeg "Updating dnf cache"
sudo "${UCMD}" makecache 2>&1|"${TSCMD}" "${TSPARMS[@]}">> "$DLOG"
case "${PIPESTATUS[0]}" in
	0)
		CFIN="$(date +%s)"
		$LCMD "DNF cache update OK.";rok "duration: $((CFIN-CSTART)) seconds"
	;;
	100)
		CFIN="$(date +%s)"
		$LWCMD "RC 100 on makecache.";rwrn "duration: $((CFIN-CSTART)) seconds"
	;;
	*) $LECMD "DNF cache update failed!";rerr;exit 1;;
esac
unset CFIN CSTART
rbeg "Checking for upgrades"
sudo "${UCMD}" -C check-upgrade 2>&1|"${TSCMD}" "${TSPARMS[@]}">>"$DLOG"
case "${PIPESTATUS[0]}" in
	0)
		$LCMD "No updates found.";rinf "No updates"
	;;
	100)
		$LCMD "Updates found."
		rok "Updates found"
		UPDAVAIL=0
	;;
	*) $LECMD "check-upgrade went wrong!";rerr;exit 1;;
esac

if [ "$UPDAVAIL" -eq 0 ];then
	supgrade
fi
#printf "Checking for outdated running services or necessity to reboot...:\n"
UPDNUM="$(sudo "${UCMD}" needs-restarting -C 2>/dev/null|grep -Pc '^[0-9]+[ ]+:')"
if echo "$UPDNUM"|grep -P '^[0-9]+$' >/dev/null;then
	case "$UPDNUM" in
		0) printf "No service requires reboot.\n";;
		1) printf "1 service requires reboot.\n";sudo "${UCMD}" needs-restarting -C 2>/dev/null|tee -a "$DLOG";;
		*) printf "%b services require reboot.\n" "$UPDNUM";sudo "${UCMD}" needs-restarting -C 2>/dev/null|tee -a "$DLOG";;
	esac
else
	printf "Error while fetching number of services requiring reboot.\n" >&2
fi
sudo "${UCMD}" needs-restarting -C -r --color true 2>&1|tee -a "$DLOG"
sudo "${UCMD}" needs-restarting -r >/dev/null 2>&1 ||$LWCMD "Reboot required."||:

if [ -e "$SCMD" ];then
	snrefresh
fi
$LCMD "Detailed logfile is located at $DLOG."