#!/bin/bash
#
# General update script for (open)SUSE hosts
#
# Copyright (C) 2008-2010, Novell, Inc.
# Copyright (C) 2011-2014, SUSE Linux Products GmbH
# Copyright (C) 2015-      SUSE Linux GmbH
# Copyright (C) 2021-      SUSE Software Solutions Germany GmbH
# Author: Lars Vogdt
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# * Neither the name of the Novell nor the names of its contributors may be
#   used to endorse or promote products derived from this software without
#   specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#

PATH=/bin:/usr/bin:/sbin:/usr/sbin
umask 027
DISTCONFIG='/etc/sysconfig/suse-online-update'
##################################################
# Default/Fallback values
# Don't change them here! Use $DISCONFIG instead
##################################################
START_UPDATE='yes'
LOGFILE='/var/log/systemupdate.log'
EMAIL="root@localhost"
FIX_PERMISSIONS='yes'
ZYPPER_UPDATE_TYPE='patch'
RESTART_SERVICES='no'
INCLUDE_OPTIONAL='no'
IGNORE_SERVICES_FROM_RESTART='dbus udev'
##################################################
LOGDIR=$(dirname "$LOGFILE")
LOGNAME='suse-online-update'
HOST=$(hostname -s)
FQHOSTNAME=$(hostname -f)
RUN_CLEANUP='yes'
CLEANUP_USER=''
DEBUG='no'
ZYPPER='/usr/bin/zypper'
ZYPPER_PS_RESTART='/usr/sbin/zypper_ps_restart'

function cleanup(){
    find /tmp -maxdepth 1 -name "zypp-online-update1-*" -size 0 -type f -user root -delete 2>/dev/null
    find /var/tmp/ -maxdepth 1 -type d -name "TmpDir.*" $CLEAN_USR -exec rm -rf {} \; 2>/dev/null
    find /var/tmp/ -maxdepth 1 -type d -name "zypp.*"   $CLEAN_USR -exec rm -rf {} \; 2>/dev/null
}

function cleanup_and_exit(){
    test -n "$TMPFILE" -a -f "$TMPFILE" && rm "$TMPFILE"
    test -n "$TMPLOG" -a -f "$TMPLOG" && rm "$TMPLOG"
    case "$RUN_CLEANUP" in
        [yY]*) cleanup
        ;;
    esac
    exit $1
}
trap cleanup_and_exit 0 1 2 3 7 13 15

function LOG(){
    local MESSAGE="$1"
    local LOG_DATE=$(date "+%b %d %H:%M:%S")
    echo "$LOG_DATE $HOST $LOGNAME[$$]: $MESSAGE" >> $LOGFILE
    if [ "$DEBUG" = "yes" ]; then
        echo "DEBUG:    $MESSAGE"
    fi
}

function usage(){
    echo "Usage: $0 [OPTION]"
    echo
    echo "       -d             : print debug output on console (default: $DEBUG)"
    echo "       -r             : restart services (default: $RESTART_SERVICES)"
    echo "       -o             : install optional updates (default: $INCLUDE_OPTIONAL)"
    echo "       -t <type>      : use 'type' for $ZYPPER update command (default: $ZYPPER_UPDATE_TYPE)"
    echo "                        supported types: patch, package, pattern, product, srcpackage"
    echo "       -e <email>     : send error messages to this Email address (default: $EMAIL)"
    echo "       -f <sysconfig> : use <sysconfig> as configuration file (default: $DISTCONFIG)"
    echo "       -l <logfile>   : use <logfile> as log file (default: $LOGFILE)"
    echo "       -c             : run cleanup (default: $RUN_CLEANUP)"
    echo "       -p             : run the permission scripts (like 'chkstat') after update (default: $FIX_PERMISSIONS)"
    echo "       -h             : print this help"
    echo
}

while getopts 'de:f:l:cphrot:' OPT; do
    case "$OPT" in
        d) DEBUG='yes'
        ;;
        e) EMAIL="$OPTARG"
        ;;
        f) DISTCONFIG="$OPTARG"
        ;;
        l) LOGFILE="$OPTARG"
        ;;
        c) RUN_CLEANUP='yes'
        ;;
        p) FIX_PERMISSIONS='yes'
        ;;
        r) RESTART_SERVICES='yes'
        ;;
        o) INCLUDE_OPTIONAL='yes'
        ;;
        t) ZYPPER_UPDATE_TYPE="$OPTARG"
        ;;
        h) usage; exit 0;
        ;;
    esac
done
shift $(( OPTIND - 1 ))

# source our config
if [ -f "$DISTCONFIG" ]; then
    . "$DISTCONFIG"
else
    echo "$DISTCONFIG not found - using defaults" >&2
    if [ -w "$LOGFILE" ]; then LOG "$DISTCONFIG not found - using defaults"; fi
fi
if [ -z "$LOGFILE" ]; then
    echo "ERROR: LOGFILE is not defined" | $MAILX -s "[$LOGNAME] error on $FQHOSTNAME" $EMAIL
    cleanup_and_exit 1
fi
if [ ! -d "$LOGDIR" ]; then
    mkdir -p "$LOGDIR" || exit 1
    echo "$LOG_DATE $HOST $LOGNAME[$$]: created $LOGDIR" > "$LOGFILE"
fi
touch "$LOGFILE" || { echo "Could not create $LOGFILE"; cleanup_and_exit 1; }
if [ ! -w "$LOGFILE" ]; then
    echo "Could not write to $LOGFILE - please fix" >&2
    cleanup_and_exit 1
fi
if [ -n "$CLEANUP_USER" ]; then
    CLEAN_USR=''
else
    CLEAN_USR="-user $CLEANUP_USER"
fi
#
# Start script
#
LOG "suse-online-update started"

#
# zypper up
#
case "$START_UPDATE" in
    [yY]*)
        TMPFILE=$(mktemp /tmp/zypp-online-update1-XXXXXX)
        TMPLOG=$(mktemp /tmp/zypp-online-update2-XXXXXX)
        MESSAGE='OK'
        LOG "Starting online-update"
        if [ -x "$ZYPPER" ]; then # SLES10 SP2
            shopt -s nocasematch
            case "$ZYPPER_UPDATE_TYPE" in
                'patch')
                    UPDATETYPE='patch'
                ;;
                'package')
                    UPDATETYPE='package'
                ;;
                'pattern')
                    UPDATETYPE='pattern'
                ;;
                'product')
                    UPDATETYPE='product'
                ;;
                'srcpackage')
                    UPDATETYPE='srcpackage'
                ;;
                *)
                    UPDATETYPE='patch'
                    LOG "ERROR: Please set a correct value for ZYPPER_UPDATE_TYPE in $DISTCONFIG. Found: $ZYPPER_UPDATE_TYPE; using 'patch' for now"
                ;;
            esac
            OPTIONAL=""
            if [ x"$INCLUDE_OPTIONAL" == x"yes" ] && [ x"$ZYPPER_UPDATE_TYPE" != x"package" ]; then
                OPTIONAL="--with-optional"
            fi
            if [ "$UPDATETYPE" == "patch" ] && [ "$OPTIONAL" == "--with-optional" ] ; then
                $ZYPPER --non-interactive --terse $UPDATETYPE --no-confirm $OPTIONAL --skip-interactive 2>"$TMPFILE" 1>/dev/null
            else
                $ZYPPER --non-interactive --terse up --no-confirm -t $UPDATETYPE $OPTIONAL --skip-interactive 2>"$TMPFILE" 1>/dev/null
            fi
            ZYPP_RET=$(echo $?)
            case "$ZYPP_RET" in
                0) MESSAGE='OK'
                ;;
                1) MESSAGE="ERROR: $ZYPPER returned with error code 1 - please see 'man 8 zypper'"
                ;;
                6) MESSAGE='ERROR: no update repositories defined'
                ;;
                7) MESSAGE='ERROR: the ZYPP library is locked - could not execute $ZYPPER'
                ;;
                102) MESSAGE='WARNING: one of the installed packages require a reboot of the machine'
                ;;
                103) LOG 'WARNING: one of the installed packages affects the package manager itself - calling myself again...'
                     $0
                     cleanup_and_exit 0 # just to be sure
                ;;
                106) MESSAGE='WARNING: some repositories have been skipped because of an error - please investigate'
                ;;
                *) MESSAGE="WARNING: $ZYPPER command exited with exit code: $ZYPP_RET - please check logs and 'man 8 zypper'"
                ;;
            esac
            grep -v ^Restoring "$TMPFILE" | grep -v ^Loading | grep -v ^Parsing | grep -v ^Summary: | grep -v ^Nothing > "$TMPLOG"
        elif [ -x /usr/bin/online_update ]; then # old SLES9 host
            # looks like SLES 9 has no option to skip interactive patches
            # so normally, we run only a check and don't install anything.
            # If you like to change this, remove the "--dry-run" option.
            online_update --check --dry-run >&2 >"$TMPFILE"
            grep -v "^No updates" "$TMPFILE" > "$TMPLOG"
        else
            LOG "Binary for installing updates not found"
            echo "Binary for installing updates not found" >&2
            cleanup_and_exit 1
        fi
        if [ -s "$TMPLOG" ]; then
            cat "$TMPLOG" >> "$LOGFILE"
        else
            LOG "Finished update - $MESSAGE"
        fi
    ;;
esac
#
# chkstat --system --set
#
case "$FIX_PERMISSIONS" in
    [yY]*)
    if [ -x /usr/bin/chkstat ]; then
        LOG "Running /usr/bin/chkstat --system --set"
        /usr/bin/chkstat --system --set >/dev/null 2>>"$LOGFILE"
    elif [ -x /sbin/SuSEconfig ]; then
        LOG "Running /sbin/SuSEconfig --module permissions"
        /sbin/SuSEconfig --module permissions >/dev/null 2>>"$LOGFILE"
    else
        LOG "Could not check/set permissions - binary not found"
        echo "Could not check/set permissions - binary not found" >&2
        cleanup_and_exit 1
    fi
esac
#
# systemctl try-restart service
#
case "$RESTART_SERVICES" in 
    [yY]*)
		ZYP_PS_DEBUG=""
		if [ "$DEBUG" = "yes" ]; then
			ZYP_PS_DEBUG="-d"
		fi
		if [ -x "$ZYPPER_PS_RESTART" ]; then
			$ZYPPER_PS_RESTART -f "$DISTCONFIG" -l "$LOGFILE" $ZYP_PS_DEBUG
		else
			LOG  "Could not execute $ZYPPER_PS_RESTART - please fix"
			echo "Could not execute $ZYPPER_PS_RESTART - please fix" >&2
		fi
    ;;
esac
#
# Stop script
#
LOG "suse-online-update finished"
cleanup_and_exit 0
