aboutsummaryrefslogtreecommitdiff
#!/bin/bash
#
# Update your system automatically!
#
# Packages from different sources are downloaded, under sub-shells, into the
# PACKAGE_DIR where they can be installed on reboot.
#
# A failure encountered at any point should abort any pending package updates
# to install.
#
# To enable sbotools updates, make the /etc/sbotools/sbotools.conf file exist
# and configure PKG_DIR to point to the same path as STAGING_DIR.

# Set to "yes" to send notification to stdout, causing cron to send an email.
NOTIFY="${NOTIFY:-no}"

# Reboot after this period for install after a successful update.
#
# Set to empty to disable reboot or 'now' to reboot immediately.
REBOOT_TIME="${REBOOT_TIME:-""}"

# Packages are copied here on successful download.  All contents of this
# directory will be deleted.
PACKAGE_DIR="${PACKAGE_DIR:-/var/spool/slack-autoupdate}"

# Packages are temporarily stored here until success.  All contents of this
# directory are deleted.
STAGING_DIR="/var/cache/slack-autoupdate/staging"

# Information of interest to the admin on success.
UPDATE_INFO="$STAGING_DIR/info.txt"

# Information of interest to the admin on failure.
UPDATE_ERROR="$STAGING_DIR/error.txt"

# Avoid concurrently running with another instance.
if [ "$(ls /var/lock/autoupdate.* 2>/dev/null)" ]; then
  echo "Another instance of autoupdate is running.  If this is not correct, you can remove /var/lock/autoupdate.* files and run autoupdate again." \
    >>"$PACKAGE_DIR/$(basename "$UPDATE_ERROR")"
  if [ "$NOTIFY" = "yes" ]; then
    cat "$PACKAGE_DIR/$(basename "$UPDATE_ERROR")"
  fi

  exit 1
fi

touch /var/lock/autoupdate.$$
trap "rm -f /var/lock/autoupdate.$$" EXIT

# Keep it simple stupid, install pending updates first.
mkdir --parents "$PACKAGE_DIR"
if [ -n "$(find "$PACKAGE_DIR" -name "*.t*z")" ]; then
  exit 0
fi

# Make the set of package updates available atomically by storing them in a
# temporary location until completion. 
mkdir --parents "$STAGING_DIR"
find "$STAGING_DIR" -mindepth 1 | xargs rm -fr

if ! OUTPUT="$(
  # Capture this subshell's error output to standard output.
  exec 2>&1

  echo "Checking updates for slackpkg..."

  yes | slackpkg -batch=on -default_answer=yes update || exit "$?"

  CHANGELOG_TXT="$(mktemp /tmp/slack-autoupdate.XXXXXX)"
  PACKAGE_UPDATES="$(mktemp /tmp/slack-autoupdate.XXXXXX)"
  trap "rm -f ${CHANGELOG_TXT} ${PACKAGE_UPDATES}" EXIT

  OUTPUT="$(slackpkg -batch=on -default_answer=n upgrade-all)" || exit_code="$?"
  if [ $exit_code -ne 0 ] && [ $exit_code -ne 20 ] && [ $exit_code -ne 100 ]; then
    # Slackpkg has several safe exit codes.
    exit exit_code
  fi

  echo "$OUTPUT" \
    | grep "\.t.z$" \
    | tee "$PACKAGE_UPDATES"
  if [ ! -s "$PACKAGE_UPDATES" ]; then
    # No updates
    exit 0
  fi

  #
  # This script needs to download updates from slackpkg into a particular
  # directory, which slackpkg does not support.  The following is a work
  # around.
  #

  SOURCE=$(sed -n '
          # Remove leading and trailing blanks
          s/^[[:blank:]]*//
          s/[[:blank:]]*$//
          # Only one token is allowed per line
          /[[:blank:]]/d
          # A single solidus should end the URI
          s,[/]*$,/,
          # Print the lines beginning with one of the URI schemes we look for
          \@^file://@p
          \@^cdrom://@p
          \@^local://@p
          \@^https\{0,1\}://@p
          \@^ftps\{0,1\}://@p' /etc/slackpkg/mirrors)

  while read PKG; do
    PKG_URL="$(
      grep "${PKG}$" /var/lib/slackpkg/CHECKSUMS.md5 \
        | tr -s ' ' \
        | cut -f 2 -d ' '
      )" || exit "$?"

    wget \
      --quiet \
      --directory-prefix="$STAGING_DIR" \
      "${SOURCE}${PKG_URL}" \
      "${SOURCE}${PKG_URL}.asc" \
      || exit "$?"
  done <"$PACKAGE_UPDATES"

  gpg2 --verify-files "$STAGING_DIR"/*.asc || exit "$?"

  (
    # Provide update information to the user.

    # Redirect this subshell's standard output to info file.
    exec 1>>"$UPDATE_INFO"

    LAST_INSTALLED_PACKAGE="$(
      grep \
        "$(ls /var/log/packages | sed "s/$/\\\|/g" | tr --delete '\n')v4EcFvjXKlWB" \
        /var/lib/slackpkg/ChangeLog.txt \
        | head -n 1
    )"

    echo "## Slackware updates"
    echo
    while read PKG; do
      echo " - $PKG"
    done <"$PACKAGE_UPDATES"
    echo
    echo "### Recent Changelog Entries"
    grep --before-context 10000 "$LAST_INSTALLED_PACKAGE" /var/lib/slackpkg/ChangeLog.txt
    echo
  )
)"; then
  >>"$UPDATE_ERROR" echo -e "slackpkg:\n\n$OUTPUT"
fi

if ! OUTPUT="$(
  exec 2>&1

  echo "Checking updates for sbotools..."

  if [ ! -f /etc/sbotools/sbotools.conf ] \
    || [ "$(sed -n 's/PKG_DIR=//p' /etc/sbotools/sbotools.conf)" != "$STAGING_DIR" ]; then
    # Assume sbotools is disabled.
    exit 0
  fi

  source /etc/profile.d/*.sh

  PACKAGE_UPDATES=$(mktemp /tmp/slack-autoupdate.XXXXXX)
  trap "rm -f ${PACKAGE_UPDATES}" EXIT

  (sbocheck | tee $PACKAGE_UPDATES) || exit 1

  # Avoid checking for 'no updates available'.  It will not
  # work if you host 'overrides' purposely different than what is
  # on SBO.
  if ! grep "needs updating" "$PACKAGE_UPDATES"; then
    exit 0
  fi

  sboupgrade \
    --all \
    --noinstall \
    --nointeractive \
    || exit "$?"

  (
    exec 1>>"$UPDATE_INFO"

    echo "## Slackbuild updates"
    echo
    cat "$PACKAGE_UPDATES"
    echo
  )
)"; then
  if [ -f "$UPDATE_ERROR" ]; then
    >>"$UPDATE_ERROR" echo ""
    >>"$UPDATE_ERROR" echo ""
  fi

  >>"$UPDATE_ERROR" echo -e "sbotools:\n\n$OUTPUT"
fi

if [ -z "$(find "$STAGING_DIR" -name "*.t*z" -or -name "info.txt" -or -name "error.txt")" ]; then
  # No updates
  exit 0
fi

if [ "$NOTIFY" = "yes" ]; then
  if [ -f "$UPDATE_ERROR" ]; then
    echo "Failures were encountered while trying to download updates"
    echo ""
    cat "$UPDATE_ERROR"
  elif [ -f "$UPDATE_INFO" ]; then
    echo "# Updates pending installation"
    echo ""
    cat "$UPDATE_INFO"
  fi
fi

if [ -f "$UPDATE_ERROR" ]; then
  mv "$UPDATE_ERROR" "$PACKAGE_DIR"
  if [ -f "$UPDATE_INFO" ]; then
    mv "$UPDATE_INFO" "$PACKAGE_DIR"
  fi

  exit 1
fi

# Now that everything is successful.
find "$PACKAGE_DIR" -mindepth 1 | xargs rm -fr
mv "$STAGING_DIR"/* "$PACKAGE_DIR/"

if [ -n "$REBOOT_TIME" ]; then
  nohup shutdown -r "$REBOOT_TIME" >/dev/null 2>&1 &
fi