#!/bin/bash

# Upgrade to a different Nextcloud version
#
# Copyleft 2017 by Ignacio Nunez Hernanz <nacho _a_t_ ownyourbits _d_o_t_ com>
# GPL licensed (see end of file) * Use at your own risk!
#
# Usage:
#       ncp-update-nc <version>
#
# More at https://ownyourbits.com
#

# test cases
####################
# - with and without moving datadir
# - failure at each test point
# -> must pass basic NC in tests.py ( meaning it's not in a broken state )

set -eE${DBG}

VER="$1"
BIN="${0##*/}"

source /usr/local/etc/library.sh

# pre-checks
####################
[[ "$VER" == "" ]] && { echo "Usage ${BIN} <version>"; exit 1; }
[[ -f /.docker-image ]] && BASEDIR=/data || BASEDIR=/var/www
cd "$BASEDIR"
DATADIR="$( get_nc_config_value datadirectory )"
ncc status &>/dev/null          || { [[ "$DBG" == x ]] && ncc status; echo "Nextcloud is currently down"; exit 1; }
[[ -d "${BASEDIR}/nextcloud-old" ]] && { echo "Nextcloud backup directory found. Interrupted or already running installation?"; exit 1; }
[[ -d "${BASEDIR}/nextcloud"     ]] || { echo "Nextcloud directory not found"      ; exit 1; }
[[ -d "$DATADIR"             ]] || { echo "Nextcloud data directory not found" ; exit 1; }

# check version
####################

[[ ${EUID} -eq 0 ]] && SUDO="sudo -u www-data"
CURRENT="$( $SUDO php /var/www/nextcloud/occ status | grep "version:" | awk '{ print $3 }' )"

MAJOR_CUR=$( cut -d. -f1 <<<"${CURRENT}" )
MAJOR_NEW=$( cut -d. -f1 <<<"${VER}" )
if [[ $((MAJOR_NEW - MAJOR_CUR)) -gt 1 ]]; then
  echo "Upgrade cannot skip major versions. Please upgrade one major version at a time" >&2
  exit 1
fi

if [[ "$MAJOR_NEW" -ge 24 ]] && [[ "$(lsb_release -r)" =~ .*10 ]]
then
  echo -e "NCP doesn't support Nextcloud versions greater than 23 with Debian 10 (Buster). Please run ncp-dist-upgrade."
  exit 1
fi

grep -qP "\d+\.\d+\.\d+" <<<"$CURRENT" || { echo "Malformed version $CURRENT"; exit 1; }
grep -qP "\d+\.\d+\.\d+" <<<"$VER"     || { echo "Malformed version $VER"    ; exit 1; }

echo "Current   Nextcloud version $CURRENT"
echo "Available Nextcloud version $VER"
is_more_recent_than "${VER}" "${CURRENT}" || { echo "Nothing to update"; exit 1; } # we want `exit 1` so the autoupdate doesn't notify success in this case


if ! is_more_recent_than '25.0.0' "${VER}" && is_more_recent_than "8.1.0" "${PHPVER}.0" && is_docker
then
  echo 'You need to upgrade to a later docker image in order to upgrade to Nextcloud 25+'
  exit 1
fi

# make sure that cron.php is not running and there are no pending jobs
# https://github.com/nextcloud/server/issues/10949
pgrep -cf cron.php &>/dev/null && { pkill -f cron.php; sleep 3; }
pgrep -cf cron.php &>/dev/null && { echo "cron.php running. Abort"; exit 1; }
mysql nextcloud <<<"UPDATE oc_jobs SET reserved_at=0;"

# cleanup
####################
cleanup() {
  local RET=$?
  set +eE
  echo "Clean up..."
  rm -rf "$BASEDIR"/nextcloud.tar.bz2 "$BASEDIR"/nextcloud-old
  trap "" EXIT
  exit $RET
}
trap cleanup EXIT

# get new code
####################
URL="https://download.nextcloud.com/server/releases/nextcloud-$VER.tar.bz2"
echo "Download Nextcloud $VER..."
wget -q "$URL" -O nextcloud.tar.bz2 || { echo "Error downloading"; exit 1; }

# backup
####################
BKPDIR="$BASEDIR"
WITH_DATA=no
COMPRESSED=yes
LIMIT=0

echo "Back up current instance..."
set +eE
ncp-backup "$BKPDIR" "$WITH_DATA" "$COMPRESSED" "$LIMIT" # && false # test point
RET=$?
sync
set -eE

BKP_="$( ls -1t "$BKPDIR"/nextcloud-bkp_*.tar.gz 2>/dev/null | head -1 )"
[[ -f "$BKP_"  ]] || {                set +eE; echo "Error backing up"; false || cleanup; }
[[ $RET -ne 0  ]] && { rm -f "$BKP_"; set +eE; echo "Error backing up"; false || cleanup; }
BKP="$( dirname "$BKP_" )/$( basename "$BKP_" .tar.gz )-${CURRENT}.tar.gz"
echo "Storing backup at '$BKP'..."
mv "$BKP_" "$BKP"

# simple restore if anything fails from here
####################
rollback_simple() {
  set +eE
  trap "" INT TERM HUP ERR
  echo -e "Abort\nSimple roll back..."
  rm -rf "$BASEDIR"/nextcloud
  mv "$BASEDIR"/nextcloud-old "$BASEDIR"/nextcloud
  false || cleanup                 # so cleanup exits with 1
}
trap rollback_simple INT TERM HUP ERR

# replace code
####################
echo "Install Nextcloud $VER..."
mv -T nextcloud nextcloud-old
tar -xf nextcloud.tar.bz2             # && false # test point
rm -rf /var/www/nextcloud.tar.bz2

# copy old config
####################
cp nextcloud-old/config/config.php nextcloud/config/

# copy old themes
####################
cp -raT nextcloud-old/themes/ nextcloud/themes/

# copy old NCP apps
####################
for app in nextcloudpi previewgenerator; do
  if [[ -d nextcloud-old/apps/"${app}" ]]; then
    cp -r -L nextcloud-old/apps/"${app}" /var/www/nextcloud/apps/
  fi
done

#false # test point

# copy data if it was at the default location
####################
if [[ "$DATADIR" == "/var/www/nextcloud/data" ]] || [[ "$DATADIR" == "/data/nextcloud/data" ]]; then
  echo "Restore data..."
  mv -T nextcloud-old/data nextcloud/data
fi

# nc-restore if anything fails from here
####################
rollback() {
  set +eE
  trap "" INT TERM HUP ERR EXIT
  echo -e "Abort\nClean up..."
  rm -rf /var/www/nextcloud.tar.bz2 "$BASEDIR"/nextcloud-old
  echo "Rolling back to backup $BKP..."
  local TMPDATA
  mkdir -p "$BASEDIR/recovery/"
  TMPDATA="$( mktemp -d "$BASEDIR/recovery/ncp-data.XXXXXX" )" || { echo "Failed to create temp dir" >&2; exit 1; }
  [[ "$DATADIR" == "$BASEDIR/nextcloud/data" ]] && mv -T "$DATADIR" "$TMPDATA"
  ncp-restore "$BKP" || { echo "Rollback failed! Data left at $TMPDATA"; exit 1; }
  [[ "$DATADIR" == "$BASEDIR/nextcloud/data" ]] && { rm -rf "$DATADIR"; mv -T "$TMPDATA" "$DATADIR"; }
  rm "$BKP"
  echo "Rollback successful. Nothing was updated"
  exit 1
}
trap rollback INT TERM HUP ERR

# fix permissions
####################
echo "Fix permissions..."
chown -R www-data:www-data nextcloud
find nextcloud/ -type d -exec chmod 750 {} \;
find nextcloud/ -type f -exec chmod 640 {} \;

# upgrade
####################
echo "Upgrade..."
ncc='sudo -u www-data php nextcloud/occ'
$ncc upgrade      # && false # test point
$ncc | grep -q db:add-missing-indices && $ncc db:add-missing-indices -n
$ncc | grep -q db:add-missing-columns && $ncc db:add-missing-columns -n
$ncc | grep -q db:add-missing-primary-keys && $ncc db:add-missing-primary-keys -n
$ncc | grep -q db:convert-filecache-bigint && $ncc db:convert-filecache-bigint -n

# use the correct version for custom apps
NCVER="$(ncc status | grep "version:" | awk '{ print $3 }')"
if is_more_recent_than "21.0.0" "${NCVER}"; then
  NCPREV=/var/www/ncp-previewgenerator/ncp-previewgenerator-nc20
else
  # Install notify_push if not installed
  if ! is_app_enabled notify_push; then
    ncc app:install notify_push
    ncc app:enable  notify_push
    install_template nextcloud.conf.sh /etc/apache2/sites-available/nextcloud.conf
    a2enmod proxy proxy_http proxy_wstunnel
    apachectl -k graceful
    ## make sure the notify_push daemon is runnnig

    install_template systemd/notify_push.service.sh /etc/systemd/system/notify_push.service
    start_notify_push
    nc_domain="$(ncc config:system:get overwrite.cli.url)"
    set-nc-domain "${nc_domain}" || {
      echo "notify_push setup failed. You are probably behind a proxy"
      echo "Run 'ncc config:system:set trusted_proxies 15 --value=<proxy_IP>' and then 'ncc notify_push:setup https://<domain>/push to enable"
      echo "Check https://help.nextcloud.com/tags/ncp for support"
    }

  fi
  NCPREV=/var/www/ncp-previewgenerator/ncp-previewgenerator-nc21
fi
rm -rf /var/www/nextcloud/apps/previewgenerator
ln -snf "${NCPREV}" /var/www/nextcloud/apps/previewgenerator

if ! is_docker && ! is_more_recent_than "24.0.0" "${NCVER}" && is_more_recent_than "8.1.0" "${PHPVER}.0"
then
  (
  echo "Upgrading PHP..."
  export DEBIAN_FRONTEND=noninteractive
  PHPVER_OLD="$PHPVER"
  PHPVER_NEW="8.1"
  PHP_PACKAGES_OLD=(php-{common,igbinary,redis,json} "php${PHPVER_OLD}" \
    "php${PHPVER_OLD}"-{curl,gd,fpm,cli,opcache,mbstring,xml,zip,fileinfo,ldap,intl,bz2,json,common,readline,mysql,bcmath,gmp})
  PHP_PACKAGES_NEW=("php${PHPVER_NEW}" \
    "php${PHPVER_NEW}"-{curl,gd,fpm,cli,opcache,mbstring,xml,zip,fileinfo,ldap,intl,bz2,mysql,bcmath,gmp,redis,common})

  php_restore() {
    trap "" INT TERM HUP ERR
    echo "Something went wrong while upgrading PHP. Rolling back to version ${PHPVER_OLD}..."
    set +e
    service "php${PHPVER_NEW}-fpm" stop
    a2disconf php${PHPVER_NEW}-fpm
    rm /etc/apt/sources.list.d/php.list
    apt-get update
    apt-get remove --purge -y "${PHP_PACKAGES_NEW[@]}"
    apt-get install -y --no-install-recommends -t "$RELEASE" "${PHP_PACKAGES_OLD[@]}"
    set_ncpcfg "php_version" "${PHPVER_OLD}"
    install_template "php/opcache.ini.sh" "/etc/php/${PHPVER_NEW}/mods-available/opcache.ini"
    run_app nc-limits
    a2enconf "php${PHPVER_OLD}-fpm"
    service "php${PHPVER_OLD}-fpm" start
    service apache2 restart
    echo "PHP upgrade has been successfully reverted"
    set -e
  }

  trap php_restore INT TERM HUP ERR

  # Setup apt repository for php 8
  wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg
  echo "deb https://packages.sury.org/php/ ${RELEASE%-security} main" > /etc/apt/sources.list.d/php.list
  apt-get update

  clear_opcache

  echo "Stopping apache and  php-fpm..."
  service "php${PHPVER_OLD}-fpm" stop
  service apache2 stop

  echo "Remove old PHP (${PHPVER_OLD})..."
  a2disconf "php${PHPVER_OLD}-fpm"

  apt-get remove --purge -y "${PHP_PACKAGES_OLD[@]}"

  echo "Install PHP ${PHPVER_NEW}..."
  install_with_shadow_workaround --no-install-recommends systemd
  apt-get install -y --no-install-recommends -t "$RELEASE" "${PHP_PACKAGES_NEW[@]}"

  set_ncpcfg "php_version" "${PHPVER_NEW}"
  install_template "php/opcache.ini.sh" "/etc/php/${PHPVER_NEW}/mods-available/opcache.ini"
  ( set -e; export PHPVER="${PHPVER_NEW}"; run_app nc-limits )

  a2enconf "php${PHPVER_NEW}-fpm"

  echo "Starting apache and php-fpm..."
  service "php${PHPVER_NEW}-fpm" start
  service apache2 start
  ncc status
  )

  # Reload library.sh to reset PHPVER
  source /usr/local/etc/library.sh

fi

# refresh completions
ncc _completion -g --shell-type bash -p ncc | sed 's|/var/www/nextcloud/occ|ncc|g' > /usr/share/bash-completion/completions/ncp

is_docker && {
  killall notify_push
  sleep 1
  start_notify_push
}

echo "Update completed successfully."
# done
####################
mkdir -p  "$DATADIR"/ncp-update-backups
mv "$BKP" "$DATADIR"/ncp-update-backups
chown -R www-data:www-data "$DATADIR"/ncp-update-backups
BKP="$DATADIR"/ncp-update-backups/"$( basename "$BKP" )"
echo "Backup stored at $BKP"

bash -c "sleep 5; source /usr/local/etc/library.sh; clear_opcache;" &>/dev/null &
