#!/usr/bin/env bash # shellcheck disable=SC1091 # Ad-hoc script for just patching the system. # Environment variables: # RKHUNTER - will be set to 0 if rkhunter exists and this variable is not set. # Setting it to 1 will skip using rkhunter. # NEEDREST - will be set to 0 if needrestart exists and this variable is not set. # Setting it to 1 will skip using needrestart or any other helper # like dnf needs-restarting or zypper needs-rebooting / zypper ps. # os_patching made by albatrossflavour et al., binary: OSPBIN='/usr/local/bin/os_patching_fact_generation.sh' . /etc/os-release || exit 1 # Debian act as if ID_LIKE wasn't necessary if ID == ID_LIKE. Great job, guys. if [ "$ID" = "debian" ]; then ID_LIKE="debian" fi # 0. Internal helpers hline() { [ -n "$COLUMNS" ] && MYCOLS="$COLUMNS" [ -z "$MYCOLS" ] && MYCOLS="$(/usr/bin/tput cols 2>/dev/null)" [ -z "$MYCOLS" ] && MYCOLS=16 c=0 printf '\033[1m' while [ "$c" -lt "$MYCOLS" ]; do printf '─' c="$((c+1))" done printf '\033[0m\n' } dline() { [ -n "$COLUMNS" ] && MYCOLS="$COLUMNS" [ -z "$MYCOLS" ] && MYCOLS="$(/usr/bin/tput cols 2>/dev/null)" [ -z "$MYCOLS" ] && MYCOLS=16 c=0 while [ "$c" -lt "$MYCOLS" ]; do printf '┄' c="$((c+1))" done printf '\n' } header() { if [ -n "$1" ]; then hline printf ' \033[3m\033[1m%b\033[0m\n' "$1" hline fi } footer() { if [ -n "$1" ]; then dline printf '\033[3m\033[1m%b\033[0m\n' "$1" fi } # 1. Find out about auxiliary helpers like rkhunter declare NRSBIN RKHBIN [ -z "$RKHUNTER" ] && RKHUNTER=2 [ -z "$NEEDREST" ] && NEEDREST=2 if [ "$NEEDREST" -gt 1 ] ; then for bin in /usr/sbin/needrestart /usr/bin/needrestart; do if [ -x "$bin" ]; then NRSBIN="$bin" NEEDREST=0 break fi done fi if [ "$RKHUNTER" -gt 1 ] ; then for bin in /usr/bin/rkhunter /usr/sbin/rkhunter; do if [ -x "$bin" ]; then RKHBIN="$bin" RKHUNTER=0 break fi done fi # 2. Patching. case "$ID_LIKE" in "debian") APTBIN='/usr/bin/apt' APTOPTS=( '-o' 'Apt::Cmd::Disable-Script-Warning=true' '-o' 'Dpkg::Progress-Fancy=False' '-o' 'Apt::Color=False' '-o' 'Dpkg::Use-Pty=False' '-o' 'Quiet::NoUpdate=True' '-o' 'APT::Get::AutomaticRemove=False' '-o' 'APT::Get::AutomaticRemove::Kernels=False' '-o' 'APT::Get::Assume-Yes=True' ) if [ "$RKHUNTER" -eq 0 ]; then header 'Starting rkhunter check' "$RKHBIN" -c --sk || exit 120 fi # 2.1. Package list refresh header 'Starting package list update' "$APTBIN" "${APTOPTS[@]}" update || exit 110 ULIST="$("$APTBIN" "${APTOPTS[@]}" -q list --upgradable | grep -iP '^[0-9a-z_:\-+\.]+/.+' | sed 's/^\([^/]\+\).*/\1/')" # Only one update will be one update with or without line-break, and NO update will be also with or without line-break. # Solution: Always add a line-break, and grep away empty lines. UPDATENUM="$(printf '%b\n' "$ULIST" | grep -vcP '^$')" printf '\033[3m\033[1m%b update(s) found.\033[0m\n' "$UPDATENUM" # 2.2. Package update. # 2.2.1. No updates found? if [ "$UPDATENUM" -lt 1 ]; then printf '\033[3m\033[1m\033[2mSkipping updates.\033[0m\n' else # 2.2.2. Updates found? header 'Starting package updates' "$APTBIN" "${APTOPTS[@]}" full-upgrade || exit 112 header 'Starting package auto-removal' "$APTBIN" "${APTOPTS[@]}" --purge autoremove || exit 113 # 2.2.3. Package file index update if [ -x /usr/bin/apt-file ]; then printf 'Starting apt-file update' /usr/bin/apt-file "${APTOPTS[@]}" update || true fi if [ "$RKHUNTER" -eq 0 ]; then header 'Starting rkhunter update' "$RKHBIN" --propupd || exit 121 fi # 2.2.4. Requirement for reboot if [ "$NEEDREST" -eq 0 ]; then header 'Starting needrestart investigation' "$NRSBIN" -b # Outdated comment (kind of), see $NEEDREST at the top of the file -- # If we don't have needrestart, this will fail - which is OK, without # a means of controlling whether reboot is necessary we will reboot in any case. if ! "$NRSBIN" -p; then footer 'Outdated libraries or kernel found, rebooting' /usr/bin/systemctl reboot || reboot else if [ "$UPDATENUM" -gt 0 ]; then if [ -x "$OSPBIN" ]; then header 'Starting os_patching_fact_generation.sh' ospstart="$(/usr/bin/date '+%s')" "$OSPBIN" ospend="$(/usr/bin/date '+%s')" footer "...done ($((ospend - ospstart)) seconds)." fi fi fi elif [ "$NEEDREST" -gt 1 ]; then footer 'No needrestart found, rebooting' /usr/bin/systemctl reboot || reboot fi fi ;; "suse"*) # Caution: # 1. Broken package dependenciers will not be solved # 2. Orphaned packages will be kept in-place header 'Refreshing zypper "services"' mystart="$(/usr/bin/date '+%s')" /usr/bin/zypper -q --non-interactive refresh-services && printf 'OK.\n' || exit 110 myend="$(/usr/bin/date '+%s')" footer "...done ($((myend - mystart)) seconds)." header 'Refreshing repository cache' mystart="$(/usr/bin/date '+%s')" /usr/bin/zypper -q --non-interactive refresh && printf 'OK.\n' || exit 111 myend="$(/usr/bin/date '+%s')" footer "...done ($((myend - mystart)) seconds)." # TODO: no amount of "-q" keeps zypper from delivering the verbose list of updates before # installing them. If only the zypper guys were modern in thinking script or automation approaches... header 'Running update' mystart="$(/usr/bin/date '+%s')" /usr/bin/zypper -q --no-refresh --non-interactive-include-reboot-patches \ up -y --auto-agree-with-licenses --solver-focus 'Update' && printf 'OK.\n' || exit 112 myend="$(/usr/bin/date '+%s')" footer "...done ($((myend - mystart)) seconds)." header 'Running dist-upgrade' mystart="$(/usr/bin/date '+%s')" /usr/bin/zypper -q --no-refresh --non-interactive-include-reboot-patches \ dup -y --allow-name-change --allow-arch-change --allow-vendor-change --no-allow-downgrade \ --auto-agree-with-licenses --solver-focus 'Update' && printf 'OK.\n' || exit 113 myend="$(/usr/bin/date '+%s')" footer "...done ($((myend - mystart)) seconds)." # zypper: why deliver exit codes WHEN WE CAN TOSS EFFIN STRINGS AT THE CONSOLE ONLY # also... too many people rather tend to localise their systems....: export LANG=C # use "C" as safe haven, we DO NOT want this to fail # yes, in the very recent part they invented "needs-rebooting", but that only checks # core services and libraries... :( so we do both here. We want to be rather aggressive # on unattended updates in that we reboot if there are lingering "programs". Any of them. header 'Checking reboot requirement... ' if ! /usr/bin/zypper -q needs-rebooting; then # TODO: zypper being locked by another "application" delivers RC7 - is this reserved # exclusively for this case? footer 'Rebooting (zypper needs-rebooting)' /usr/bin/systemctl reboot elif [ "$(/usr/bin/zypper ps -sss 2>&1 | wc -l)" -gt 0 ]; then footer 'Rebooting (zypper ps)' /usr/bin/systemctl reboot else printf 'no reboot required.\n' if [ -x "$OSPBIN" ]; then header 'Starting os_patching_fact_generation.sh' ospstart="$(/usr/bin/date '+%s')" "$OSPBIN" ospend="$(/usr/bin/date '+%s')" footer "...done ($((ospend - ospstart)) seconds)." fi fi ;; "rhel"*|"centos"*) # we do not use --skip-broken here - we keep our systems tidy, so any pollution may and should # cause an error :-) header 'Starting package list update' mystart="$(/usr/bin/date '+%s')" /usr/bin/dnf -d1 makecache && printf 'OK.\n' || exit 110 myend="$(/usr/bin/date '+%s')" footer "...done ($((myend - mystart)) seconds)." header 'Starting package upgrade' mystart="$(/usr/bin/date '+%s')" /usr/bin/dnf --comment='os_patching_adhoc' -d1 --obsoletes --best -y upgrade &&\ printf 'OK.\n' || exit 111 myend="$(/usr/bin/date '+%s')" footer "...done ($((myend - mystart)) seconds)." header 'Checking reboot requirement' if ! /usr/bin/dnf -d1 needs-restarting -r; then footer 'Outdated libraries or kernel found, rebooting.' /usr/bin/systemctl reboot else if [ -x "$OSPBIN" ]; then header 'Starting os_patching_fact_generation.sh' ospstart="$(/usr/bin/date '+%s')" "$OSPBIN" ospend="$(/usr/bin/date '+%s')" footer "...done ($((ospend - ospstart)) seconds)." fi fi ;; esac