Skip to content

Instantly share code, notes, and snippets.

@kepek
Forked from dakanji/Install_USB_Maker.sh
Created August 10, 2023 13:02
Show Gist options
  • Save kepek/b1c92fd24269c45fd227e04f7a01e2f4 to your computer and use it in GitHub Desktop.
Save kepek/b1c92fd24269c45fd227e04f7a01e2f4 to your computer and use it in GitHub Desktop.
Creates Bootable USB for Mac OS Lion to Monterey (Excluding Mavericks)
#!/usr/bin/env bash
# Install_USB_Maker.sh
# Copyright (c) 2020 - 2023 Dayo Akanji
# - dakanji@users.sourceforge.net
# Portions Copyright (c) Jeff Geerling
# - https://github.com/geerlingguy/macos-virtualbox-vm/blob/master/LICENSE
#
# MIT License
# Derived from:
# * https://apple.stackexchange.com/a/389830
# * https://github.com/geerlingguy/macos-virtualbox-vm/blob/master/prepare-iso.sh
#
# 1. Download Lion to Monterey (Excluding Mavericks)
# * From : https://support.apple.com/HT211683 (Lion to Sierra or Links to App Store)
# * : https://github.com/corpnewt/gibMacOS (High Sierra to Monterey)
# 2. Use Disk Utility to format a USB stick partition:
# * Name : Install_USB_Maker
# * Size : Easiest to assume 16 GB but see details below
# * : 08 GB (Min) For Lion to Sierra (if via Install DMG)
# * : 16 GB (Min) For Yosemite to Sierra (if via Installer App)
# * : 16 GB (Min) For High Sierra to Monterey (Always Installer App)
# * Format : Mac OS Extended (Journaled)
# 3. Make the downloaded "Install" package available to this script
# * For Lion to Sierra:
# * Mount the downloaded "Install DMG" file
# * Close the Finder window for the DMG if displayed
# * For High Sierra to Monterey:
# * Place the install app in the 'Applications' folder
# * This can be done manually or by running the downloaded package
# * Placing manually in the folder containing this script also works
# * Installer Apps in the folder containing this script take precedence
# * For Yosemite to Sierra (Alternative Option):
# * Place the install app in the 'Applications' folder
# * This can be done manually or by running the downloaded package
# * Placing manually in the folder containing this script also works
# * Installer Apps in the folder containing this script take precedence
# 4. Execute "chmod +x /Path/to/this/script/file" without quotes and then run this script file
# 5. Install Mac OS with the USB Installer created by the script
# * Name : {{MAC_OS_NAME}} Installer
## CUSTOM COLOURS ##
msg_status() {
echo -e "\033[0;35m$1\033[0m"
}
msg_info() {
echo -e "\033[0;33m$1\033[0m"
}
msg_error() {
echo -e "\033[0;31m$1\033[0m"
}
# createISO by Jeff Geerling as modified by Dayo Akanji
# This function creates the ISO image.
# Inputs: $1 = The name of the installer - located in Applications folder or local folder/PATH.
# $2 = The Name of the ISO to be created.
function createISO() {
# Realign 'TMP_STR' as changed before calling this function
TMP_STR='NotSet'
if [ $# -eq 2 ] ; then
local installerAppName
local isoName
local error
local SparseImageFile
local InstallAppVol
local InstallAppBaseSystem
if [ -e "${SCRIPT_DIR}/${installerAppName}" ] ; then
installerAppName="${SCRIPT_DIR}/${1}"
elif [ -e "/Applications/${installerAppName}" ] ; then
installerAppName="/Applications/${1}"
else
echo; echo
msg_info 'ERROR!!'
msg_error "Could Not Find Installer App}"
runExit 'Error' ;
fi
isoName="${2}"
error=0
SparseImageFile="${INSTALL_TEMP}/${isoName}.sparseimage"
InstallAppVol='/Volumes/install_app'
InstallAppBaseSystem="${installerAppName}/Contents/SharedSupport/BaseSystem"
sudo -v
msg_status "STEP 01 of 16: Create a Blank 16GB Single Partition Apple Partition Map ISO Image"
# Create INSTALL_TEMP folder
mkdir -p "${INSTALL_TEMP}"
# Just in case - delete any previous sparseimage
[ -e "${SparseImageFile}" ] && rm -f "${SparseImageFile}"
# increased size to 16G - 8G is too small for Catalina
hdiutil create -o "${INSTALL_TEMP}/${isoName}" -size 16g -layout SPUD -fs HFS+J -type SPARSE
sudo -v; echo
msg_status "STEP 02 of 16: Mount Installer Image"
if [ "${isoName}" == "Monterey" ] || [ "${isoName}" == "BigSur" ] ; then
hdiutil attach "${SparseImageFile}" -noverify -nobrowse -mountpoint "${InstallAppVol}"
elif [ -e "${SCRIPT_DIR}/${installerAppName}" ] ; then
hdiutil attach "${installerAppName}/Contents/SharedSupport/InstallESD.dmg" -noverify -nobrowse -mountpoint "${InstallAppVol}"
error=$?
else
error=1
fi
if [ ${error} -ne 0 ] ; then
echo; echo
msg_info 'ERROR!!'
msg_error "Failed to mount the InstallESD.dmg from the installer at ${installerAppName}"
msg_error "Exiting on 'Error Code: ${error}'"
runExit 'Error' ;
fi
sudo -v; echo
msg_status "STEP 03 of 16: Mount Sparse Bundle for Package Addition"
if [ "${isoName}" == "Monterey" ] || [ "${isoName}" == "BigSur" ] ; then
echo "N/A ... Skipped for ${INSTALL_NAME}"
else
hdiutil attach "${SparseImageFile}" -noverify -nobrowse -mountpoint '/Volumes/install_build'
fi
sudo -v; echo
msg_status "STEP 04 of 16: Restore Base System into '${isoName}' ISO Image"
if [ "${isoName}" == "Monterey" ] || [ "${isoName}" == "BigSur" ] ; then
echo "N/A ... Skipped for ${INSTALL_NAME}"
elif [ "${isoName}" == "Catalina" ] || [ "${isoName}" == "Mojave" ] || [ "${isoName}" == "HighSierra" ] ; then
# Note by Jeff Geerling
#following asr command returns an error and prints:
#"Personalization succeeded"
#"asr: Couldn't personalize volume /Volumes/macOS Base System - Operation not permitted"
#I disabled SIP and the error still occurs.
#This was reported in Issue #73 for Mojave
#I added ||true for now to prevent the script from exiting as the steps that follow still seem to work fine for Catalina
sudo asr restore -source "${InstallAppBaseSystem}.dmg" -target '/Volumes/install_build' -noprompt -noverify -erase || true
else
sudo asr restore -source "${InstallAppVol}/BaseSystem.dmg" -target '/Volumes/install_build' -noprompt -noverify -erase
fi
sudo -v; echo
msg_status "STEP 05 of 16: Remove Package Link and Replace with Actual Files"
if [ "${isoName}" == "Monterey" ] || [ "${isoName}" == "BigSur" ] ; then
echo "N/A ... Skipped for ${INSTALL_NAME}"
elif [ "${isoName}" == "Catalina" ] || [ "${isoName}" == "Mojave" ] || [ "${isoName}" == "HighSierra" ] ; then
ditto -V "${InstallAppVol}/Packages" "${VOL_BASE_DIR}/System/Installation/"
else
rm -v "${VOL_BASE_DIR}/System/Installation/Packages"
cp -rpv "${InstallAppVol}/Packages" "${VOL_BASE_DIR}/System/Installation/"
fi
sudo -v; echo
msg_status "STEP 06 of 16: Copy '${INSTALL_STICK}' Dependencies"
msg_status 'NB: Type passwword and press "Enter" if prompted'
msg_status ' Nothing will display when typing passwword'
if [ "${isoName}" == "Monterey" ] || [ "${isoName}" == "BigSur" ] ; then
hsudo "${installerAppName}/Contents/Resources/createinstallmedia" --volume "${InstallAppVol}" --nointeraction
elif [ "${isoName}" == "Catalina" ] || [ "${isoName}" == "Mojave" ] || [ "${isoName}" == "HighSierra" ] ; then
ditto -V "${InstallAppBaseSystem}.chunklist" "${VOL_BASE_SYS}.chunklist"
ditto -V "${InstallAppBaseSystem}.dmg" "${VOL_BASE_SYS}.dmg"
else
cp -rpv "${InstallAppVol}/BaseSystem.chunklist" "${VOL_BASE_SYS}.chunklist"
cp -rpv "${InstallAppVol}/BaseSystem.dmg" "${VOL_BASE_SYS}.dmg"
fi
sudo -v; echo
msg_status "STEP 07 of 16: Unmount Installer Image"
if [ "${isoName}" == "Monterey" ] || [ "${isoName}" == "BigSur" ] ; then
hdiutil detach "/Volumes/Install macOS ${INSTALL_NAME}" -force # NOTE: force because "Resource busy"
else
hdiutil detach "${InstallAppVol}"
fi
sudo -v; echo
msg_status 'STEP 08 of 16: Unmount Sparse Bundle'
if [ "${isoName}" == "Monterey" ] || [ "${isoName}" == "BigSur" ] ; then
echo "N/A ... Skipped for ${INSTALL_NAME}"
else
hdiutil detach "${VOL_BASE_DIR}/"
fi
sudo -v; echo
msg_status 'STEP 09 of 16: Resize Sparse Bundle to Remove Free Space'
hdiutil resize -size `hdiutil resize -limits "${SparseImageFile}" | tail -n 1 | awk '{ print $1 }'`b "${SparseImageFile}"
sudo -v; echo
msg_status "STEP 10 of 16: Convert '${isoName}' Sparse Bundle to ISO/CD Master"
hdiutil convert "${SparseImageFile}" -format UDTO -o "${INSTALL_TEMP}/${isoName}"
sudo -v; echo
msg_status 'STEP 11 of 16: Remove Sparse Bundle'
rm -v "${SparseImageFile}"
sudo -v; echo
msg_status 'STEP 12 of 16: Convert ISO/CD Master to Regular Image'
mv -v "${INSTALL_TEMP}/${isoName}.cdr" "${INSTALL_TEMP}/${isoName}.iso"
sudo -v; echo
msg_status "STEP 13 of 16: Get Disk ID for '${INSTALL_ID}' ..."
DiskID=$( diskutil info "${INSTALL_ID}" | grep 'Device Node' )
TmpDiskInfo=${DiskID##*/}
DiskID=${TmpDiskInfo##*( )}
echo "Disk ID = '${DiskID}'"
sudo -v; echo
msg_status "STEP 14 of 16: Create a bootable installer in '${INSTALL_ID}' ..."
msg_status 'NB: This takes a while to complete'
msg_status ' Type passwword and press "Enter" if prompted'
msg_status ' Nothing will display when typing passwword'
sudo dd if="${INSTALL_TEMP}/${isoName}.iso" of="/dev/r${DiskID}" bs=1m
sudo -v; echo
msg_status "STEP 15 of 16: Rename '${INSTALL_ID}' as '${INSTALL_STICK}' ..."
msg_status 'NB: Type passwword and press "Enter" if prompted'
msg_status ' Nothing will display when typing passwword'
sudo diskutil rename "${INSTALL_ID}" "${INSTALL_STICK}"
sudo -v; echo
msg_status "STEP 16 of 16: Finishing"
msg_status 'NB: This takes a while to complete'
cd "${HOME}" || true
rm -rf "${INSTALL_TEMP}" || true
diskutil eject "${INSTALL_STICK}" || true
runExit ;
fi
}
# installerExists by Jeff Geerling
# Returns 0 if the installer was found locally or in '/Applications'. 1 if not.
function installerExists() {
local installerAppName
local result
installerAppName="${1}"
result=1
if [ -e "${SCRIPT_DIR}/${installerAppName}" ] ; then
result=0
elif [ -e "/Applications/${installerAppName}" ] ; then
result=0
fi
return ${result}
}
## EXIT HANDLER ##
runExit() {
flag=${1:-Norm}
if [ "${flag}" == 'Norm' ] ; then
echo; echo
msg_info 'All Done!'
fi
echo; echo
msg_info '################################'
msg_info ' ## Create Mac OS Install USB ##'
msg_info ' ################################'
echo; echo
exit 0
}
## ERROR HANDLER ##
runErr() {
echo; echo
msg_info "Runtime Error!!"
msg_error "Could Not Create USB Installer"
echo; echo
rm -rf "${INSTALL_TEMP}"
exit 1
}
trap runErr ERR
##
## Start Procedural Code
##
INSTALL_STICK='NotSet'
INSTALL_BASIC='Install OS X'
INSTALL_NAME='NotSet'
INSTALL_TEMP='/tmp/VersionOSX'
INSTALL_BASE='OS X Base System'
INSTALL_APP='Mac OS Install'
INSTALL_ESD='/Volumes/TMP_ESD'
INSTALL_ID='Install_USB_Maker'
INSTALL_X='InstallMacOSX'
TYPE_OSX="macOS"
TMP_STR='NotSet'
USE_APP='NotSet'
VOL_BASE_SYS='NotSet'
VOL_BASE_DIR='NotSet'
# Resolve TMP_SOURCE until the file is no longer a symlink
TMP_SOURCE=${BASH_SOURCE[0]}
while [ -L "${TMP_SOURCE}" ]; do
DIR=$( cd -P "$( dirname "${TMP_SOURCE}" )" >/dev/null 2>&1 && pwd )
TMP_SOURCE=$(readlink "${TMP_SOURCE}")
# if ${TMP_SOURCE} is relative symlink, resolve relative to symlink file location
[[ ${TMP_SOURCE} != /* ]] && TMP_SOURCE=${DIR}/${TMP_SOURCE}
done
SCRIPT_DIR=$( cd -P "$( dirname "${TMP_SOURCE}" )" >/dev/null 2>&1 && pwd )
if [ ! -d "/Volumes/${INSTALL_ID}" ] ; then
err_msg="Connect a correctly sized USB stick labelled as '${INSTALL_ID}' and rerun this script."
echo; echo
msg_info 'ERROR!!'
msg_error "${err_msg}"
echo; echo
exit 1
fi
sudo clear
echo; echo
msg_info ' ################################'
msg_info ' ## Create Mac OS Install USB ##'
msg_info '################################'
echo; echo
msg_info '## STAGE 1 of 4: Confirm Install Target ##'
msg_info '------------------------------------------'
msg_status 'STEP 01 of 02: Get Mac OS Target'
PS3='Enter Corresponding Number for Mac OS Version or Exit: '
options=("Monterey" "Big Sur" "Catalina" "Mojave" "High Sierra" "Sierra" "El Capitan" "Yosemite" "Mavericks" "Mountain Lion" "Lion" "Exit")
select opt in "${options[@]}"
do
case $opt in
"Monterey" | "Big Sur" | "Catalina" | "Mojave" | "High Sierra")
INSTALL_NAME="${opt}"
USE_APP='True'
break
;;
"Sierra")
INSTALL_X='InstallOS'
INSTALL_NAME="${opt}"
USE_APP='Maybe'
INSTALL_BASIC='Install macOS'
INSTALL_APP="Install Mac OS ${INSTALL_NAME}.app"
break
;;
"El Capitan" | "Yosemite")
INSTALL_NAME="${opt}"
USE_APP='Maybe'
INSTALL_APP="Install Mac OS X ${INSTALL_NAME}.app"
break
;;
"Mountain Lion" | "Lion")
INSTALL_NAME="${opt}"
USE_APP='False'
INSTALL_BASE='Mac OS X Base System'
INSTALL_BASIC='Install Mac OS X'
INSTALL_APP="Install OS X ${INSTALL_NAME}.app"
break
;;
"Mavericks")
TMP_STR='Exit'
INSTALL_NAME="${opt}"
break
;;
"Exit")
TMP_STR='Exit'
break
;;
*)
echo; echo
msg_info 'ERROR!!'
msg_error "Invalid Selection:- '${REPLY}'"
echo; echo
exit 1
;;
esac
done
echo
msg_status "STEP 02 of 02: Returned '${INSTALL_NAME}'"
if [ "${TMP_STR}" == 'Exit' ] ; then
if [ "${INSTALL_NAME}" == 'Mavericks' ] ; then
echo; echo
msg_info 'NOTE'
msg_status "Mavericks Installer is not Supported"
fi
runExit 'Exit' ;
fi
TMP_STR='NotSet'
INSTALL_STICK="${INSTALL_NAME} Installer"
echo
msg_info 'Completed Stage 1'
echo; echo
msg_info '## STAGE 2 of 4: Confirm Prerequisites ##'
msg_info '-----------------------------------------'
gotError="False"
msg_status "STEP 01 of 01: Validate Misc ..."
# Check if using installer app for Yosemite to Sierra
if [ "${USE_APP}" == 'Maybe' ] ; then
USE_APP='False'
if [ "${INSTALL_NAME}" == 'Sierra' ] ; then
TMP_STR='macOS'
elif [ "${INSTALL_NAME}" == 'El Capitan' ] || [ "${INSTALL_NAME}" == 'Yosemite' ] ; then
TMP_STR='OS X'
fi
if [ "${TMP_STR}" != 'NotSet' ] ; then
if installerExists "Install ${TMP_STR} ${INSTALL_NAME}.app" ; then
USE_APP='True'
fi
fi
fi
TMP_STR='NotSet'
if [ "${USE_APP}" == 'False' ] ; then
# Ensure DMG is mounted
if [ ! -d "/Volumes/${INSTALL_BASIC}" ] ; then
err_msg="Mount the '${INSTALL_X}.dmg' file and rerun this script."
gotError="True"
fi
else
# Note by Jeff Geerling
# Eject installer disks in case opened after download from App Store
# grep "partition_scheme" because "partition" finds too many lines
for disk in $(hdiutil info | grep /dev/disk | grep partition_scheme | cut -f 1); do
hdiutil detach -force ${disk}
done
fi
# Error/Exit Handling
if [ "${gotError}" == 'True' ] ; then
echo; echo
msg_info 'ERROR!!'
msg_error "${err_msg}"
runExit 'Error' ;
fi
echo
msg_info 'Completed Stage 2'
if [ "${USE_APP}" == 'True' ] ; then
echo; echo
msg_info '## STAGE 3 of 4: Find Installer ##'
msg_info '----------------------------------'
msg_status "STEP 01 of 01: Checking for Installer App ..."
if [[ "${INSTALL_NAME}" == "Monterey" || "${INSTALL_NAME}" == "Big Sur" || "${INSTALL_NAME}" == "Catalina" || "${INSTALL_NAME}" == "Mojave" ]] ; then
if installerExists "Install macOS ${INSTALL_NAME}.app" ; then
TMP_STR='macOS'
fi
elif [[ "${INSTALL_NAME}" == "High Sierra" || "${INSTALL_NAME}" == "Sierra" ]] ; then
# DA-TAG: The Sierras are odd with 'macOS' app name but 'OS X' elsewhere
if installerExists "Install macOS ${INSTALL_NAME}.app" ; then
TMP_STR='OS X'
fi
elif [[ "${INSTALL_NAME}" == "El Capitan" || "${INSTALL_NAME}" == "Yosemite" ]] ; then
if installerExists "Install OS X ${INSTALL_NAME}.app" ; then
TMP_STR='OS X'
fi
fi
if [ "${TMP_STR}" == 'NotSet' ] ; then
echo; echo
msg_info 'ERROR!!'
msg_error "Could not find valid installer app file"
runExit 'Error' ;
fi
echo "Found Installer App for ${INSTALL_NAME}"
VOL_BASE_DIR="/Volumes/${TMP_STR} Base System"
VOL_BASE_SYS="${VOL_BASE_DIR}/BaseSystem"
TMP_STR='NotSet'
echo
msg_info 'Completed Stage 3'
echo; echo
msg_info '## STAGE 4 of 4: Create Bootable USB ##'
msg_info '---------------------------------------'
# Remove Spaces in 'INSTALL_NAME' and use as ISO name
# TMP_STR is realinged in createISO
TMP_STR=$( echo "${INSTALL_NAME}" | sed 's/ //g' )
sudo -v; createISO "Install ${TYPE_OSX} ${INSTALL_NAME}.app" "${TMP_STR}"
else
echo; echo
msg_info '## STAGE 3 of 4: Deconstruct Installer ##'
msg_info '-----------------------------------------'
msg_status "STEP 01 of 05: Clearing ${INSTALL_TEMP} ..."
rm -rfv "${INSTALL_TEMP}"
sudo -v; echo
msg_status "STEP 02 of 05: Creating ${INSTALL_TEMP} ..."
msg_info 'NB: This takes a while to complete'
pkgutil --expand "/Volumes/${INSTALL_BASIC}/${INSTALL_X}.pkg" "${INSTALL_TEMP}"
sudo -v; echo
msg_status "STEP 03 of 05: Ejecting ${INSTALL_BASIC} ..."
diskutil eject "${INSTALL_BASIC}"
sudo -v; echo
msg_status 'STEP 04 of 05: Preparing and Attaching InstallESD DMG ...'
hdiutil attach "${INSTALL_TEMP}/${INSTALL_X}.pkg/InstallESD.dmg" -noverify -nobrowse -mountpoint "${INSTALL_ESD}"
sudo -v; echo
msg_status 'STEP 05 of 05: Preparing and Aligning BaseSystem DMG ...'
msg_info 'NB: This takes a while to complete'
msg_info ' Type passwword and press "Enter" if prompted'
msg_info ' Nothing will display when typing passwword'
sudo asr restore -source "${INSTALL_ESD}/BaseSystem.dmg" -target "/Volumes/${INSTALL_ID}" -noprompt -noverify -erase || true
echo
msg_info 'Completed Stage 3'
echo; echo
msg_info '## STAGE 4 of 4: Repackage Installer ##'
msg_info '---------------------------------------'
msg_status "STEP 01 of 09: Renaming ${INSTALL_BASE} ..."
diskutil rename "${INSTALL_BASE}" "${INSTALL_STICK}"
sudo -v; echo
msg_status 'STEP 02 of 09: Clearing Installation Packages ...'
rm -v "/Volumes/${INSTALL_STICK}/System/Installation/Packages"
sudo -v; echo
msg_status 'STEP 03 of 09: Reconstituting ESD Packages ...'
msg_info 'NB: This takes a while to complete'
cp -rpv "${INSTALL_ESD}/Packages" "/Volumes/${INSTALL_STICK}/System/Installation"
sudo -v; echo
msg_status 'STEP 04 of 09: Reconstituting BaseSystem Chunklist ...'
cp -rpv "${INSTALL_ESD}/BaseSystem.chunklist" "/Volumes/${INSTALL_STICK}/"
sudo -v; echo
msg_status 'STEP 05 of 09: Reconstituting BaseSystem DMG ...'
msg_info 'NB: This takes a while to complete'
cp -rpv "${INSTALL_ESD}/BaseSystem.dmg" "/Volumes/${INSTALL_STICK}/"
sudo -v; echo
msg_status 'STEP 06 of 09: Detaching InstallESD DMG ...'
hdiutil detach "${INSTALL_ESD}" || true
sudo -v; echo
msg_status 'STEP 07 of 09: Relabelling Volume ...'
msg_status 'NB: Type passwword and press "Enter" if prompted'
msg_status ' Nothing will display when typing passwword'
sudo bless --folder "/Volumes/${INSTALL_STICK}/System/Library/CoreServices" --label "${INSTALL_STICK}"
sudo -v; echo
msg_status 'STEP 08 of 09: Updating VolumeIcon ...'
InstallIcon="/Volumes/${INSTALL_STICK}/${INSTALL_APP}/Contents/Resources/InstallAssistant.icns"
if [ -f "${InstallIcon}" ]; then
cp -v "${InstallIcon}" "/Volumes/${INSTALL_STICK}/.VolumeIcon.icns"
else
echo 'Skipped ... VolumeIcon not found'
fi
sudo -v; echo
msg_status 'STEP 09 of 09: Finishing ...'
msg_info 'NB: This takes a while to complete'
cd "${HOME}" || true
rm -rf "${INSTALL_TEMP}" || true
diskutil eject "${INSTALL_STICK}" || true
echo
msg_info 'Completed Stage 4'
runExit ;
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment