#!/bin/sh

#
# By Gengik84
#
# 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

#
# Tip: In case you run into the ERROR_7E7AEE96CA error then you need to change the ProductVersion and 
# in /System/Library/CoreServices/SystemVersion.plist to 10.13
#

#
# GLOBAL VARS (some of them will be updated by parseCatalog() )
#
###################################################

# possible channels
channels=(
  # dev
  "Developer Beta"
  # pub
  "Public Beta"
  # normal
  "Normal Channel"
)

# the exact url based on channels
catalogURL=""

# keys for downloading pkg
urlIndex=""
keyNum=""
keyIndex=""
keySalt=""

# packages to be downloaded
targetFiles=()

# url where the packages live
url=""
# distribution
distribution=""

# our target volume
targetVolume=""

###################################################

function printInfo(){
  printf "\e[1m${1}\e[0;1m"
}  

function printInfo_Blue(){
  printf "\033[1;34;5m${1}\033[0m\n"
} 

function printInfo_Green(){
  printf "\e[32;1m${1}\e[0m\n"
}

function ResetColor(){
  printf "\e[0m${1}\e[1m\n"
}

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


#
# You may need VolumeCheck() to return true (and thus skip checks)
#
export __OS_INSTALL=1

#
# Skip firmware update.
#
export __FIRMWARE_UPDATE_OPTOUT

#
# Do not convert target HFS volume to APFS (OSInstaller).
#
export __APFS_OPTOUT=1

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

#
# Initialisation of a variable (our target folder).
#
tmpDirectory="/tmp"

#
# Directory for Backup
#
userPath="/Users/`users`/Desktop"

#
# Name of target installer package
#
installerPackage="installer.pkg"


function selectChannel() {
  local catalogs=(
    # dev
    "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"
    # pub
    "https://swscan.apple.com/content/catalogs/others/index-10.14beta-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"
  )
 

  local i=0
  while true; do
    # list all possible channels
    for channel in "${channels[@]}"; do
      echo "[ $i ] ${channel}"
      let i++ 
    done

    read -p "Please select a channel: " channelNum
    # insane choice?!
    if [[ "${channelNum}" -lt 0 || "${channelNum}" -ge $i ]]; then
      echo "Wrong choice!"
      # restore i
      i=0
      # try again
      continue
    fi
    # update the global var
    catalogURL="${catalogs[${channelNum}]}"
    echo "Your choice is: [ ${channelNum} ] ${channels[${channelNum}]},"
    read -p "Right? (Y/N) " confirmChannel
    case "${confirmChannel}" in
      y | Y )
        break
      ;;

      * )
        echo "Try again..."
        # restore i
        i=0
        # try again
        continue
      ;;
    esac
  done
}

function selectVersion() {
  STY_LINE

  local versions=(
    "10.14"
    "10.14.1"
    # add more releases when they come out (e.g 10.14.2) 
  )

  local i=0
  local ver=""
  while true; do
    # list all possible releases
    for version in "${versions[@]}"; do
      echo "[ $i ] ${version}"
      let i++ 
    done

    printf "\n%s\n" "Example: Select [0] for ${versions[0]}"
    read -p "Please select a version: " versionNum
    # insane choice?!
    if [[ "${versionNum}" -lt 0 || "${versionNum}" -ge $i ]]; then
      echo "Wrong choice!"
      # restore i
      i=0
      # try again
      continue
    fi
    ver="${versions[${versionNum}]}"
    echo "Your choice is: [ ${versionNum} ] ${versions[${versionNum}]},"
    read -p "Right? (Y/N) " confirmVer
    case "${confirmVer}" in
      y | Y )
        break
      ;;

      * )
        echo "Try again..."
        # restore i
        i=0
        # try again
        continue
      ;;
    esac
  done

  # workaround for the local var "ver"
  echo "${ver}" > "${tmpDirectory}/ver.txt"
}

function parseCatalog() {
  selectChannel
  # ver.txt will be created for storing var "ver"
  selectVersion

  cd "${tmpDirectory}"
  local ver="$(cat ver.txt)"

  # download catalog
  curl "${catalogURL}" -o update-catalogs.gz
  [[ $? -ne 0 ]] && echo "FAILED!" && exit 1
  # decompress it
  gunzip update-catalogs.gz

  # parse the catalog and update corresponding vars
  urlIndex="$(cat /tmp/update-catalogs | \
              grep macOSUpd${ver} | grep pkg | grep -v RecoveryHDUpdate | grep -v Patch | grep -v integrityData | \
              sed 's/<string>//' | sed 's/<\/string>//' | \
              awk '{print $1;}' | awk NR==1 | sed 's/^.*downloads//')"
  keyNum="$(echo ${urlIndex} | cut -d/ -f2,3)"
  keyIndex="$(echo ${urlIndex} | cut -d/ -f4,4)"
  keySalt="$(echo ${urlIndex} | cut -d/ -f5,5)"
  # now we are also able to determine which packages to be downloaded
  targetFiles=(
    "FirmwareUpdate.pkg"
    "FullBundleUpdate.pkg"
    "EmbeddedOSFirmware.pkg"
    "macOSUpd${ver}.pkg"
    "macOSUpd${ver}.Patch.pkg"
    "macOSUpd${ver}.RecoveryHDUpdate.pkg"
    "macOSBrain.pkg"
    "SecureBoot.pkg"
  )
  # time to update the url index and distribution
  url="https://swdist.apple.com/content/downloads/${keyNum}/${keyIndex}/${keySalt}/"
  distribution="${keyIndex}.English.dist"
}

function checkDir() {
  #
  # Check target directory.
  #
  if [ ! -d "${tmpDirectory}/${keyIndex}" ]
    then
      mkdir "${tmpDirectory}/${keyIndex}"
      cd "${tmpDirectory}/${keyIndex}"
  fi
}

function downloadDist() {
  #
  # Download distribution file
  #
  if [ ! -e "${tmpDirectory}/${keyIndex}/${distribution}" ];
    then
      printInfo "Downloading: ${distribution} ...\n"
      curl "${url}${distribution}" -o "${tmpDirectory}/${keyIndex}/${distribution}"
    else
      printInfo_Green "File: ${distribution} already there, skipping download.\n"
  fi
  ResetColor
}

function confirmUpdate() {
  STY_LINE

  cd "${tmpDirectory}/${keyIndex}"

  local ver="$(cat ${tmpDirectory}/ver.txt)"
  local awkNR=0
  if [[ "${ver}" == "10.14" ]]; then
    awkNR=14
  else
    # FIXME: now only for 10.14.1, how will 10.14.2 be? lets see.
    awkNR=13
  fi
  local latestBuild="$(cat ${distribution} | awk NR==${awkNR} | sed 's/<string>//' | sed 's/<\/string>//'  2>&1 )"
  printInfo "Latest Build: (${channels[channelNum]})" && printInfo_Green "${latestBuild}"

  STY_LINE

  read -p "Confirm the update to this build (please do press Y if yes)? (Y/N) " confirmUpdate
  case "${confirmUpdate}" in
    y|Y ) ;;

    * )
      exit 0
    ;;
  esac
}

function downloadPkg() {
  #
  # Change to working directory (otherwise it will fail to locate the packages).
  #
  cd "${tmpDirectory}/${keyIndex}"

  #
  # Download target files.
  #
  for filename in "${targetFiles[@]}"
    do
      if [ ! -e "${tmpDirectory}/${keyIndex}/${filename}" ];
        then
          STY_LINE
          printInfo "Downloading: ${filename} ...\n"
          curl "${url}${filename}" -o "${tmpDirectory}/${keyIndex}/${filename}"
        else
          STY_LINE
          printInfo_Green "File: ${filename} already there, skipping download.\n"
      fi
    done
}

function selectTarget() {
  #
  # Change to Volumes folder.
  #
  cd /Volumes

  #
  # Collect available target volume names.
  #
  local i=0
  targetVolumes=(*)
  while true; do
    printInfo "\nAvailable target volumes:\n\n"
    for volume in "${targetVolumes[@]}"
      do
        echo "\t[ $i ] ${volume}"
        let i++
      done
    echo ""

    #
    # Ask to select a target volume.
    #
    read -p "Select a target volume for the boot file: " volumeNumber
    # insane choice?!
    if [[ "${volumeNumber}" -lt 0 || "${volumeNumber}" -ge $i ]]; then
      echo "Wrong choice!"
      # restore i
      i=0
      # try again
      continue
    fi
    #
    # Path to target volume.
    #
    targetVolume="/Volumes/${targetVolumes[$volumeNumber]}"
    echo "Your choice is: [ ${volumeNumber} ] ${targetVolumes[${volumeNumber}]},"
    read -p "Right? (Y/N) " confirmVol
    case "${confirmVol}" in
      y | Y )
        break
      ;;

      * )
        echo "Try again..."
        # restore i
        i=0
        # try again
        continue
      ;;
    esac
  done

  #
  # Path to enrollment plist.
  #
  local seedEnrollmentPlist="${targetVolume}/Users/Shared/.SeedEnrollment.plist"
  #
  # Write enrollement plist when missing (seed program options: CustomerSeed, DeveloperSeed or PublicSeed).
  #
  if [ ! -e "${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}"
      echo '    <string>DeveloperSeed</string>'                                   >> "${seedEnrollmentPlist}"
      echo '  </dict>'                                                >> "${seedEnrollmentPlist}"
      echo '</plist>'                                                 >> "${seedEnrollmentPlist}"
  fi
}

function updateRecovery() {
  echo "Updating Recovery HD..."

  cd "${tmpDirectory}"
  local ver="$(cat ver.txt)"
  rm -rf updRec
  mkdir "updRec" && cd "updRec"

  # download dm for updating Recovery
  curl -O "https://raw.githubusercontent.com/Gengik84/MacOS_Updater/master/dm"
  [[ $? -ne 0 ]] && echo "FAILED to download dm!" && exit 1
  # fix permissions
  chmod +x dm

  # check for macOSUpd10.14.RecoveryHDUpdate.pkg
  local recoverHDPackage="${tmpDirectory}/${keyIndex}/macOSUpd${ver}.RecoveryHDUpdate.pkg"
  if [[ ! -f "${recoverHDPackage}" ]]; then
    echo "Package for updating Recovery HD not found!"
    exit 1
  fi

  # Thanks crazybirdy for the following code: (slightly modified)
  #===============================================================

  ###### crazybirdy add ######
  /bin/rm -rf /tmp/RecoveryHDUpdate
  pkgutil --expand-full "${recoverHDPackage}" /tmp/RecoveryHDUpdate
  local PACKAGE_PATH="/tmp/RecoveryHDUpdate/RecoveryHDMeta.dmg"
  local TARGET="${targetVolume}"
  local dmbin="${tmpDirectory}/updRec/dm"
  ###### crazybirdy add ######

  echo
  echo "Attempting to create temporary mount point"
  local MOUNT_POINT="$(/usr/bin/mktemp -d)"

  echo
  echo "Attempting mount of ${PACKAGE_PATH} to ${MOUNT_POINT}"
  echo
  /usr/bin/hdiutil attach -nobrowse "${PACKAGE_PATH}" -mountpoint "${MOUNT_POINT}"
  echo

  echo "Probing Target Volume: ${TARGET}"
  FS_TYPE=$(diskutil info "${TARGET}" | awk '$1 == "Type" { print $NF }')
  echo "Target Volume FS: ${FS_TYPE}"
  echo
  if [[ "${FS_TYPE}" == "apfs" ]]; then
    echo "Running ensureRecoveryBooter for APFS target volume: ${TARGET}"
    "${dmbin}" ensureRecoveryBooter "${TARGET}" -base "${MOUNT_POINT}/BaseSystem.dmg" "${MOUNT_POINT}/BaseSystem.chunklist" -diag "${MOUNT_POINT}/AppleDiagnostics.dmg" "${MOUNT_POINT}/AppleDiagnostics.chunklist" -diagmachineblacklist 0 -installbootfromtarget 0 -slurpappleboot 0 -delappleboot 0 -addkernelcoredump 0
  else
    echo "Running ensureRecoveryPartition for Non-APFS target volume: ${TARGET}"
    "${dmbin}" ensureRecoveryPartition "${TARGET}" "${MOUNT_POINT}/BaseSystem.dmg" "${MOUNT_POINT}/BaseSystem.chunklist" "${MOUNT_POINT}/AppleDiagnostics.dmg" "${MOUNT_POINT}/AppleDiagnostics.chunklist" 0 0 0
  fi

  echo
  echo "Eject ${MOUNT_POINT}"
  /usr/bin/hdiutil eject "${MOUNT_POINT}"
  echo "Delete ${MOUNT_POINT}"
  /bin/rm -rf "${MOUNT_POINT}"

  ###### crazybirdy add ######
  /bin/rm -rf /tmp/RecoveryHDUpdate
  ###### 10.14.x end ######
  ###### 10.14.x end ######

  #===============================================================
}

function updateBaseSystem() {
  printInfo_Blue "Building Installer...\n"
  cd "${tmpDirectory}/${keyIndex}"
  #
  # remove these lines to avoid installer error
  #
  cp "${keyIndex}".English.dist "${keyIndex}".English.dist-org
  sed -e '/volume-check/d' "${keyIndex}".English.dist > 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
  sed -e '/Patch/d' new4.txt > new5.txt
  cp new5.txt "${keyIndex}".English.dist
  rm "${keyIndex}".English.dist-org
  rm new?.txt
  
  #
  # Create an installer.pkg
  #
  productbuild --distribution "${tmpDirectory}/${keyIndex}/${distribution}" --package-path "${tmpDirectory}/${keyIndex}" "${tmpDirectory}/${keyIndex}"/"${installerPackage}"
  
  #
  # Launch the installer. (Update the system)
  #
  if [ -e "${tmpDirectory}/${keyIndex}/${installerPackage}" ]
    then
    printInfo_Blue "Running installer ...\n"
    /usr/sbin/installer -pkg "${tmpDirectory}/${keyIndex}/${installerPackage}" -target "${targetVolume}"
  fi
}

function updateSystem() {
  # update Recovery first
  updateRecovery

  # then base system
  updateBaseSystem
}

function askForBackup() {
  read -p "Do you want to backup installer.pkg? (y/N) " confirmBackup
  case "${confirmBackup}" in
    y|Y )
      # Creating DMG
      printInfo_Blue "Creating dmg for installer backup..." && printInfo_Green "Please wait..."
      hdiutil create -format UDZO -srcfolder "${tmpDirectory}/${keyIndex}/${installerPackage}" "${userPath}"/Installer_"${keyIndex}"
    ;;
  esac
}

function endProgram(){
  printInfo_Blue "Cleaning temp files..."
  rm -rf "${tmpDirectory}/${keyIndex}" 2>/dev/null
  rm -rf "${tmpDirectory}"/update-catalogs 2>/dev/null
  rm -rf "${tmpDirectory}"/updRec 2>/dev/null
  rm -rf "${tmpDirectory}"/ver.txt 2>/dev/null

  STY_LINE
  printInfo "Requires system reboot..."
}

function askReboot(){
  read -p "Do you want to reboot now? (y/N) " confirmReboot
  case "${confirmReboot}" in
    y | Y )
      reboot
    ;;

    * )
      printf "\n%s\n" "Reboot Aborted."
    ;;
  esac
}

function main() {
  case "$1" in
    "-i"|"-installer" )
      # for use installer.pkg already created..
      selectTarget
      read -p "Drag installer.pkg on terminal windows" installerPKG
      [[ ! -f "${installerPKG}" ]] && echo "${installerPKG} not found!" && exit 1   
      printInfo_Blue "Running installer ...\n" 
      /usr/sbin/installer -pkg "${installerPKG}" -target "${targetVolume}"

      askReboot
      exit
    ;;           
  esac

  parseCatalog
  checkDir
  downloadDist
  confirmUpdate
  downloadPkg
  selectTarget
  updateSystem
  askForBackup
  endProgram
  askReboot 
}


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

exit 0
