#!/bin/sh

#
# By Gengik84, rewritten by PMheart
#
# Based on Pike R. Alpha's script and crazybirdy's modifications.
#
# Many thanks to PikeRAlpha, crazybirdy and PMheart!
#

# uncomment to enable debug logging
# set -x

# Change additional shell optional behavior (expand unmatched names to a null string).
shopt -s nullglob

#
# VARIABLES
#
#----------------------------------------------------------------------------------------------------

#
# script version
#
gScriptVersion="1.0.20"

#
# Beta version (ONLY the latest Beta)
# could be empty when there is no Beta available. (consider 10.14.6)
#
gBetaVersion=""

#
# stable version (ONLY the latest stable)
#
gStableVersion="10.14.6"

#
# Current macOS version returned from sw_vers (e.g 10.14.5).
#
gProductVersion="$(sw_vers -productVersion)"

#
# Current macOS build returned from sw_vers (e.g 18F132).
#
gProductBuildVersion="$(sw_vers -buildVersion)"

#
# Current macOS major version.
# For example: given gProductVersion 10.14.3, gProductMajorVersion will be 14.
#
gProductMajorVersion="${gProductVersion:3:2}"

#
# Current macOS minor version.
# For example: given gProductVersion 10.14.3, gProductMinorVersion will be 3.
#
gProductMinorVersion="${gProductVersion:6:2}"
# NOTE: It will be null when no minor version available. Appending 0.
[[ -z "${gProductMinorVersion}" ]] && gProductMinorVersion=0

#
# Beta Major version
# For example: given gBetaVersion 10.14.4, gBetaMajorVersion will be 14.
#
gBetaMajorVersion="${gBetaVersion:3:2}"

#
# Beta Minor version
# For example: given gBetaVersion 10.14.4, gBetaMinorVersion will be 4.
#
gBetaMinorVersion="${gBetaVersion:6:2}"
# NOTE: It will be null when no minor version available. Appending 0.
[[ -z "${gBetaMinorVersion}" ]] && gBetaMinorVersion=0

#
# Stable Major version
# For example: given gStableVersion 10.14.4, gStableMajorVersion will be 14.
#
gStableMajorVersion="${gStableVersion:3:2}"

#
# Stable Minor version
# For example: given gStableVersion 10.14.3, gStableMinorVersion will be 3.
#
gStableMinorVersion="${gStableVersion:6:2}"
# NOTE: It will be null when no minor version available. Appending 0.
[[ -z "${gStableMinorVersion}" ]] && gStableMinorVersion=0

#
# Package Mode:
#  Delta - a more lightweight package, should only be used on the most recent macOS.
#  Combo - a reasonably full package, could be used on all major releases, used by default.
#
#  When to use Delta (pseudo code):
#    gProductMajorVersion == gBetaMajorVersion                                                    &&
#    gProductMinorVersion == gBetaMinorVersion         [e.g update from 10.14.4 b1 to 10.14.4 b3] ||
#    gProductMinorVersion == (gBetaMinorVersion - 1)   [e.g update from 10.14.3 to 10.14.4 b3]
#===============================================================================================================
#    gProductMajorVersion == gStableMajorVersion                                                                &&
#    gProductMinorVersion == gStableMinorVersion       [e.g update from 10.14.3 to 10.14.3 supplemental update] ||
#    gProductMinorVersion == (gStableMinorVersion - 1) [e.g update from 10.14.2 to 10.14.3]
#
#  When to use Combo:
#    Of course, scenarios other than ones using Delta...
#
gPackageMode="Combo"

#----------------------------------------------------------------------------------------------------

#
# GLOBAL VARS
# WILL BE UPDATED BY CERTAIN FUNCTIONS
#
#----------------------------------------------------------------------------------------------------

# selected channel for determining URL
# will be updated by selectChannel()
gChannel=""
# selected catalog URL (see channels and catalogs above)
# will be updated by selectChannel()
gCatalog=""
# selected system version
gVersion=""
# target destination
gVolume=""

# package keys
# will be updated by parseCatalog()
gUrlIndex=""
gKeyNum=""
gKeyIndex=""
gKeySalt=""
# the exact url
gUrl=""
# distribution file
gDist=""

# initialisation of a variable (our target folder).
gTmpDirectory="/tmp"
gWorkDir=""
# name of target installer package
gInstallerPackage="installer.pkg"
# backup dir
gUserPath="${HOME}/Desktop"

#----------------------------------------------------------------------------------------------------

#
# CONSTANTS
# NO CHANGE NEEDED HERE
#
#----------------------------------------------------------------------------------------------------

# possible channels
gChannels=(
  # dev beta (pub beta has been dropped)
  "Beta"
  # normal
  "Normal Channel"
)
# possible catalog URLs (corresponding to channels above)
gCatalogs=(
  # beta
  "https://swscan.apple.com/content/catalogs/others/index-10.14seed-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz"
  # normal
  "https://swscan.apple.com/content/catalogs/others/index-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz"
)

# colors
RED="\e[31m"
GREEN="\e[32m"
DARK_YELLOW="\e[33m"
BLUE="\e[34m"
PURPLE="\e[35m"
CYAN="\e[36m"
OFF="\e[0m"

#----------------------------------------------------------------------------------------------------

function STY_LINE() {
  printf '%s\n\n' '-------------------------------------------------------------------------------'
}

function isProductBeta() {
  # if the last char of build version is a lowercased letter,
  # then it is Beta, 1 will be returned.
  local chBetaIndicator="${gProductBuildVersion:-1}"
  if [[ "${chBetaIndicator}" =~ [a-z] ]]; then
    echo 1
    return
  fi

  echo 0
}

function setPackageMode() {
  if [[ "${gChannel}" == 'Beta' ]]; then
    let "gBetaMinorVersionMinusOne   = gBetaMinorVersion   - 1"
    if [[ "${gProductMajorVersion}" -eq "${gBetaMajorVersion}" && \
          "${gProductMinorVersion}" -eq "${gBetaMinorVersion}" || \
          "${gProductMinorVersion}" -eq "${gBetaMinorVersionMinusOne}" ]]; then
        printf "\nUsing ${GREEN}Delta${OFF} package (Beta).\n\n"
        gPackageMode='Delta'
    else
      printf "\nUsing ${DARK_YELLOW}Combo${OFF} package (Beta).\n\n"
    fi
  else
    let "gStableMinorVersionMinusOne = gStableMinorVersion - 1"
    if [[ "${gProductMajorVersion}" -eq "${gStableMajorVersion}" && \
          "${gProductMinorVersion}" -eq "${gStableMinorVersion}" ]]; then
      	printf "\nUsing ${GREEN}Delta Supplemental${OFF} package (Stable).\n\n"
      	gPackageMode='DeltaSup'
    elif [[ "${gProductMajorVersion}" -eq "${gStableMajorVersion}" && \
            "${gProductMinorVersion}" -eq "${gStableMinorVersionMinusOne}" ]]; then
      	printf "\nUsing ${GREEN}Delta${OFF} package (Stable).\n\n"
      	gPackageMode='Delta'
    else
      printf "\nUsing ${DARK_YELLOW}Combo${OFF} package (Stable).\n\n"
    fi
  fi
}

function updateScript() {
 local remoteAddr="https://raw.githubusercontent.com/Gengik84/MacOS_Updater/master/MacOS_Updater"
 local tmpScriptPath="/tmp/MacOS_Updater"
 # download remote script
 rm -f "${tmpScriptPath}" && curl -s "${remoteAddr}" -o "${tmpScriptPath}" || exit 1
 local remoteVer=$(cat ${tmpScriptPath} | grep '^gScriptVersion=' | tr -cd '.0-9')

  if [[ "${gScriptVersion}" != "${remoteVer}" ]]; then
    clear
    printf "\tLocal Script Version: ${RED}${gScriptVersion}${OFF}\t\tUpdate available: ${GREEN}v${remoteVer}${OFF}\n"
    local confirmScptUpd
    read -p "Do you want update to the latest version? (y/n) " confirmScptUpd
    case "${confirmScptUpd}" in
      y|Y )
        cp "${tmpScriptPath}" "$0"
        STY_LINE

        printf "${BLUE}Running the new script...${OFF}"
        local args="$@"
        "$0" "${args}"
        # do not run the current (old) script again
        exit 0
     ;;
    esac
  fi
}

function downloadFile() {
  local link="$1"
  local out="$2"

  local attempt=0
  local maxTry=3
  for ((; attempt < maxTry; attempt++ )); do
    curl "${link}" -o "${out}"
    if [[ $? -ne 0 ]]; then
      # failed, remove the "corrupted" file and try again
      rm -rf "${out}"
    else
      # success
      return 0
    fi

    sleep 0.5
  done

  printf "Failed to download ${RED}${out}${OFF} after ${attempt} times trying!\n"
  exit 1
}

function registerScript() {
  local prefDir="${HOME}/.MacOS_Updater"
  local prefFile="${prefDir}/pref.conf"

  [[ ! -d "${prefDir}" ]] && mkdir "${prefDir}"

  if [[ -f "${prefFile}" ]]; then
    source "${prefFile}"
  else
    clear
    echo 'It seems to be the first time you are using this script,'
    echo 'for convenience, you may want to register it,'
    echo 'in this case you will no more be prompted to enter the password'
    echo 'until having been moved to elsewhere.'
    echo 'Note that it might also be a security risk when registering.'
    echo
    echo 'Well, it is totally up to you to register or not,'
    echo 'and you will be no more asked you after this time.'
    echo

    while true; do
      local confirmRegScpt
      read -p "Would you like to? (y/n) " confirmRegScpt
      case "${confirmRegScpt}" in
        y|Y )
          echo 'wantRegScpt=1' > "${prefFile}"
          break
        ;;

        n|N )
          echo 'wantRegScpt=0' > "${prefFile}"
          break
        ;;

        * )
          echo 'Try again...'
        ;;
      esac
    done
  fi

  if [[ "${wantRegScpt}" -eq 1 ]]; then
    local regFile="/etc/sudoers.d/MacOS_Updater"
    local context="$(users) ALL=(ALL) NOPASSWD: ${0}"
    if [[ ! -f "${regFile}" || \
          "$(cat ${regFile})" != "${context}" ]]; then
      rm -f "${regFile}"
      echo "${context}" | sudo tee "${regFile}" &> /dev/null
    fi
  fi
}

function selectChannel() {
  local i=0
  while true; do
    # list all possible channels
    printf "${CYAN}SELECT A CHANNEL${OFF}\n"
    for c in "${gChannels[@]}"; do
      printf "[ ${GREEN}${i}${OFF} ] ${PURPLE}${c}${OFF}\n"
      let i++
    done

    local channelNum
    read -p ">> " channelNum
    # insane choice?!
    if [[ -z "${channelNum}" || ! "${channelNum}" =~ ^[0-9]+$ || "${channelNum}" -lt 0 || "${channelNum}" -ge $i ]]; then
      printf "${RED}Wrong choice!${OFF}\n\n"
      # restore i to 0 for another traversal
      i=0
      continue
    fi

    # update vars
    gChannel="${gChannels[channelNum]}"
    gCatalog="${gCatalogs[channelNum]}"
    # ask for confirmation
    printf "Your choice: [ ${GREEN}${channelNum}${OFF} ] ${PURPLE}${gChannel}${OFF}\n"
    while true; do
      local confirmChannel
      read -p ">> CONFIRM? (Y/N) " confirmChannel
      case "${confirmChannel}" in
        "" )
          printf 'Please do type Y if you are sure.\n'
          continue
        ;;

        y|Y )
          # done, terminate the function
          return
        ;;

        n|N )
          printf 'goodbye\n'
          exit 0
        ;;

        * )
          printf 'Try again...\n\n'
          # restore i to 0 for another traversal
          i=0
          # go to the last loop
          break
        ;;
      esac
    done
  done
}

function getURLIndex() {
  local str2Grep="${1}"

  echo "$(cat /tmp/update-catalogs | \
          grep "${str2Grep}" | grep pkg | grep -v RecoveryHDUpdate | grep -v Patch | grep -v integrityData | \
          sed 's/<string>//' | sed 's/<\/string>//' | \
          awk '{print $1;}'  | sed 's/^.*downloads//')"
}

function parseCatalog() {
  selectChannel

  # show version
  if [[ "${gChannel}" == "Beta" && ! -z "${gBetaVersion}" ]]; then
    gVersion="${gBetaVersion}"
  elif [[ "${gChannel}" == "Beta" && -z "${gBetaVersion}" ]]; then
    printf "${RED}Warning:${OFF} using Beta channel but there is no release at the moment!\n"
    printf "Redirecting to ${PURPLE}Normal Channel ${OFF}and using version ${GREEN}${gStableVersion}${OFF}.\n"

    # manually put normal channel and stable version here for redirection
    gChannel="${gChannels[1]}"
    gCatalog="${gCatalogs[1]}"
    gVersion="${gStableVersion}"

    sleep 2
  elif [[ "${gChannel}" != "Beta" ]]; then
    gVersion="${gStableVersion}"
  fi

  # check if it is fine to go with Delta
  setPackageMode

  # download catalog
  cd "${gTmpDirectory}"
  [[ -f update-catalogs ]] && rm -f update-catalogs
  downloadFile "${gCatalog}" "update-catalogs.gz" && gunzip update-catalogs.gz || exit 1

  # default value for Delta
  local str2Grep="macOSUpd${gVersion}.pkg"
  case "${gPackageMode}" in
    'Combo' )
      str2Grep="macOSUpdCombo${gVersion}"
    ;;

    'DeltaSup' )
      str2Grep="macOSUpd${gVersion}Supplemental"
    ;;
  esac

  # update vars
  gUrlIndex="$(getURLIndex "${str2Grep}")"

  #
  # When upgrading, for example,
  # from 10.14.5 Beta  (18F131a)
  # to   10.14.5 Final (18F132),
  # gPackageMode would be set to DeltaSup, which is fine,
  # yet supplemental update may not exist
  # all the time, that is to say that gUrlIndex is null.
  # We are going to be back to Delta in this case and check again.
  #
  local isBeta="$(isProductBeta)"
  if [[ "${isBeta}" -eq 1               && \
        "${gPackageMode}" == 'DeltaSup' && \
        -z "${gUrlIndex}" ]]; then
    printf 'You are on Beta and going to update to supplemental update,\nbut there is nothing at the moment!\nUpdating to the latest stable version via Delta.\n\n'

    gPackageMode='Delta'

    str2Grep="macOSUpd${gVersion}.pkg"
    gUrlIndex="$(getURLIndex "${str2Grep}")"
  fi

  if [[ "${gChannel}" == "Normal Channel" && \
        -z "${gUrlIndex}"                 && \
        "${gProductVersion}" == "${gStableVersion}" ]]; then
    printf "You are already ${GREEN}up to date${OFF}!\n"
    exit 0
  fi

  [[ -z "${gUrlIndex}" ]] && printf "ERROR: ${RED}URL Index not found!${OFF}\n" && exit 1
  gKeyNum="$(  echo ${gUrlIndex} | cut -d/ -f2,3)"
  gKeyIndex="$(echo ${gUrlIndex} | cut -d/ -f4,4)"
  gKeySalt="$( echo ${gUrlIndex} | cut -d/ -f5,5)"
  gUrl="https://swdist.apple.com/content/downloads/${gKeyNum}/${gKeyIndex}/${gKeySalt}/"
  gDist="${gKeyIndex}.English.dist"

  # create our working dir
  gWorkDir="${gTmpDirectory}/${gKeyIndex}"
  [[ ! -d "${gWorkDir}" ]] && mkdir "${gWorkDir}"
  cd "${gWorkDir}"
  # download dist
  rm -f "${gDist}"
  printf "Downloading: ${BLUE}${gDist}${OFF}...\n"
  downloadFile "${gUrl}${gDist}" "${gDist}"

  # show build
  local awkNR=0
  [[ "${gChannel}" == "Beta" && ! -z "${gBetaVersion}" ]] && awkNR=13 || awkNR=12
  build="$(cat ${gDist} | \
           sed 's/<string>//' | sed 's/<\/string>//' | \
           awk '{print $1;}'  | awk NR==${awkNR})"

  confirmDownload
}

function confirmDownload() {
  while true; do
    printf "${RED}${gVersion} (${build})${OFF} will be downloaded, confirm? (y/n) "
    local confirmVer
    read -ea confirmVer
    case "${confirmVer}" in
      "" )
        printf 'Please do type Y if you are sure.\n'
        continue
      ;;

      y|Y )
        # done, terminate the function
        return
      ;;

      n|N )
        rm -rf "${gWorkDir}"
        printf 'goodbye\n'
        exit 0
      ;;

      * )
        printf 'Try again...\n\n'
        continue
      ;;
    esac
  done
}

function downloadPackage() {
  # now we are inside "${gWorkDir}" thanks to the prior parseCatalog()

  # packages to be downloaded
  if [[ "${gPackageMode}" == 'Combo' ]]; then
    # Looks like only 10.14.4 has Auto suffixed and others do not.
    # TODO: find when Auto exists and when not.
    if [[ "${gVersion}" == '10.14.4' ]]; then
      local targetFiles=(
        "macOSUpdCombo${gVersion}Auto.pkg"
        "macOSUpdCombo${gVersion}Auto.RecoveryHDUpdate.pkg"
        "macOSBrain.pkg"
        "SecureBoot.pkg"
        "EmbeddedOSFirmware.pkg"
        "FirmwareUpdate.pkg"
        "FullBundleUpdate.pkg"
      )
    else
      local targetFiles=(
        "macOSUpdCombo${gVersion}.pkg"
        "macOSUpdCombo${gVersion}.RecoveryHDUpdate.pkg"
        "macOSBrain.pkg"
        "SecureBoot.pkg"
        "EmbeddedOSFirmware.pkg"
        "FirmwareUpdate.pkg"
        "FullBundleUpdate.pkg"
      )
    fi
  elif [[ "${gPackageMode}" == 'DeltaSup' ]]; then
    local targetFiles=(
      "macOSUpd${gVersion}Supplemental.pkg"
      "macOSUpd${gVersion}Supplemental.RecoveryHDUpdate.pkg"
      "macOSBrain.pkg"
      "SecureBoot.pkg"
      "EmbeddedOSFirmware.pkg"
      "FirmwareUpdate.pkg"
      "FullBundleUpdate.pkg"
    )
  else
    local targetFiles=(
      "macOSUpd${gVersion}.pkg"
      "macOSUpd${gVersion}.RecoveryHDUpdate.pkg"
      "macOSBrain.pkg"
      "SecureBoot.pkg"
      "EmbeddedOSFirmware.pkg"
      "FirmwareUpdate.pkg"
      "FullBundleUpdate.pkg"
    )
  fi

  # download them
  clear
  for filename in "${targetFiles[@]}"; do
    if [[ ! -e "${filename}" ]]; then
      printf "Downloading: ${BLUE}${filename}${OFF}...\n"
      downloadFile "${gUrl}${filename}" "${filename}"
    elif [[ -z "$(pkgutil --check-signature ${filename} | grep -i 'signed Apple Software')" ]]; then
      printf "File: ${filename} is ${RED}CORRUPTED${OFF}, redownloading...\n"
      rm -rf "${filename}"
      downloadFile "${gUrl}${filename}" "${filename}"
    else
      printf "File: ${GREEN}${filename}${OFF} is already there, ${DARK_YELLOW}skipping download.${OFF}\n"
    fi

    STY_LINE
  done
}

function selectDestination() {
  cd /Volumes

  local i=0
  local targetVolumes=(*)

  while true; do
    printf "${CYAN}SELECT A DESTINATION${OFF}\n"
    for vol in "${targetVolumes[@]}"; do
      printf "[ ${GREEN}${i}${OFF} ] ${CYAN}${vol}${OFF}\n"
      let i++
    done

    # ask to select a target volume
    local volNum
    read -p ">> " volNum
    # insane choice?!
    if [[ -z "${volNum}" || ! "${volNum}" =~ ^[0-9]+$ || "${volNum}" -lt 0 || "${volNum}" -ge $i ]]; then
      printf "${RED}Wrong choice!${OFF}\n\n"
      # restore i to 0 for another traversal
      i=0
      continue
    fi

    local volume="${targetVolumes[volNum]}"
    gVolume="/Volumes/${volume}"
    printf "Your choice: [ ${GREEN}${volNum}${OFF} ] ${CYAN}${volume}${OFF}\n"
    while true; do
      local confirmVol
      read -p ">> CONFIRM? (y/n) " confirmVol
      case "${confirmVol}" in
        "" )
          printf 'Please do type Y if you are sure.\n'
          continue
        ;;

        y|Y )
          # done, terminate the function
          return
        ;;

        * )
          printf 'Try again...\n\n'
          # restore i to 0 for another traversal
          i=0
          # go to the last loop
          break
        ;;
      esac
    done
  done

  # create the enrollment plist
  local seedEnrollmentPlist="${vol}/Users/Shared/.SeedEnrollment.plist"
  # write enrollement plist when missing (seed program options: CustomerSeed, DeveloperSeed)
  if [[ ! -f "${seedEnrollmentPlist}" ]]; then
    echo '<?xml version="1.0" encoding="UTF-8"?>'                                                                  >  "${seedEnrollmentPlist}"
    echo '<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' >> "${seedEnrollmentPlist}"
    echo '<plist version="1.0">'                                                                                   >> "${seedEnrollmentPlist}"
    echo '  <dict>'                                                                                                >> "${seedEnrollmentPlist}"
    echo '    <key>SeedProgram</key>'                                                                              >> "${seedEnrollmentPlist}"
    if [[ "${gChannel}" == "Beta" ]]; then
      echo '    <string>DeveloperSeed</string>'                                                                    >> "${seedEnrollmentPlist}"
    else
      echo '    <string>CustomerSeed</string>'                                                                     >> "${seedEnrollmentPlist}"
    fi
    echo '  </dict>'                                                                                               >> "${seedEnrollmentPlist}"
    echo '</plist>'                                                                                                >> "${seedEnrollmentPlist}"
  fi
}

function updateRecovery() {
  printf "${CYAN}UPDATING RECOVERY${OFF}\n"

  cd "${gTmpDirectory}"
  rm -rf "updRec" && mkdir "updRec" && cd "updRec"

  # download dm for updating Recovery
  curl -O "https://raw.githubusercontent.com/Gengik84/MacOS_Updater/master/dm" && chmod +x dm || exit 1
  local dmBin="${gTmpDirectory}/updRec/dm"

  if [[ "${gPackageMode}" == 'Combo' ]]; then
    # Looks like only 10.14.4 has Auto suffixed and others do not.
    # TODO: find when Auto exists and when not.
    if [[ "${gVersion}" == '10.14.4' ]]; then
      local recPkg="${gWorkDir}/macOSUpdCombo${gVersion}Auto.RecoveryHDUpdate.pkg"
    else
      local recPkg="${gWorkDir}/macOSUpdCombo${gVersion}.RecoveryHDUpdate.pkg"
    fi
  elif [[ "${gPackageMode}" == 'DeltaSup' ]]; then
    local recPkg="${gWorkDir}/macOSUpd${gVersion}Supplemental.RecoveryHDUpdate.pkg"
  else
    local recPkg="${gWorkDir}/macOSUpd${gVersion}.RecoveryHDUpdate.pkg"
  fi
  [[ ! -f "${recPkg}" ]] && printf "ERROR: ${RED}${recPkg} NOT found!${OFF}\n" && exit 1

  local tmpRecUpdDir="/tmp/RecoveryHDUpdate"
  rm -rf "${tmpRecUpdDir}" && pkgutil --expand-full "${recPkg}" "${tmpRecUpdDir}"
  local tmpDmg="${tmpRecUpdDir}/RecoveryHDMeta.dmg"
  # attempt to create a temporary mount point
  local tmpMountPoint="$(/usr/bin/mktemp -d)"
  # mount tmpDmg there
  /usr/bin/hdiutil attach -nobrowse "${tmpDmg}" -mountpoint "${tmpMountPoint}"

  # probe target volume
  local fsType="$(diskutil info "${gVolume}" | awk '$1 == "Type" { print $NF }')"
  # start updating
  if [[ "${fsType}" == "apfs" ]]; then
    "${dmBin}" ensureRecoveryBooter "${gVolume}" -base "${tmpMountPoint}/BaseSystem.dmg" "${tmpMountPoint}/BaseSystem.chunklist" -diag "${tmpMountPoint}/AppleDiagnostics.dmg" "${tmpMountPoint}/AppleDiagnostics.chunklist" -diagmachineblacklist 0 -installbootfromtarget 0 -slurpappleboot 0 -delappleboot 0 -addkernelcoredump 0
  else
    "${dmBin}" ensureRecoveryPartition "${gVolume}" "${tmpMountPoint}/BaseSystem.dmg" "${tmpMountPoint}/BaseSystem.chunklist" "${tmpMountPoint}/AppleDiagnostics.dmg" "${tmpMountPoint}/AppleDiagnostics.chunklist" 0 0 0
  fi

  # done
  /usr/bin/hdiutil eject "${tmpMountPoint}"
  rm -rf "${tmpMountPoint}"
  rm -rf "${tmpRecUpdDir}"
}

function updatePrimarySystem() {
  printf "${CYAN}UPDATING PRIMARY SYSTEM${OFF}\n"

  cd "${gWorkDir}"

  # remove these lines to avoid installer error
  sed -e '/volume-check/d' "${gDist}"      > new1.txt
  sed -e '/installation-check/d' new1.txt  > new2.txt
  sed -e '/RecoveryHDUpdate/d'   new2.txt  > new3.txt
  sed -e '/system-image/d'       new3.txt  > new4.txt
  # remove macOS${gVersion}Patch.pkg since it will cause installation failure
  sed -e '/Patch/d'              new4.txt  > new5.txt
  cp new5.txt "${gDist}"
  rm new?.txt

  # build pkg
  productbuild --distribution "${gWorkDir}/${gDist}" --package-path "${gWorkDir}" "${gWorkDir}/${gInstallerPackage}"

  # perform the update by launching the installer
  [[ -f "${gWorkDir}/${gInstallerPackage}" ]] && /usr/sbin/installer -pkg "${gWorkDir}/${gInstallerPackage}" -target "${gVolume}"
}

function updateSystem() {
  # update Recovery first
  updateRecovery
  # then primary system
  updatePrimarySystem
}

function ask4Backup() {
  local confirmBackup
  read -p "Do you want to backup installer.pkg? (y/N) " confirmBackup
  case "${confirmBackup}" in
    y|Y )
      printf "${CYAN}BACKING UP${OFF}\n"
      hdiutil create -format UDZO -srcfolder "${gWorkDir}/${gInstallerPackage}" "${gUserPath}/Installer_${gVersion}_${build}"
    ;;
  esac
}

function endProgram() {
  printf "${CYAN}CLEANING UP${OFF}\n"

  rm -rf "${gWorkDir}"
  rm -rf "${gTmpDirectory}/update-catalogs"
  rm -rf "${gTmpDirectory}/updRec"

  STY_LINE
  printf "${GREEN}RESTART REQUIRED${OFF}\n"
  local confirmReboot
  read -p "Do you want to reboot now? (y/N) " confirmReboot
  case "${confirmReboot}" in
    y|Y )
      reboot
    ;;
  esac

  printf "${RED}ALL DONE${OFF}\nPlease reboot manually.\n"
}

function main() {
  updateScript "$@"

  # try to register the script
  registerScript

  # direct update from a pre-built pkg
  case "$1" in
    -i|--installer )
      selectDestination
      local installerPKG
      read -p "Drag installer.pkg on terminal windows: " installerPKG
      [[ ! -f "${installerPKG}" ]] && echo "${installerPKG} not found!" && exit 1
      printf "${CYAN}UPDATING PRIMARY SYSTEM${OFF}\n"
      /usr/sbin/installer -pkg "${installerPKG}" -target "${gVolume}"

      endProgram
      # done
      exit 0
    ;;
  esac

  parseCatalog
  downloadPackage

  selectDestination
  updateSystem

  ask4Backup

  endProgram
}

clear

if [[ $EUID -ne 0 ]]; then
  echo "$(basename "$0") must be run as ROOT!"
  sudo "$0" "$@"
else
  main "$@"
fi