#!/bin/bash
#
# Copyright (C) 2020-2024 Thorsten Kukuk <kukuk@thkukuk.de>
#
# This program 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 2 of the License, or
# (at your option) any later version.
#
# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.

export LANG=C.UTF-8

# Default variables
PKG_MANAGER="zypper"
UPDATE_CMD="auto"
REBOOT_CMD="auto"
REBOOT_METHOD="reboot"
RESTART_SERVICES="yes"
IGNORE_SERVICES_FROM_RESTART="dbus virtlockd"
SERVICES_TRIGGERING_REBOOT=""
SERVICES_TRIGGERING_SOFT_REBOOT="dbus"
RETVAL=0
LOG_TAG="os-update"

# Additional variables per package manager
ZYPPER_NONINTERACTIVE="-y --auto-agree-with-product-licenses"


log_error()
{
    echo "$@" >&2
    local LOG_ARGS="--priority user.err"
    if [ -n "$LOG_TAG" ]; then
        LOG_ARGS="$LOG_ARGS -t $LOG_TAG"
    fi
    logger ${LOG_ARGS} "$@"
}

log_info()
{
    echo "$@"
    local LOG_ARGS=""
    if [ -n "$LOG_TAG" ]; then
        LOG_ARGS="$LOG_ARGS -t $LOG_TAG"
    fi
    logger ${LOG_ARGS} "$@"
}

usage() {
    echo "Syntax: os-update"
    echo ""
    echo "Update the system with automatic reboot if required."
    echo ""
    echo "Options:"
    echo "--help, -h                 Display this help and exit"
    echo "--version                  Display version and exit"
}

print_version() {
    echo "os-update 1.17"
}

check_and_reboot() {
    if [ -x /usr/bin/needs-restarting ]; then
        needs-restarting -r
	if [ $? -eq 1 ] ; then
	    reboottrigger="yes"
	    if [ -f "/run/reboot-needed" ] && [ -s "/run/reboot-needed" ]; then
		case $(cat /run/reboot-needed) in
		    "soft-reboot")
			log_info "Update suggests reboot with soft-reboot"
			REBOOT_METHOD="soft-reboot"
			;;
		    *)
			log_info "Update suggests reboot with $(cat /run/reboot-needed), using hard reboot"
			REBOOT_METHOD="reboot"
			;;
		esac
	    else
		log_info "Update suggests reboot"
		REBOOT_METHOD="reboot"
	    fi
	fi
    else
        release=$(uname -r)
        flavor=${release##*-}
        version=${release%-*}
        for rpm in $(rpm -q kernel-${flavor}) ; do
            installed=${rpm#kernel-${flavor}-}
            if [[ ${installed} > ${version}.1.$(uname -m) ]] ; then
		log_info "New kernel detected, trigger reboot"
                reboottrigger="yes"
		REBOOT_METHOD="reboot"
            fi
        done
    fi
    if [ "$reboottrigger" = "yes" ]; then
	case "$REBOOT_CMD" in
	    "auto")
		if [ -x /usr/sbin/rebootmgrctl ]; then
		    if /usr/sbin/rebootmgrctl is-active -q; then
			log_info "Trigger reboot with rebootmgrctl $REBOOT_METHOD"
			/usr/sbin/rebootmgrctl $REBOOT_METHOD
		    else
			log_info "Trigger reboot with systemctl $REBOOT_METHOD"
			systemctl $REBOOT_METHOD
		    fi
		else
		    log_info "Trigger reboot with systemctl $REBOOT_METHOD"
		    systemctl $REBOOT_METHOD
		fi
		;;
	    "rebootmgr")
		if [ ! -x /usr/sbin/rebootmgrctl ]; then
		    log_error "ERROR: rebootmgrctl not installed"
		    exit 1
		fi
		if ! /usr/sbin/rebootmgrctl is-active -q; then
		    log_error "ERROR: rebootmgrd not running"
		    exit 1
		fi
		log_info "Trigger reboot with rebootmgrctl $REBOOT_METHOD"
		/usr/sbin/rebootmgrctl $REBOOT_METHOD
		;;
	    "reboot")
		log_info "Trigger reboot with systemctl $REBOOT_METHOD"
		systemctl $REBOOT_METHOD
		;;
	    "no"|"none")
		log_info "A $REBOOT_METHOD is required"
		;;
	esac
    fi
}

eval_zypper_retval() {
    RETVAL=$1

    if [ "$RETVAL" -eq 0 ] || [ "$RETVAL" -eq 102 ] || [ "$RETVAL" -eq 103 ]; then
	log_info "Update was successful"
    else
	log_error "ERROR: update failed, exit code was ${RETVAL}"
	if [ "$RETVAL" -ne 106 ]; then
	    exit 1
	fi
    fi
}

zypper_security() {
    zypper patch ${ZYPPER_NONINTERACTIVE} --category security
    eval_zypper_retval $?
}

zypper_up() {
    zypper up ${ZYPPER_NONINTERACTIVE}
    eval_zypper_retval $?
}

zypper_dup() {
    zypper dup ${ZYPPER_NONINTERACTIVE}
    eval_zypper_retval $?
}

zypper_restart_services() {
    if [[ $(lsof -w -a -d DEL -p 1 -Ff  | grep -c "^fDEL") -gt 0 ]]; then
        log_info "Reexecing systemd"
        systemctl daemon-reexec;
    fi
    reboottrigger="no"
    for service in $(zypper ps --print %s); do
        for rebootservice in $SERVICES_TRIGGERING_SOFT_REBOOT; do
            if [ "$rebootservice" = "$service" ]; then
                log_info "$service triggers a soft-reboot as it is included in SERVICES_TRIGGERING_SOFT_REBOOT"
		reboottrigger="yes"
		REBOOT_METHOD="soft-reboot"
		break
	    fi
	done
        for rebootservice in $SERVICES_TRIGGERING_REBOOT; do
            if [ "$rebootservice" = "$service" ]; then
                log_info "$service triggers a reboot as it is included in SERVICES_TRIGGERING_REBOOT"
		reboottrigger="yes"
		REBOOT_METHOD="reboot"
		break
	    fi
	done
        for ignore in $IGNORE_SERVICES_FROM_RESTART; do
            if [ "$ignore" = "$service" ]; then
                log_info "Not restarting $service as it is excluded in IGNORE_SERVICES_FROM_RESTART"
		break
	    fi
	done
        if [ "$ignore" != "$service" ] && [ "$service" != "os-update" ]; then
	    log_info "Restarting $service"
            systemctl try-restart "$service".service
	fi
    done
}

run_zypper() {

    case "$UPDATE_CMD" in
	auto)
	    . /etc/os-release
	    case "$NAME" in
		"SLES"|"openSUSE Leap")
		    zypper_up
		    ;;
		"openSUSE Tumbleweed")
		    zypper_dup
		    ;;
		*)
		    log_error "ERROR: your OS is not supported in 'auto' mode"
		    ;;
	    esac
	    ;;
	dup)
	    zypper_dup
	    ;;
	up)
	    zypper_up
	    ;;
	security)
	    zypper_security
	    ;;
	*)
	    log_error "ERROR: UPDATE_CMD ($UPDATE_CMD) unknown"
	    exit 1
	    ;;
    esac
}

#
# Main
#

while true; do
    if [ $# -eq 0 ]; then
        break
    fi

    case "$1" in
	-h|--help)
            usage
	    exit 0
            ;;
        --version)
            print_version
	    exit 0
            ;;
        *)
            usage
	    exit 1
            ;;
    esac
done

if [ $# -ne 0 ]; then
    usage
    exit 1
fi

# Read configuration variables

# Overwrite with vendor specific values if exist:
if [ -e /usr/etc/os-update.conf ]; then
  . /usr/etc/os-update.conf
fi
# Overwrite with admin specific values if exist:
if [ -e /etc/os-update.conf ]; then
  . /etc/os-update.conf
fi

run_${PKG_MANAGER}

case "$RESTART_SERVICES" in
    [yY]*)
	zypper_restart_services
	;;
esac

check_and_reboot

exit $RETVAL
