#!/bin/bash
#
#        Title: Zypper-Upgradedistro
#      Version: 1.2.0
#       Author: Fabio Mucciante
#       E-mail: fabio.mucciante@gmail.com
#      License: GPL v3
# Requirements: bash, sed, zypper, zypper-upgraderepo
#        Usage: This script performs all the steps needed to upgrade the openSUSE Leap
#               distribution smoothly.
#      Created: 2020/04/04
#      Updated: 2025/12/19

VERSION_CHECK=1
ALLOW_UNSTABLE=0
SYSTEM_UPDATE=1
REPOSITORY_CHECK=1
REPOSITORY_UPGRADE=1
DOWNLOAD_PACKAGES=1
LOAD_OVERRIDES=''
RESUME_FILE="$HOME/upgradedistro.resume"
UPGRADE_TO=''
CHECK_FOR=''
UPGRADEREPO_EXE=''

function help {
  local ver

  ver=$(grep '# *Version: [0-9]' "$0"|cut -f 2 -d ':'|tr -d ' ')
  echo "
Zypper-Upgradedistro v$ver

SYNOPSIS:

  upgradedistro [OPTIONS]

DESCRIPTION:

  Zypper-Upgradedistro drives through all the required steps to upgrade the openSUSE Leap
  distribution, smoothly.

OPTIONS:

  --allow-unstable (-U):
    Allow unstable releases to be considered as an option

  --load-overrides <FILENAME>:
    Load the repository overrides FILENAME

  --no-version-check (-v):
    Skip the release version check

  --no-system-update (-s)
    Skip the system update

  --no-repository-check (-c):
    Skip the repository check procedure

  --no-repository-upgrade (-u):
    Skip the repository upgrade procedure

  --no-repository (-r)
    Skip both the repository check and upgrade procedures

  --no-packages (-p)
    Skip the download of the packages to be upgraded

  --resume
    Restart from the last step left uncompleted

OTHER:

  --help (-h):
    This guide

DEFAULT EDITOR:

  * $EDITOR

  To change this, override the variable \$EDITOR before launching the script
"
}

function filter_params {

  while [ -n "$1" ]; do

    case "$1" in
    '--allow-unstable' | '-U') ALLOW_UNSTABLE=1
    ;;
    '--load-overrides') shift; LOAD_OVERRIDES="$1"
    ;;
    '--no-version-check' | '-v') VERSION_CHECK=-1
    ;;
    '--no-system-update' | '-s') SYSTEM_UPDATE=-1
    ;;
    '--no-repository-check' | '-c') REPOSITORY_CHECK=-1
    ;;
    '--no-repository-upgrade' | '-u') REPOSITORY_UPGRADE=-1
    ;;
    '--no-repository' | '-r') REPOSITORY_CHECK=-1; REPOSITORY_UPGRADE=-1
    ;;
    '--no-packages' | '-p') DOWNLOAD_PACKAGES=-1
    ;;
    '--resume')
      if [ -f "$RESUME_FILE" ]; then
        source "$RESUME_FILE"
      fi
    ;;
    '--help' | '-h') help; exit
    ;;
    esac

    shift
  done
}

function _nothing {
  echo -e "\r\E[31m\033[1m[-]\E[0m $1"
}

function _error {
  echo -e "\r\E[31m\033[1m[E]\E[0m $1"
}

function _ok {
  echo -e "\r\E[32m\033[1m[V]\E[0m $1"
}


function _warning {
  echo -e "\r\E[33m\033[1m[W]\E[0m $1"
}

function _ask {
  echo -ne "\E[35m\033[1m[?]\E[0m $1 [y/N]: "
}

function _option {
  echo -e "\E[34m\033[1m[*]\E[0m $1"
}

function _clean {
  echo "    $1"
}

function _up {
  echo -e "\e[1A\e[K"
}

function load_ur_exe {
  if command -v 'upgraderepo'; then
    UPGRADEREPO_EXE='upgraderepo'
  else
    UPGRADEREPO_EXE='zypper-upgraderepo'
  fi
}

function discover_available_versions {
  local allow_unstable
  local versions
  local num_versions

  echo -n 'Checking for the available versions... '
  allow_unstable=$([[ $ALLOW_UNSTABLE -eq 1 ]] && echo '--allow-unstable' || echo '')
  versions=$($UPGRADEREPO_EXE --status --quiet $allow_unstable)
  num_versions=$(echo $versions | cut -f 1 -d ' ')

  if [[ $num_versions -gt 1 ]]; then
    _warning 'Which version do you want to upgrade to?'
    c=1
    for i in $(echo $versions | cut -f 2- -d ' '); do
      _option "$c) $i"
      c=$((c+1))
    done

    ans=
    while [[ -z $ans ]]; do
      echo -ne "Insert a valid value: [1/.../$num_versions] "
      read ans
      if [[ -z "$ans" || $ans -lt 1 || $ans -gt $num_versions ]]; then
        _up;_up
        ans=
      fi
    done
    UPGRADE_TO=$(echo $versions | cut -f $((ans+1)) -d ' ')
    _ok "Selected the $UPGRADE_TO version"
  elif [[ $num_versions -eq 1 ]]; then
    _ok 'Selected the last available release to upgrade'
  else
    _error 'The zypper-upgraderepo package is not updated yet to recognize the new release!'
    exit
  fi
  echo "UPGRADE_TO=$UPGRADE_TO" >> "$RESUME_FILE"
  echo "ALLOW_UNSTABLE=$ALLOW_UNSTABLE" >> "$RESUME_FILE"
}

function repos_upgrade {
  local options=''

  if [ -n "$LOAD_OVERRIDES" ]; then
    options="--load-overrides $LOAD_OVERRIDES"
  fi

  echo -n 'Upgrading repositories...'
  local upgrade_to=$([[ -z "$UPGRADE_TO" ]] && echo '--upgrade' || echo "--upgrade-to $UPGRADE_TO")
  sudo $UPGRADEREPO_EXE $upgrade_to --only-enabled --quiet $options 2> /dev/null
  case $? in
  0)
    _ok 'Repositories correctly upgraded!'
    ;;
  5)
    libzypp_locked_error
    exit
    ;;
  *)
    _error 'An error occurred while upgrading the repositories'
    _clean ''
    _clean '***************************************************************************************************'
    _clean "*** Run 'sudo $UPGRADEREPO_EXE $upgrade_to --only-enabled $options' to check and fix the issues ***"
    _clean '***   then restart this script with the --resume option                                         ***'
    _clean '***   or skip this step with the --no-repository option (-r)                                    ***'
    _clean '***************************************************************************************************'
    exit
    ;;
  esac
}

function fix_repos_upgrade {
  local tmpfile

  tmpfile=$([ -n "$LOAD_OVERRIDES" ] && echo "$LOAD_OVERRIDES" || mktemp "$HOME/upgradedistro_overrides.XXXXXXXXXXX")

  EDITOR=$([ -n "$EDITOR" ] && echo "$EDITOR" || echo 'vim')
  _warning "Some repository can't get upgraded, so you should either..."
  _option 'Disable the repositories having an invalid URL'
  _option 'Replace the repository URL with a valid one'
  _clean ''
  _clean 'A list of invalid repositories will be exported in an INI file format'
  _clean "  and opened with the default text editor ($EDITOR) in order to consent"
  _clean '  one of these changes achieved by:'
  _clean '  - updating the *enabled* field to *no*'
  _clean '  - overriding the *url* field with a valid URL or using the suggested'
  _clean '    one if any is given'
  _clean ''
  _clean 'To change the default editor just restart the script setting the *EDITOR*'
  _clean '  variable properly: EDITOR=nano upgradedistro --resume'
  _clean ''
  _clean 'Press the [RETURN] key to continue.'
  read

  echo -n 'Exporting the list...'
  $UPGRADEREPO_EXE $CHECK_FOR --ini --only-enabled --only-invalid 1> "$tmpfile"
  _ok 'List ready to be edited. Press the [RETURN] key to continue'
  read
  local res=7
  while [[  $res !=  0 ]]; do
    $EDITOR "$tmpfile"
    echo -n 'Checking repositories again...'
    $UPGRADEREPO_EXE $CHECK_FOR --only-enabled --load-overrides "$tmpfile" --exit-on-fail --quiet
    res=$?
    if [[ $res != 0 ]]; then
      _error 'Other problems have been detected!    '
      _clean ''
      _clean 'Press the [RETURN] key to edit the file again'
      read
      $UPGRADEREPO_EXE $CHECK_FOR --only-enabled --load-overrides "$tmpfile" --ini --only-invalid 1> "$tmpfile"
    fi
  done

  _ok 'Repository check completed with success!'
  LOAD_OVERRIDES="$tmpfile"
  echo "LOAD_OVERRIDES=$LOAD_OVERRIDES" >> "$RESUME_FILE"
}

function libzypp_locked_error {
  local library
  local pid

  library=$(ldd /usr/bin/zypper | grep 'libzypp.so' | cut -f 2 -d '>' | cut -f 2 -d ' ')
  pid=$(sudo lsof "$library" | tail -n 1 | cut -f 2 -d ' ')

  _error "The application with pid $pid is accessing the package management system"
  _clean ''
  _clean '*******************************************************************'
  _clean "*** Terminate the application with 'sudo kill $pid'             ***"
  _clean '***   then restart this script with the --resume option         ***'
  _clean '*******************************************************************'
}


filter_params "$@"

if [[ $EUID == 0 ]]; then
  _error 'Running as root, please start the script as a regular user.'
  exit
fi

if [[ -n "$LOAD_OVERRIDES" ]]; then
  if [[ ! -f "$LOAD_OVERRIDES" ]]; then
    _error "The overrides filename '$LOAD_OVERRIDES' doesn't exist!"
    exit
  fi
fi

load_ur_exe

# ===========================================================
#
# Step n.1
#   openSUSE Leap version check through the download webpage
#   for the reference. In case more versions are available,
#   it asks which one to upgrade to.
#
# ===========================================================
if [[ $VERSION_CHECK == 1 ]]; then
  echo -n 'Online version check...'

  if [[ $ALLOW_UNSTABLE == 0 ]]; then
    res=$(curl -A 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0' 'https://get.opensuse.org/' 2> /dev/null | grep '<h4>Leap' | head -n 1)
  else
    res=$(curl -A 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0' 'https://get.opensuse.org/' 2> /dev/null | grep '<h4>Leap.*<sup>' | head -n 1)
  fi

  if [[ "$res" == '' ]]; then
    _error "Connection troubles, the address https://get.opensuse.org can't be reached"
    exit
  fi

  long_ver=$(echo $res | sed  's@.*>Leap \([^<\ ]\+\)\ *<.*@\1@g')
  short_ver=$(echo $long_ver | cut -f 2 -d ' ')
  #echo $long_ver; echo $short_ver; exit
  current_ver=$(grep -i '^Version=' /etc/os-release | cut -f 2 -d '=' | tr -d '"')
  if [ $short_ver != $current_ver ]; then
    _ok "The version $long_ver is available"
    _ask 'Do you want to perform an upgrade?'
    read ans
    if [ "$ans" != 'y' ]; then
      _nothing "Upgrade aborted."
      exit
    fi
    echo 'VERSION_CHECK=0' > "$RESUME_FILE"
  else
    _ok "The current version ($current_ver) is already the last stable ($short_ver)"
    exit 2
  fi
  discover_available_versions
elif [[ $VERSION_CHECK == 0 ]]; then
  _ok 'Version check achieved'
else
  _warning 'Version check skipped'
fi

# ===========================================================
#
# Step n.2
#   Update the system to the latest available packages
#
# ===========================================================
if [[ $SYSTEM_UPDATE == 1 ]]; then
  echo -n 'Performing the system update...'
  sudo zypper update -y -l &> /dev/null
  case $? in
  0)
    _up
    _ok 'System correctly updated         '
    ;;
  7)
    libzypp_locked_error
    exit
    ;;
  *)
    _error 'An error occurred while updating the system'
    _clean ''
    _clean '*******************************************************************'
    _clean '*** Run "sudo zypper update" to check and fix the issues        ***'
    _clean '***   then restart this script with the --resume option         ***'
    _clean '***   or skip this step with the --no-system-update option (-s) ***'
    _clean '*******************************************************************'
    exit
    ;;
  esac
  echo 'SYSTEM_UPDATE=0' >> "$RESUME_FILE"
elif [[ $SYSTEM_UPDATE == 0 ]]; then
  _ok 'System update achieved'
else
  _warning 'System update skipped'
fi

# ===========================================================
#
# Step n.3
#   Check for the repository availability to migrate to the
#   new selected version.
#
# ===========================================================
if [[ $REPOSITORY_CHECK == 1 ]]; then
  echo -n 'Checking the Repositories... '
  CHECK_FOR=$([[ -z "$UPGRADE_TO" ]] && echo '--check-next' || echo "--check-for $UPGRADE_TO")
  $UPGRADEREPO_EXE $CHECK_FOR --only-enabled --quiet --exit-on-fail
  case $? in
  0)
    _ok 'Repositories: Ready to be upgraded'
    echo 'REPOSITORY_CHECK=0' >> "$RESUME_FILE"
    ;;
  2)
    _error 'The system is already upgraded   '
    exit
    ;;
  7)
    fix_repos_upgrade
    ;;
  16)
    _error "The current system doesn't support the x86_64-v2 instruction set!"
    exit
    ;;
  *)
    _error 'An error occurred while checking the repositories'
    _clean ''
    _clean '**************************************************************************************'
    _clean "*** Run '$UPGRADEREPO_EXE $CHECK_FOR --only-enabled' to check and fix the issues ***"
    _clean '***   then restart this script with the --resume option                            ***'
    _clean '***   or skip this step with the --no-repository-check option (-c)                 ***'
    _clean '**************************************************************************************'
    exit
    ;;
  esac
elif [[ $REPOSITORY_CHECK == 0 ]]; then
  _ok 'Repository check achieved'
else
  _warning 'Repository check skipped'
fi

# ===========================================================
#
# Step n.4
#   Upgrade the repository URLs
#
# ===========================================================
if [[ $REPOSITORY_UPGRADE == 1 ]]; then
  echo -n 'Backing up repositories...'

  if $UPGRADEREPO_EXE --backup "$PWD" --quiet; then
    _ok "Repositories backup created under '$PWD'"
  else
    _error 'Repository backup failed'
    exit
  fi
  repos_upgrade
  echo 'REPOSITORY_UPGRADE=0' >> "$RESUME_FILE"
elif [[ $REPOSITORY_UPGRADE == 0 ]];then
  _ok 'Repository upgrade achieved'
else
  _warning 'Repository upgrade skipped'
fi

# ===========================================================
#
# Step n.5
#   Download the packages to upgrade and keep them in cache
#
# ===========================================================
if [[ $DOWNLOAD_PACKAGES == 1 ]]; then
  echo -n 'Downloading packages...'
  sudo zypper dup --allow-vendor-change --download-only
  case $? in
  0)
    _ok 'All the packages have been downloaded'
    echo 'DOWNLOAD_PACKAGES=0' >> "$RESUME_FILE"
    ;;
  7)
    libzypp_locked_error
    exit
    ;;
  *)
    _error 'An error occurred while downloading the upgrading packages'
    _clean ''
    _clean '*************************************************************************'
    _clean '*** Run "sudo zypper dup --download-only" to check and fix the issues ***'
    _clean '***   other helpful options to use might be:                          ***'
    _clean '***     --allow-vendor-change                                         ***'
    _clean '***     --allow-downgrade                                             ***'
    _clean '***     --allow-name-change                                           ***'
    _clean '***   then restart this script with the --resume option               ***'
    _clean '***   or skip this step with the --no-packages option (-p)            ***'
    _clean '*************************************************************************'
    exit
    ;;
  esac
elif [[ $DOWNLOAD_PACKAGES == 0 ]]; then
  _ok 'Package download achieved'
else
  _warning 'Package download skipped'
fi

# ===========================================================
#
# Step n.6
#   Leave the graphical session and switch to runlevel 3
#
# ===========================================================
echo -n 'Running in graphical session check...'
if [[ "$(tty)" =~ tty[0-9]+$ ]]; then
  _ok 'Running in a virtual console'
else
  _error 'Running in a graphical session'
  _clean ''
  _clean '***********************************************************************************'
  _clean '*** Please logout from your graphical session and continue in a virtual console ***'
  _clean "***   typing CTRL + ALT + Fn and logging in with the same username ($USER).     ***"
  _clean '***                                                                             ***'
  _clean '***   then restart this script with the --resume option                         ***'
  _clean '***********************************************************************************'
  exit
fi

_clean '***************************************************************************'
_clean '*** Going to switch to runlevel 3 in order to have the minimal services ***'
_clean '***   running before applying the changes.                              ***'
_clean '***                                                                     ***'
_clean '*** Be sure to:                                                         ***'
_clean '***   - close all the applications in the graphical session             ***'
_clean '***   - unmount unnedeed external storage disks/USB keys/SD cards/...   ***'
_clean '***   - logout from the graphical session                               ***'
_clean '***************************************************************************'
_ask 'Are you ready to continue?'
read ans
if [[ "$ans" == 'y' ]]; then
  sudo init 3
  if [[ $? == 0 ]]; then
    _ok 'Runlevel 3 enabled'
  else
    _error 'Something wrong happened'
    exit
  fi
else
  _error 'Upgrading process aborted.'
  _clean ''
  _clean '***************************************************************'
  _clean '*** When ready, restart the script with the --resume option ***'
  _clean '***************************************************************'
  exit
fi

# ===========================================================
#
# Step n.7
#   Upgrading the packages from cache
#
# ===========================================================
_clean '*************************************************************************************'
_clean "*** Time to upgrade, don't turn off the computer until the procedure is completed ***"
_clean '*************************************************************************************'
_clean ''
_ask 'Are you ready to continue?'
read ans
if [[ "$ans" == 'y' ]]; then
  sudo zypper --no-refresh dup --allow-vendor-change
  case $? in
  0)
    _ok 'All packages have been installed'
    ;;
  7)
    libzypp_locked_error
    exit
    ;;
  *)
    _error 'Something wrong happened'
    _clean ''
    _clean '***************************************************************'
    _clean '*** Retry restarting the script with the --resume option    ***'
    _clean '***   or just execute "sudo zypper --no-refresh dup" again  ***'
    _clean '***   other helpful options to fix the issues use might be: ***'
    _clean '***     --allow-vendor-change                               ***'
    _clean '***     --allow-downgrade                                   ***'
    _clean '***     --allow-name-change                                 ***'
    _clean '**************************************************************'
    exit
    ;;
  esac
else
  _error 'Upgrading process aborted'
  _clean ''
  _clean '***************************************************************'
  _clean '*** When ready, restart the script with the --resume option ***'
  _clean '***************************************************************'
  exit
fi

# ===========================================================
#
# Step n.8
#   Reboot the system
#
# ===========================================================
_clean '**********************************************'
_clean '*** Time to reboot to complete the upgrade ***'
_clean '**********************************************'
_clean
_ask 'Are you ready to continue?'
read ans
if [[ "$ans" == 'y' ]]; then
  _ok 'Rebooting'
  sudo reboot now
else
  _error 'Reboot the system manually to complete the process!'
  exit
fi

