#!/bin/sh -e

##########################################################################
#   Synopsis:
#       auto-update-system [ --sync-pkg-cache user@host ] \\
#       [ --pause ] \\
#       [ --binary | --binary+reboot | --defaults | --defaults+reboot | --yes ]
#       
#   Description:
#       auto-update-system performs a comprehensive system update
#       including installed binary packages, a ports or pkgsrc tree if
#       one is found, and the base system if such a thing exists on the
#       platform (on RHEL, there is no base system separate from Yum
#       packages).
#
#       CAUTION: auto-update-system should not be run in parallel on
#       many systems, as this will swamp the network and package servers
#       with redundand downloads.  Either run updates serially, using
#       --sync-pkg-cache for all but the first system, or configure a
#       package mirror or proxy server.
#
#       On BSD systems, auto-pkgsrc-setup ensures that the binary package
#       repository and ports/pkgsrc tree are on the same branch.  If they
#       are not, auto-update-system offers to update the ports/pkgsrc
#       tree accordingly.
#
#       On RHEL/CentOS, auto-update-system runs yum-complete-transaction
#       before updates, as the system may frequently be left in an
#       inconsistent state due to a previous failed update.
#
#       If no flags are provided, auto-update-system prompts the user
#       before updating each component.
#
#       Binary packages can be pulled from another local system using the
#       --sync-pkg-cache user@host option to reduce downloads from primary
#       package servers.  The option uses rsync over ssh.
#       This both reduces needless Internet load and probably
#       speeds up the update process significantly for systems with similar
#       installed packages.  Local network speeds may approach 100 MB/s
#       for a typical gigabit LAN while downloads from primary servers
#       probably won't exceed a few MB/s.
#
#       If the user@host spec is stored in
#       $PREFIX/etc/auto-admin/pkg-cache-host, it will be automatically
#       used for all future updates.
#
#       Following a successful update, the current time is stored in
#       $PREFIX/etc/auto-admin/last-system-update.  The auto-last-update
#       command reads this file and reports the number of hourse since the
#       last update.
#
#       Packages listed in $PREFIX/etc/auto-admin/critical packages
#       (1 per line) must be available on the package server in order for
#       updates to proceed automatically.  If any package in this list is
#       not available, the user is warned and offered the option to
#       proceed anyway.  Packages can be added to this list using
#       auto-mark-package-critical(1).
#       Package names are the same as those used in a
#       package install command, minus any version information.  This
#       system protects against situations where a package is temporarily
#       unavailable due to a regression.  This is rare in most package
#       managers, but does happen occasionally in the latest package
#       repositories.  Such issues are usually resolved within a few days,
#       so the most common response to such a warning is to simply wait a
#       day or so and try again.  Using quarterly packages should
#       eliminate any possibility of this happening.
#
#       Ports or pkgsrc packages listed as category/name (1 per line)
#       $PREFIX/etc/auto-admin/install-from-source will be rebuilt and
#       reinstalled from source following successful binary packages and
#       port/pkgsrc tree updates.  Ports/packages can be added to the list
#       using auto-mark-install-from-source(1).  This can be useful for updating
#       ports/packages that cannot be redistributed in binary form
#       for licesning reasons, work-in-progress ports/packages, and
#       ports/packages with non-default build options such as non-portable
#       optimizations to capitalize on AVX and the like.
#
#       This system should only be used if your
#       installed packages and ports tree are well-synchronized, which is
#       usually the case if you use auto-update-system for all your updates.
#       If you use quarterly packages and ports/pkgsrc, there should be no
#       issues at all, since only minor bug fixes are permitted to the
#       quarterly branches of ports and pkgsrc, and API changes are
#       all but forbidden.  This system is also safe to use for
#       latest/current ports/packages as long as package builds are done
#       regularly.  This is the case for FreeBSD ports on amd64, where
#       changes to the ports tree are generally reflected in the official
#       binary packages within a few days.  However, on other architectures,
#       official packages may not be updated frequently, so the ports tree
#       may contain much newer versions of some software, including API
#       differences that could break dependencies.
#
#       If $PREFIX/etc/auto-admin/auto-update-system-post-ports exists and
#       is secure (owned by root/wheel and not writable to anyone else)
#       it will be executed following successful updates of binary packages
#       and the ports/pkgsrc tree.  This should only be used as a last
#       option for customizations that can't be performed by
#       install-from-source.
#
#   Arguments:
#       --sync-pkg-cache user@host  Sync package cache from host
#       --pause                     Prompt user before exit
#       --binary                    Binary packages and base only
#       --binary+reboot             Same as above + auto reboot
#       --defaults                  All components that can be easily updated
#       --defaults+reboot           Same as above + auto reboot
#       --yes                       Synonymous with --defaults+reboot
#
#   Returns:
#       0 upon successful update, non-zero otherwise
#
#   Examples:
#       auto-update-system --defaults
#
#   Files:
#       $PREFIX/etc/auto-admin/pkg-cache-host
#       $PREFIX/etc/auto-admin/last-system-update
#       $PREFIX/etc/auto-admin/critical-packages
#       $PREFIX/etc/auto-admin/install-from-source
#       $PREFIX/etc/auto-admin/auto-update-system-post-ports
#
#   Environment:
#       PORTSDIR, PKGSRC
#
#   See also:
#       auto-mark-install-from-source(1), auto-mark-package-critical(1)
#       
#   History:
#   Date        Name        Modification
#   2016-03-11  J Bacon     Begin
##########################################################################

usage()
{
    cat << EOM

Usage: $0 [--sync-pkg-cache user@host] [--pause] [--binary | --binary+reboot | --defaults | --defaults+reboot | --yes]

user@host is used to rsync packages from another host that has been updated
more recently than this one.  This reduces load on the primary package servers
and may significantly speed up package upgrades.  The other host must have the
same architecture and OS version as this one.  user@host can also be
permanently stored in $(auto-localbase)/etc/auto-admin/pkg-cache-host.

EOM
    exit 1
}


##########################################################################
#   Function description:
#       Pause until user presses return
##########################################################################

pause()
{
    local junk
    
    printf "Press return to continue..."
    read junk
}


abuse_warning()
{
    cat << EOM
****************************************************************************

    Running auto-update-system non-interactively can overload the update
    servers if done in parallel.
    
    Run only one instance at a time.
    
    DO NOT ABUSE THIS FEATURE!!

****************************************************************************
EOM
    return 0
}


##########################################################################
#   History:
#   Date        Name        Modification
#   2024-11-06  Jason Bacon Begin
##########################################################################

reboot_advice()
{
    printf "No kernel componented updated.  Reboot probably not necessary.\n"
    printf "It may be wise to reboot anyway, or at least restart X11\Wayland.\n"
    printf "Updates to Xorg/Wayland may require them to be restarted to avoid problems.\n"
    return 0
}


##########################################################################
#   Function description:
#       Record the last time of a full system update in hours since
#       the epoch
#       
#   History:
#   Date        Name        Modification
#   2021-06-13  J Bacon     Begin
##########################################################################

record_update_time()
{
    current_time=$(date '+%s')
    current_time=$((current_time / 3600))
    config_dir=$(auto-localbase)/etc/auto-admin
    mkdir -p $config_dir
    printf "$current_time\n" > $config_dir/last-system-update
}


##########################################################################
#   Function description:
#       Remove unneeded dependencies
#
#   2018-01-13  Jason Bacon Begin
##########################################################################

freebsd_autoremove()
{
    if [ 0$autoremove != 0n ]; then
	printf "\nRemoving unneeded dependencies...\n"
	case $mode in
	interactive)
	    pkg autoremove
	    ;;
	*)
	    pkg autoremove -y
	    ;;
	esac
    fi
}


ifs_corrupt()
{
    cat << EOM

****************************************************************************
Error: $INSTALL_FROM_SOURCE is corrupt.

Each line must contain the following three columns:

category/port  reason-for-installing-from-source  newer|always

A 4th column indicating the port flavor is optional.
****************************************************************************

EOM
    exit 1
}


ifs_update()
{
    awk '{ if ( NF == 2 ) print $0 " newer"; else print $0; }' \
	$INSTALL_FROM_SOURCE > $INSTALL_FROM_SOURCE.new
    mv -f $INSTALL_FROM_SOURCE.new $INSTALL_FROM_SOURCE
}


##########################################################################
#   Main
##########################################################################

case $(auto-ostype) in
FreeBSD|OpenBSD)
    : ${PORTSDIR:=/usr/ports}
    ;;

DragonFly)
    : ${PORTSDIR:=/usr/dports}
    ;;

esac
export PORTSDIR

os_type=`auto-ostype`

if [ 0"$1" = 0--help ]; then
    usage
fi

# Prevent user from running a Trojan as root in the case their account
# was compromised
absolute="$(which $0)"
# Don't count on -e being set at this point
if ! auto-file-secure "$absolute"; then
    exit 1
fi

if ! auto-root-check $0; then
    printf "Root "
    # exec quotes '$absolute --flag', causing usage error
    # Assigning to cmd works around the problem
    cmd="$absolute $@"
    while ! su -m root -c "$cmd"; do
	printf "Command failed, possibly due to an incorrect password.  Try again? [y]/n "
	read again
	if [ 0"$again" = 0n ]; then
	    exit 0
	fi
    done
    exit 0
fi

cat << EOM

***********************************************************************
* Warning: Updating a live system can cause running processes to fail *
* when program components are updated.  Refrain from running programs *
* until this update is complete.  It is best to log out of graphical  *
* desktop sessions and run the update from a text console.            *
*                                                                     *
* After the update, auto-update-system will attempt to restart all    *
* running services.  However, it's best to double check any critical  *
* services to make sure they will continue to function properly.      *
*                                                                     *
* Reboot if the kernel or kernel modules have been updated.           *
***********************************************************************

EOM
sleep 10

# Check this first and allow --sync-pkg-cache to override
if [ -e $(auto-localbase)/etc/auto-admin/pkg-cache-host ]; then
    sync_pkg_cache=1
    pkg_cache_host=$(cat $(auto-localbase)/etc/auto-admin/pkg-cache-host)
fi

while [ 0"$1" = 0--sync-pkg-cache ] || [ 0"$1" = 0--pause ]; do
    if [ 0"$1" = 0--sync-pkg-cache ]; then
	if [ $# -ge 2 ]; then
	    sync_pkg_cache=1
	    pkg_cache_host=$2
	    if echo $pkg_cache_host | fgrep -q '@'; then
		shift
		shift
	    else
		usage
	    fi
	else
	    usage
	fi
    fi
    
    if [ 0"$1" = 0--pause ]; then
	pause=yes
	shift
    fi
done

if [ -n "$pkg_cache_host" ]; then
    case $os_type in
    FreeBSD|DragonFly)
	pkg_cache=/var/cache/pkg
	;;
    Darwin|NetBSD)
	# FIXME: This depends on the bootstrap options
	pkg_cache=/var/db/pkgin/cache
	;;
    OpenBSD)
	pkg_cache=/var/db/pkg
	;;
    RHEL)
	pkg_cache=/var/cache/yum
	;;
    *)
	auto-unsupported-os $0
	exit 1
	;;
    esac
    printf "Syncing from $pkg_cache_host...\n"
    while ! ssh -o ConnectTimeOut=2 $pkg_cache_host ls > /dev/null; do
	printf "pkgcache host ($pkg_cache_host) is not responding.\n"
	printf "Will retry in 10 seconds.\n"
	sleep 10
    done
    rsync -av --timeout=5 ${pkg_cache_host}:$pkg_cache/ $pkg_cache || true
fi

mode=interactive    # Default
if [ $# = 1 ]; then
    if [ "$1" = --binary ]; then
	mode=binary
    elif [ "$1" = --binary+reboot ]; then
	mode=binary+reboot
    elif [ "$1" = --defaults+reboot ] || [ "$1" = --yes ]; then
	mode=defaults+reboot
    elif [ "$1" = --defaults ]; then
	mode=defaults
    else
	usage
    fi
fi

##########################################################################
#   Reboot will only occur if needed, e.g. if freebsd-update updated
#   something in /boot.  --yes and --defaults+reboot are conditioned
#   on this.
##########################################################################

case $mode in
binary|defaults)
    reboot_requested=n
    ;;
binary+reboot|defaults+reboot)
    reboot_requested=y
    ;;
interactive)
    printf "Reboot after updates? y/[n] "
    read reboot_requested
    ;;
esac

case $os_type in
FreeBSD|DragonFly)
    if [ ! -t 0 ]; then
	# Input redirected.  Being fed y/n responses.
	portsnap_flags=--interactive
	freebsd_update_flags=--not-running-from-cron
	abuse_warning
    fi
    
    readonly INSTALL_FROM_SOURCE=$(auto-localbase)/etc/auto-admin/install-from-source
    while read line; do
	port=$(echo $line | awk '{ print $1 }')
	cols=$(echo $line | wc -w)
	if [ $cols -lt 3 ]; then
	    ifs_update
	    break
	fi
    done < $INSTALL_FROM_SOURCE
    
    # Don't group questions: Need to pause after each step to view output
    case $mode in
    binary|binary+reboot|defaults|defaults+reboot)
	update_packages=y
	abuse_warning
	;;
    interactive)
	printf "Update installed packages? [y]/n "
	read update_packages
	;;
    esac
    
    if [ 0$update_packages != 0n ]; then
	critical_pkg_file=$(auto-localbase)/etc/auto-admin/critical-packages
	if [ -e $critical_pkg_file ]; then
	    printf "Verifying availability of critical packages...\n"
	    printf "Use auto-mark-package-critical to update the list\n"
	    printf "or edit $critical_pkg_file.\n"
	    missing_packages=0
	    for pkg in $(cat $critical_pkg_file); do
		if pkg info -q $pkg; then
		    if pkg search "^${pkg}-[0-9].*" > /dev/null; then
			printf "$pkg is available.\n"
		    else
			printf "$pkg is installed and marked critical, but missing from the repository.\n"
			missing_packages=$(($missing_packages + 1))
		    fi
		fi
	    done
	    if [ $missing_packages -gt 0 ]; then
		cat << EOM

			       *** WARNING ***

Upgrading may remove the missing critical packages list above.
Check https://www.freebsd.org/support/bugreports/ for PRs related to the
packages, or try again later.  Most problems are fixed within a few days.

EOM
		if [ -t 1 ]; then
		    printf "Continue with upgrades anyway? y/[n] "
		    read upgrade_anyway
		    if [ 0$upgrade_anyway != 0y ]; then
			exit
		    fi
		else
		    printf "stdout is not a terminal. Can't ask user what to do, so aborting.\n"
		    exit 1
		fi
	    fi
	fi
	case $mode in
	interactive)
	    printf "Autoremove unneeded packages? [y]/n "
	    read autoremove
	    ;;
	*)
	    autoremove=y
	    ;;
	esac
	
	freebsd_autoremove

	if [ -e $INSTALL_FROM_SOURCE ]; then
	    printf "Cataloging install-from-source versions before upgrade...\n"
	    installed_versions_db=$(auto-localbase)/etc/auto-admin/old-pkg-versions
	    mkdir -p $(auto-localbase)/etc/auto-admin/
	    rm -f $installed_versions_db
	    while read line; do
		port=$(echo $line | awk '{ print $1 }')
		cols=$(echo $line | wc -w)
		when=$(echo $line | awk '{ print $3 }')
		if [ 0"$when" != 0newer ] && [ 0"$when" != 0always ]; then
		    ifs_corrupt
		fi
		if [ $cols = 4 ]; then
		    flavor="$(echo $line | awk '{ print $4 }')"
		    pkgbase=$(auto-print-make-variable --flavor $flavor $port PKGBASE) || true
		elif [ $cols = 3 ]; then
		    unset flavor
		    pkgbase=$(auto-print-make-variable $port PKGBASE) || true
		else
		    ifs_corrupt
		fi
		installed_version=$(pkg query '%v' $pkgbase) || true
		printf "$pkgbase $installed_version\n" >> $installed_versions_db
	    done < $INSTALL_FROM_SOURCE
	fi
	
	auto_admin_old_version=$(pkg query '%v' auto-admin)
	printf "\n**** Updating installed packages...\n"
	pkg update
	pkg upgrade -y
	freebsd_autoremove
	pkg clean -y
	auto_admin_new_version=$(pkg query '%v' auto-admin)
	
	##########################################################################
	#   If auto-admin has been upgraded, restart auto-update-system
	#   to ensure use of the latest fixes
	##########################################################################
	
	if [ $auto_admin_new_version != $auto_admin_old_version ]; then
	    printf "Restarting with newer version of auto-update-system...\n"
	    exec auto-update-system "$@"
	fi
    fi
    
    case $mode in
    defaults|defaults+reboot)
	update_ports=y
	portsnap_flags=--interactive
	;;
    binary|binary+reboot)
	update_ports=n
	;;
    interactive)
	printf "Update ports tree? [y]/n "
	read update_ports
	;;
    esac
    
    if [ 0$update_ports != 0n ]; then
	printf "\n**** Updating $PORTSDIR...\n"
	auto-check-ports-branch || true
	# Was CHANGES for FreeBSD.  Is Makefile just as good?
	if [ -e $PORTSDIR/Makefile ]; then
	    if [ -e $PORTSDIR/.git ]; then
		save_cwd=$(pwd)
		cd $PORTSDIR
		stash_file=Mk/bsd.local.mk
		if ! git diff --quiet $stash_file; then
		    stash=1
		fi
		if [ 0$stash = 01 ]; then
		    printf "Stashing bsd.local.mk...\n"
		    git stash push Mk/bsd.local.mk || true
		fi
		
		printf "Pulling...\n"
		git pull

		if [ 0$stash = 01 ]; then
		    printf "Unstashing...\n"
		    git stash pop || true   # Returns non-zero if nothing stashed
		fi
		cd $save_cwd
	    else
		cat << EOM

$PORTSDIR is not a git clone. auto-check-ports-branch appears to have
failed or was not allowed to replace an outdated ports tree.
Your ports tree must be a git clone in order to receive updates.
Aborting...

EOM
		exit 1
	    fi
	else
	    auto-ports-checkout
	fi

	if which wip-update; then
	    wip-update
	fi
    fi

    # In rare circumstances, the binary pkg package can be incompatible
    # with the latest ports tree.  The pkg port should probably get
    # special attention and trigger a new package build immediately
    # following commits, but until then...
    # PKGVERSION variable includes PORTREVISION (e.g. _1), not reported
    # by pkg -v, so use pkg query '%v' for comparison.
    if [ $(pkg query '%v' pkg) != $(auto-print-make-variable ports-mgmt/pkg PKGVERSION) ]; then
	printf "pkg version does not match ports.  Rebuilding from source...\n"
	(cd $PORTSDIR/ports-mgmt/pkg && make -DBATCH clean stage \
	    && make -DBATCH deinstall reinstall)
    else
	printf "Binary pkg is up-to-date.\n"
    fi
    
    # If binary packages were updated, also update ports listed in
    # install-from-source.  Do this whether or not ports tree was updated
    # here since it may have been updated by other means.  E.g. SPCM
    # updates binary packages and rsyncs ports tree from the head node
    # to other nodes.
    if [ 0$update_packages != 0n ]; then
	# Process ports marked by auto-mark-install-from-source
	printf "Checking for ports to be installed from source...\n"
	printf "Use auto-mark-install-from-source to update the list\n"
	printf "or edit $INSTALL_FROM_SOURCE.\n"
	set +e  # pkg query
	if [ -e "$INSTALL_FROM_SOURCE" ]; then
	    while read line; do
		port=$(echo $line | awk '{ print $1 }')
		if [ $(echo $line | wc -w) = 4 ]; then
		    flavor="$(echo $line | awk '{ print $4 }')"
		    pkgbase=$(auto-print-make-variable --flavor $flavor $port PKGBASE)
		else
		    unset flavor
		    pkgbase=$(auto-print-make-variable $port PKGBASE)
		fi
		installed_version=$(awk -v pkgbase=$pkgbase '$1 == pkgbase { print $2 }' $installed_versions_db)
		ports_version=$(auto-print-make-variable $port PKGVERSION)
		if [ ! -z "$installed_version" ]; then
		    when=$(echo $line | awk '{ print $3 }')
		    if [ 0"$ports_version" != 0"$installed_version" ] || \
		       [ 0"$when" = 0always ]; then
			reason=$(echo $line | awk '{ print $2 }')
			printf "Reinstalling $port from source, reason = $reason\n"
			printf "Installed: $installed_version\n"
			printf "Ports:     $ports_version\n"
			# This code is redundant with auto-install-packages,
			# but we don't use auto-install-packages here in case
			# auto-admin itself is marked for install from source
			# Remove other versions that might block install
			pkg remove -fy $pkgbase || true
			save_cwd=$(pwd)
			cd $PORTSDIR/$port
			if [ -n "$flavor" ]; then
			    make clean reinstall clean FLAVOR=$flavor
			else
			    make clean reinstall clean
			fi
			cd $save_cwd
		    else
			printf "$pkgbase is up-to-date.\n"
		    fi
		else
		    printf "$pkgbase is not installed.\n"
		fi
	    done < $INSTALL_FROM_SOURCE
	fi
	set -e
    fi
    
    if [ -e $(auto-localbase)/etc/auto-update-system-post-ports ]; then
	plugin=$(auto-localbase)/etc/auto-update-system-post-ports
	cat << EOM

Warning: Use of

$(auto-localbase)/etc/auto-update-system-post-ports

is deprecated. Please move the plugin to

$(auto-localbase)/etc/auto-admin/auto-update-system-post-ports

as soon as possible.

EOM
    else
	plugin=$(auto-localbase)/etc/auto-admin/auto-update-system-post-ports
    fi
    if [ -e $plugin ]; then
	owner=`stat -L $plugin | awk '{ print $5 }'`
	group_write=`stat -L $plugin | awk '{ print $3 }' | cut -c 6`
	world_write=`stat -L $plugin | awk '{ print $3 }' | cut -c 9`
	if [ $owner != root ]; then
	    printf "ERROR: $plugin is not owned by root!  You may have been hacked!\n"
	elif [ $group_write != - ]; then
	    printf "ERROR: $plugin is group writable!  You may have been hacked!\n"
	elif [ $world_write != - ]; then
	    printf "ERROR: $plugin is world writable!  You may have been hacked!\n"
	else
	    $plugin
	fi
    fi
    
    # FIXME: Factor out to something like $(auto-os-variant)
    if [ -e /etc/rc.conf.ghostbsd ]; then
	update_base=n
    else
	case $mode in
	binary|binary+reboot|defaults|defaults+reboot)
	    update_base=y
	    freebsd_update_flags=--not-running-from-cron
	    ;;
	interactive)
	    printf "Update base system? [y]/n "
	    read update_base
	    ;;
	esac
    fi
    
    if [ 0$update_base != 0n ]; then
	if [ $(auto-ostype) = FreeBSD ]; then
	    printf "\n**** Updating base system...\n"
	    if [ ! -e /boot.save ]; then
		cp -Rp /boot /boot.save
	    fi
	    fetch_output=/tmp/auto-update-system-fetch.out
	    install_output=/tmp/auto-update-system-install.out
	    freebsd-update $freebsd_update_flags fetch 2>&1 | tee $fetch_output
	    
	    if ! fgrep -q 'No updates' $fetch_output; then
		freebsd-update $freebsd_update_flags install 2>&1 | tee $install_output
		# FIXME: Is this still necessary?  IB may be part of base now.
		if [ -e /boot/modules/ibcore.ko ]; then
		    cat << EOM

Infiniband kernal modules detected.  Run auto-update-infiniband-modules, after
rebooting if a reboot is needed.

EOM
		fi
	    fi
	    
	    # Reboot only if kernel or modules updated
	    if [ 0$reboot_requested = 0y ]; then
		if grep -q '^/boot' $fetch_output; then
		    printf "Kernel componented updated.  System will reboot.\n"
		    auto_reboot=y
		else
		    reboot_advice
		    auto_reboot=n
		fi
	    else
		if grep -q '^/boot' $fetch_output; then
		    printf "Kernel componented updated.  Reboot manually as soon as possible.\n"
		    auto_reboot=n
		else
		    reboot_advice
		    auto_reboot=n
		fi
	    fi
	    rm -f $fetch_output $install_output
    
	    # FIXME: Temporary hack: Perms should be set by base
	    # mandoc.db is automatically generated
	    # Ensure it's readable on systems with restrictive root umask
	    chmod 644 /usr/share/man/mandoc.db
	else
	    printf "Base system update not yet implemented for $(auto-ostype).\n"
	fi
    fi
    
    if [ 0$update_packages != 0n ] && [ 0$update_ports != 0n ] && \
       [ 0$update_base != 0n ]; then
	record_update_time
    fi
    ;;

OpenBSD)
    
    # Don't group questions: Need to pause after each step to view output
    case $mode in
    binary|binary+reboot|defaults|defaults+reboot)
	update_packages=y
	abuse_warning
	;;
    interactive)
	printf "Update installed packages? [y]/n "
	read update_packages
	;;
    esac
    if [ 0$update_packages != 0n ]; then
	printf "\n**** Updating installed packages...\n"
	pkg_add -u || true
    fi
    
    # Ports tree
    case $mode in
    defaults|defaults+reboot)
	update_ports=y
	portsnap_flags=--interactive
	;;
    binary|binary+reboot)
	update_ports=n
	;;
    interactive)
	printf "Update ports tree? [y]/n "
	read update_ports
	;;
    esac
    
    if [ 0$update_ports != 0n ]; then
	cd $PORTSDIR
	printf "\n**** Updating $(pwd)...\n"
	if ! fgrep -q '@' CVS/Root; then
	    cat << EOM
	
You need to set your CVSROOT to one of the anoncvs URLs listed at

https://www.openbsd.org/anoncvs.html#CVSROOT

EOM

printf "Enter hostname:/path for the mirror, e.g. obsdacvs.cs.toronto.edu:/cvs: "
read mirror
printf "anoncvs@$mirror\n" > CVS/Root

	    cat << EOM

Opening $PORTSDIR/CVS/Root using $EDITOR for verification.

EOM
	    pause
	    $EDITOR CVS/Root
	    printf "Updating all CVS/Root files...\n"
	    find . -name CVS -type d -mindepth 2 -exec cp -f CVS/Root '{}' \;
	fi
	printf "Updating repository...\n"
	cvs -q up -dP
	
	if [ -e $PORTSDIR/openbsd-wip ]; then
	    if ! which git; then
		pkg_add git
	    fi
	    (cd $PORTSDIR/openbsd-wip && git pull)
	fi
    fi
    
    # Base system
    case $mode in
    binary|binary+reboot|defaults|defaults+reboot)
	update_base=y
	;;
    interactive)
	printf "Update base system? [y]/n "
	read update_base
	;;
    esac
    
    if [ 0$update_base != 0n ]; then
	printf "\n**** Updating base with syspatch...\n"
	syspatch
    fi
    ;;
    
RHEL)
    # Complete interrupted yum updates if the tool is installed and there
    # are any
    if ! which yum-complete-transaction; then
	yum install -y yum-utils
    fi
    case $mode in
    interactive)
	yum-complete-transaction || true
	yum update
	if [ $(auto-os-release) = RHEL7 ]; then
	    printf "Auto-remove unneeded dependencies? [y]/n "
	    read autoremove
	    if [ 0$autoremove != 0n ]; then
		yum autoremove -y || true
	    fi
	fi
	;;
    binary|defaults)
	yum-complete-transaction || true
	yum update -y
	yum autoremove -y
	;;
    binary+reboot|defaults+reboot)
	yes | yum-complete-transaction || true
	yes | yum update -y
	yum autoremove -y
	;;
    esac
    
    # auto-pkgsrc-prefix exits non-zero if no tree found
    PKGSRC=$(auto-pkgsrc-prefix) || true
    if [ -z "$PKGSRC" ]; then
	printf "$0: No active pkgsrc tree found.\n"
	exit 1
    fi
    if auto-using-pkgsrc; then
	auto-update-pkgsrc "$@"
	record_update_time
    fi
    
    if [ `auto-os-release` != RHEL6 ]; then
	needs-restarting -r || true
    fi
    printf "Services that need restarting:\n"
    needs-restarting
    ;;

NetBSD)
    auto-update-pkgsrc "$@"
    record_update_time

    case $mode in
    interactive)
	printf "Update base system? [y]/n "
	read update_base
	if [ 0$update_base != 0n ]; then
	    export CVS_RSH=ssh 
	    if [ ! -e /usr/src ]; then
		cd /usr
		release=netbsd-`uname -r | cut -d . -f 1,2 | tr . - `
		printf "Checking out $release src...\n"
		pause
		cvs -d anoncvs@anoncvs.NetBSD.org:/cvsroot co -r $release -P src
		cd src
	    else
		cd /usr/src
		cvs -q up -dP
	    fi
	    mkdir -p /usr/obj /usr/tools
	    
	    # Build tools
	    # Added -U to silence warning
	    ./build.sh -O /usr/obj -T /usr/tools -U -u tools
	    
	    # Build kernel
	    arch=$(uname -m)
	    kern=AUTOUPDATE
	    cp sys/arch/$arch/conf/GENERIC sys/arch/$arch/conf/$kern
	    ./build.sh -O ../obj -T ../tools -U kernel=$kern
	    
	    # Build userland
	    ./build.sh -O ../obj -T ../tools -U distribution
	    
	    # Install everything
	    mv /netbsd /netbsd.old
	    mv /usr/obj/sys/arch/$arch/compile/$kern/netbsd /
    
	    cat << EOM
    
Rebooting...

After reboot, run the following to install the new userland:

cd /usr/src
./build.sh -O ../obj -T ../tools -U install=/
EOM
	    pause
	    shutdown -r now
	fi
	;;
    
    *)
	;;

    esac
    ;;

Darwin)
    # auto-pkgsrc-prefix exits non-zero if no tree found
    PKGSRC=$(auto-pkgsrc-prefix) || true
    if [ -z "$PKGSRC" ]; then
	printf "$0: No active pkgsrc tree found.\n"
	exit 1
    fi
    if auto-using-pkgsrc; then
	auto-update-pkgsrc "$@"
    fi
    
    # FIXME: Add softwareupdate logic for base updates
    case $mode in
    binary|binary+reboot|defaults|defaults+reboot)
	update_base=y
	;;
    interactive)
	printf "Update base system? [y]/n "
	read update_base
	;;
    esac
    if [ 0$update_base != 0n ]; then
	printf "\n**** Updating base system...\n"
	softwareupdate --install --all
	record_update_time
    fi
    ;;

*)
    auto-unsupported-os $0
    exit 1
    ;;

esac

# Show completion date and time, mainly for cron job logs
printf "System update completed.\n"
date

# FIXME: Find a secure way to give back to the central cache host
# Requires root access
# Push newly downloaded packages back to central host
#if [ -n "$pkg_cache_host" ]; then
#    printf "Syncing to $pkg_cache_host...\n"
#    rsync -av $pkg_cache/ ${pkg_cache_host}:$pkg_cache
#fi

if [ 0$reboot_requested = 0y ]; then
    if [ $auto_reboot = y ]; then
	if [ $mode = interactive ] || [ 0$pause = 0yes ]; then
	    printf "Minutes to reboot? [2] "
	    read minutes
	    if [ -z "$minutes" ]; then
		minutes=2
	    fi
	else
	    minutes=2
	fi
	printf "Rebooting...\n"
	sync    # Attempt to prevent Linux kernel update failures
	shutdown -r +$minutes
    else
	auto-restart-services || true
	cat << EOM

****************************************************************************
    A reboot was requested, but no kernel updates were installed.
    Most services have been restarted instead.
    Reboot manually if you want to be sure the system boots normally
    with the latest updates.
    
    Otherwise, all users should log out and log back in to restart any
    updated programs started by login sessions.  If X11 components were
    updated, restart the X11 server (or reboot if you do not know how).
****************************************************************************

EOM
	if [ 0$pause = 0yes ]; then
	    pause
	fi
    fi
else
    # Restart services in case daemons were updated
    # FIXME: Not ideal to tolerate failures in service restarts, but there
    # are too many things that can result in a non-zero exit status
    auto-restart-services || true

    cat << EOM

Be sure to reboot if you've updated the kernel, or restart any services
affected by package upgrades.

EOM
    if [ 0$pause = 0yes ]; then
	pause
    fi
fi
