Create_macos_recovery.sh

Autor: rtrouton

#!/bin/bash

# This script uses a macOS 10.13 and later installer application to repair the Recovery volume (APFS) or partition (HFS+).
# 
# Based on the following script:
# https://gist.github.com/jonathantneal/f20e6f3e03d5637f983f8543df70cef5


# Provide custom colors in Terminal for status and error messages

msg_status() {
	echo -e "\033[0;32m-- $1\033[0m"
}
msg_error() {
	echo -e "\033[0;31m-- $1\033[0m"
}

# Explanation of how the script works

usage() {
	cat <<EOF
Usage:
$(basename "$0") "/path/to/Install macOS [Name].app"

Description:
This script uses a macOS 10.13 and later installer application to repair the Recovery volume (APFS) or partition (HFS+).
Requirements:

Compatible macOS installer application for the Mac's installed OS.
Account with the ability to run commands using sudo, to allow specific functions to run with root privileges.

EOF
}

# Set the macOS installer path as a variable.

macOS_installer="$1"
mount_point="$macOS_installer/Contents/SharedSupport"

# Remove trailing slashes from input paths if needed

macOS_installer=${macOS_installer%%/}

# Check to see if the path to the macOS installer application has been provided
# as part of running the script.

if [[ -z "$1" ]] || [[ ! -d "$1" ]]; then
    msg_error "The path to the macOS installer application is required as the first argument."
    usage
	exit 1
else
    msg_status "macOS installer is $macOS_installer"
fi

# Check for the OS information of both the macOS installer application
# and that of the Mac this script is running on.

macos_version=$(/usr/bin/sw_vers -productVersion)
macos_version_digits_only=$(echo "$macos_version" | awk -F'[^0-9]*' '$0=$1$2$3')
installer_version=$(/usr/libexec/PlistBuddy -c 'Print :System\ Image\ Info:version' "$macOS_installer/Contents/SharedSupport/InstallInfo.plist")
installer_version_digits_only=$(echo $installer_version | awk -F'[^0-9]*' '$0=$1$2$3')
installer_qualifies=$(echo $installer_version_digits_only | head -c4)
installer_mounted_volume=$(echo "$macOS_installer" | grep -o 'Install.*' | sed 's/....$//')

# If the installed OS is only four digits long, add a zero to the end.

if [[ $(echo -n "$macos_version_digits_only" | wc -m | tr -d '[:space:]') = "4" ]]; then
   macos_version_digits_only="$macos_version_digits_only"0
fi

# If the installer's OS version is only four digits long, add a zero to the end.

if [[ $(echo -n "$installer_version_digits_only" | wc -m | tr -d '[:space:]') = "4" ]]; then
   installer_version_digits_only="$installer_version_digits_only"0
fi

# Define download URL and name for the RecoveryHDUpdate installer package
# which includes the tools needed to rebuild the Recovery volume or partition.

recovery_download="http://swcdn.apple.com/content/downloads/63/02/061-90748-A_MQ4MTMMXYW/jtw6sxlorttnetfsu9wpu0rqubsa55bskp/SecUpd2020-002HighSierra.RecoveryHDUpdate.pkg"
recovery_package="SecUpd2020-002HighSierra.RecoveryHDUpdate.pkg"

# Detect if the macOS installer application is running macOS 10.13.x or later. If the
# macOS installer application is for 10.12.x or earlier, stop and display an error message.

if [[ "$installer_qualifies" -lt 1013 ]]; then
    msg_error "This script supports repairing Recovery for macOS 10.13.0 and later."
    msg_error "Please use an installer app which installs macOS 10.13.0 or later."
	exit 1
else
    msg_status "Installer application for macOS $installer_version detected. Proceeding...."
fi

if [[ "$installer_version_digits_only" -lt "$macos_version_digits_only" ]]; then
    msg_error "The macOS installer app provided installs $installer_version's Recovery, which is earlier than macOS $macos_version"
    msg_error "Please use an installer app which installs macOS $macos_version or later."
    usage
	exit 1
fi

# Identify the target disk

boot_drive=$(/usr/sbin/diskutil info "$(bless --info --getBoot)" | awk -F':' '/Volume Name/ { print $2 }' | sed -e 's/^[[:space:]]*//')
msg_status "Target disk is ${boot_drive}."

# Detect the target disk's filesystem type (APFS or HFS+)

filesystem_type=$(/usr/sbin/diskutil info "$boot_drive" | awk '$1 == "Type" { print $NF }')
msg_status "Target filesystem is ${filesystem_type}."

msg_status "Downloading $recovery_package into /private/tmp"
/usr/bin/curl "$recovery_download" --progress-bar -L -o /private/tmp/"$recovery_package"
pkgutil --expand /private/tmp/"$recovery_package" /private/tmp/recoveryupdate"$installer_version"

if [[ -d /private/tmp/recoveryupdate"$installer_version" ]]; then
   return_code=0
  if [[ "${filesystem_type}" == "apfs" ]]; then
	msg_status "Running ensureRecoveryBooter for APFS target volume: ${boot_drive}"
	/private/tmp/recoveryupdate"$installer_version"/Scripts/Tools/dm ensureRecoveryBooter "$boot_drive" -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
    return_code=$(($return_code + $?))
      if [[ "$return_code" == 0 ]]; then
         msg_status "Successfully created $installer_version Recovery volume on ${boot_drive}."
      else
         msg_error "Failed to create $installer_version Recovery volume on ${boot_drive}."
         msg_error "Recovery tools returned the following non-zero exit code: $return_code"
         exit $return_code
      fi
  elif [[ "${filesystem_type}" == "hfs" ]]; then
	msg_status "Running ensureRecoveryPartition for non-APFS target volume: ${boot_drive}"
	/private/tmp/recoveryupdate"$installer_version"/Scripts/Tools/dm ensureRecoveryPartition "$boot_drive" "$mount_point/BaseSystem.dmg" "$mount_point/BaseSystem.chunklist" "$mount_point/AppleDiagnostics.dmg" "$mount_point/AppleDiagnostics.chunklist" 0 0 0
	return_code=$(($return_code + $?))
      if [[ "$return_code" == 0 ]]; then
         msg_status "Successfully created $installer_version Recovery partition on ${boot_drive}."
      else
         msg_error "Failed to create $installer_version Recovery partition on ${boot_drive}."
         msg_error "Recovery tools returned the following non-zero exit code: $return_code"
         exit $return_code
      fi
  else
	msg_error "Failed to create $installer_version Recovery partition on ${boot_drive}."
	msg_error "${boot_drive} does not have an APFS or HFS+ filesystem."
  fi
else
   msg_error "Unable to locate the following directory: /private/tmp/recoveryupdate"$installer_version""
   exit 1
fi

# Clean up
/bin/rm -rf /private/tmp/"$recovery_package"
/bin/rm -rf /private/tmp/recoveryupdate"$installer_version"