Files
macos-virtualbox/macos-guest-virtualbox.sh
2020-05-28 02:11:16 +03:00

1570 lines
70 KiB
Bash
Executable File

#!/bin/bash
# Push-button installer of macOS on VirtualBox
# (c) myspaghetti, licensed under GPL2.0 or higher
# url: https://github.com/myspaghetti/macos-virtualbox
# version 0.93.5
# Dependencies: bash coreutils gzip unzip wget xxd dmg2img
# Supported versions:
# VirtualBox with Extension Pack >= 6.1.6
# GNU bash >= 4.3, GNU coreutils >= 8.22,
# GNU gzip >= 1.5, GNU wget >= 1.14,
# Info-ZIP unzip >= 6.0, xxd >= 1.7,
# dmg2img >= 1.6.5
function set_variables() {
# Customize the installation by setting these variables:
vm_name="macOS" # name of the VirtualBox virtual machine
macOS_release_name="Catalina" # install "HighSierra" "Mojave" or "Catalina"
storage_size=80000 # VM disk image size in MB, minimum 22000
storage_format="vdi" # VM disk image file format, "vdi" or "vmdk"
cpu_count=2 # VM CPU cores, minimum 2
memory_size=4096 # VM RAM in MB, minimum 2048
gpu_vram=128 # VM video RAM in MB, minimum 34, maximum 128
resolution="1280x800" # VM display resolution
# The following commented commands, when executed on a genuine Mac,
# may provide the values for NVRAM and other parameters required by iCloud,
# iMessage, and other connected Apple applications.
# Parameters taken from a genuine Mac may result in a "Call customer support"
# message if they do not match the genuine Mac exactly.
# Non-genuine yet genuine-like parameters usually work.
# system_profiler SPHardwareDataType
DmiSystemFamily="MacBook Pro" # Model Name
DmiSystemProduct="MacBookPro11,2" # Model Identifier
DmiSystemSerial="NO_DEVICE_SN" # Serial Number (system)
DmiSystemUuid="CAFECAFE-CAFE-CAFE-CAFE-DECAFFDECAFF" # Hardware UUID
DmiOEMVBoxVer="string:1" # Apple ROM Info
DmiOEMVBoxRev="string:.23456" # Apple ROM Info
DmiBIOSVersion="string:MBP7.89" # Boot ROM Version
# ioreg -l | grep -m 1 board-id
DmiBoardProduct="Mac-3CBD00234E554E41"
# nvram 4D1EDE05-38C7-4A6A-9CC6-4BCCA8B38C14:MLB
DmiBoardSerial="NO_LOGIC_BOARD_SN"
MLB="${DmiBoardSerial}"
# nvram 4D1EDE05-38C7-4A6A-9CC6-4BCCA8B38C14:ROM
ROM='%aa*%bbg%cc%dd'
# ioreg -l -p IODeviceTree | grep \"system-id
SYSTEM_UUID="aabbccddeeff00112233445566778899"
# csrutil status
SYSTEM_INTEGRITY_PROTECTION='10' # '10' - enabled, '77' - disabled
# Additional configurations may be saved in external files and loaded with the
# following command prior to executing the script:
# export macos_vm_vars_file=/path/to/variable_assignment_file
# "variable_assignment_file" is a plain text file that contains zero or more
# lines with a variable assignment for any variable specified above.
[[ -r "${macos_vm_vars_file}" ]] && source "${macos_vm_vars_file}"
}
# welcome message
function welcome() {
echo -ne "\n${highlight_color}Push-button installer of macOS on VirtualBox${default_color}
This script installs only open-source software and unmodified Apple binaries,
and requires about ${highlight_color}45GB${default_color} of available storage, of which 25GB are for temporary
installation files that may be deleted when the script is finished.
The script interacts with the virtual machine twice, ${highlight_color}please do not interact${default_color}
${highlight_color}with the virtual machine manually${default_color} before the script is finished.
Documentation about optional configuration, ${highlight_color}iCloud and iMessage connectivity${default_color},
resuming the script by stages, and other topics can be viewed with the
following command:
"
would_you_like_to_know_less
echo -ne "\n${highlight_color}Press enter to review the script configuration.${default_color}"
clear_input_buffer_then_read
function pad_to_33_chars() {
local padded="${1} "
echo -n "${padded:0:33}"
}
# custom settings prompt
echo -e "\nvm_name=\"${vm_name}\""
pad_to_33_chars "macOS_release_name=\"${macOS_release_name}\""
echo "# install \"HighSierra\" \"Mojave\" or \"Catalina\""
pad_to_33_chars "storage_size=${storage_size}"
echo "# VM disk image size in MB. minimum 22000"
pad_to_33_chars "storage_format=\"${storage_format}\""
echo "# VM disk image file format, \"vdi\" or \"vmdk\""
pad_to_33_chars "cpu_count=${cpu_count}"
echo "# VM CPU cores, minimum 2"
pad_to_33_chars "memory_size=${memory_size}"
echo "# VM RAM in MB, minimum 2048"
pad_to_33_chars "gpu_vram=${gpu_vram}"
echo "# VM video RAM in MB, minimum 34, maximum 128"
pad_to_33_chars "resolution=\"${resolution}\""
echo "# VM display resolution"
echo -ne "\nThese values may be customized as described in the documentation.\n
${highlight_color}Press enter to continue, CTRL-C to exit.${default_color}"
clear_input_buffer_then_read
}
# check dependencies
function check_shell() {
if [[ "${SHELL}" =~ /bash ]]; then
if [[ -z "${BASH_VERSION}" ]]; then
echo "Can't determine BASH_VERSION. Exiting."
exit
elif [[ ! ( "${BASH_VERSION:0:1}" -ge 4 ) ]]; then
echo "Please execute this script with Bash 4.3 or higher."
if [[ -n "$(sw_vers 2>/dev/null)" ]]; then
echo "macOS detected. Make sure the script is not executed with"
echo "the default /bin/bash which is version 3."
fi
exit
elif [[ ! ( "${BASH_VERSION:0:1}" -ge 5
|| "${BASH_VERSION:0:3}" =~ 4\.[3-9]
|| "${BASH_VERSION:0:4}" =~ 4\.[12][0-9] ) ]]; then
echo "Please execute this script with Bash 4.3 or higher."
exit
fi
elif [[ "${SHELL}" =~ /zsh ]]; then
if [[ -z "${ZSH_VERSION}" ]]; then
echo "Can't determine ZSH_VERSION."
if [[ -n "${BASH_VERSION}" ]]; then
echo "The script appears to be executed on bash inside zsh."
echo "Please explicitly execute the script with zsh or edit the #! at the top of the"
echo "script so it points to the zsh executable."
fi
echo "Exiting."
exit
elif [[ ! ( "${ZSH_VERSION:0:1}" -ge 6
|| "${ZSH_VERSION:0:3}" =~ 5\.[5-9]
|| "${ZSH_VERSION:0:4}" =~ 5\.[1-4][0-9] ) ]]; then
echo "Please execute this script with zsh version 5.5 or higher."
exit
fi
# make zsh parse the script (almost) like bash
setopt extendedglob sh_word_split ksh_arrays posix_argzero nullglob
else
echo "Can't determine SHELL. Exiting."
exit
fi
}
function check_gnu_coreutils_prefix() {
if [[ -n "$(gcsplit --help 2>/dev/null)" ]]; then
function base64() {
gbase64 "$@"
}
function csplit() {
gcsplit "$@"
}
function expr() {
gexpr "$@"
}
function ls() {
gls "$@"
}
function od() {
god "$@"
}
function split() {
gsplit "$@"
}
function tac() {
gtac "$@"
}
fi
}
function check_dependencies() {
# check environment for macOS and non-GNU coreutils
if [[ -n "$(sw_vers 2>/dev/null)" ]]; then
# Add Homebrew GNU coreutils to PATH if path exists
homebrew_gnubin="/usr/local/opt/coreutils/libexec/gnubin"
if [[ -d "${homebrew_gnubin}" ]]; then
PATH="${homebrew_gnubin}:${PATH}"
fi
# if csplit isn't GNU variant, exit
if [[ -z "$(csplit --help 2>/dev/null)" ]]; then
echo -e "\nmacOS detected.\nPlease use a package manager such as ${highlight_color}homebrew${default_color}, ${highlight_color}pkgsrc${default_color}, ${highlight_color}nix${default_color}, or ${highlight_color}MacPorts${default_color}"
echo "Please make sure the following packages are installed and that"
echo "their path is in the PATH variable:"
echo -e "${highlight_color}bash coreutils dmg2img gzip unzip wget xxd${default_color}"
echo "Please make sure Bash and coreutils are the GNU variant."
exit
fi
fi
# check for xxd, gzip, unzip, coreutils, wget
if [[ -z "$(echo "xxd" | xxd -p 2>/dev/null)" ||
-z "$(gzip --help 2>/dev/null)" ||
-z "$(unzip -hh 2>/dev/null)" ||
-z "$(csplit --help 2>/dev/null)" ||
-z "$(wget --version 2>/dev/null)" ]]; then
echo "Please make sure the following packages are installed:"
echo "coreutils gzip unzip xxd wget"
echo "Please make sure the coreutils and gzip packages are the GNU variant."
exit
fi
# wget supports --show-progress from version 1.16
regex='1\.1[6-9]|1\.[2-9][0-9]' # for zsh compatibility
if [[ "$(wget --version 2>/dev/null | head -n 1)" =~ ${regex} ]]; then
wgetargs="--quiet --continue --show-progress" # pretty
else
wgetargs="--continue" # ugly
fi
# VirtualBox in ${PATH}
# Cygwin
if [[ -n "$(cygcheck -V 2>/dev/null)" ]]; then
if [[ -n "$(cmd.exe /d /s /c call VBoxManage.exe -v 2>/dev/null)" ]]; then
function VBoxManage() {
cmd.exe /d /s /c call VBoxManage.exe "$@"
}
else
cmd_path_VBoxManage='C:\Program Files\Oracle\VirtualBox\VBoxManage.exe'
echo "Can't find VBoxManage in PATH variable,"
echo "checking ${cmd_path_VBoxManage}"
if [[ -n "$(cmd.exe /d /s /c call "${cmd_path_VBoxManage}" -v 2>/dev/null)" ]]; then
function VBoxManage() {
cmd.exe /d /s /c call "${cmd_path_VBoxManage}" "$@"
}
echo "Found VBoxManage"
else
echo "Please make sure VirtualBox version 6.0 or higher is installed, and that"
echo "the path to the VBoxManage.exe executable is in the PATH variable, or assign"
echo "in the script the full path including the name of the executable to"
echo -e "the variable ${highlight_color}cmd_path_VBoxManage${default_color}"
exit
fi
fi
# Windows Subsystem for Linux (WSL)
elif [[ "$(cat /proc/sys/kernel/osrelease 2>/dev/null)" =~ [Mm]icrosoft ]]; then
if [[ -n "$(VBoxManage.exe -v 2>/dev/null)" ]]; then
function VBoxManage() {
VBoxManage.exe "$@"
}
else
wsl_path_VBoxManage='/mnt/c/Program Files/Oracle/VirtualBox/VBoxManage.exe'
echo "Can't find VBoxManage in PATH variable,"
echo "checking ${wsl_path_VBoxManage}"
if [[ -n "$("${wsl_path_VBoxManage}" -v 2>/dev/null)" ]]; then
PATH="${PATH}:${wsl_path_VBoxManage%/*}"
function VBoxManage() {
VBoxManage.exe "$@"
}
echo "Found VBoxManage"
else
echo "Please make sure VirtualBox is installed on Windows, and that the path to the"
echo "VBoxManage.exe executable is in the PATH variable, or assigned in the script"
echo -e "to the variable \"${highlight_color}wsl_path_VBoxManage${default_color}\" including the name of the executable."
exit
fi
fi
# everything else (not cygwin and not wsl)
elif [[ -z "$(VBoxManage -v 2>/dev/null)" ]]; then
echo "Please make sure VirtualBox version 6.0 or higher is installed,"
echo "and that the path to the VBoxManage executable is in the PATH variable."
exit
fi
# VirtualBox version
vbox_version="$(VBoxManage -v 2>/dev/null)"
vbox_version="${vbox_version//[$'\r\n']/}"
if [[ -z "${vbox_version}" || -z "${vbox_version:2:1}" ]]; then
echo "Can't determine VirtualBox version. Exiting."
exit
elif [[ ! ( "${vbox_version:0:1}" -gt 5
|| "${vbox_version:0:3}" =~ 5\.2 ) ]]; then
echo -e "\nPlease make sure VirtualBox version 5.2 or higher is installed."
echo "Exiting."
exit
elif [[ "${vbox_version:0:1}" = 5 ]]; then
echo -e "\n${highlight_color}VirtualBox version ${vbox_version} detected.${default_color} Please see the following"
echo -ne "URL for issues with the VISO filesystem on VirtualBox 5.2 to 5.2.40:\n\n"
echo " https://github.com/myspaghetti/macos-virtualbox/issues/86"
echo -ne "\n${highlight_color}Press enter to continue, CTRL-C to exit.${default_color}"
clear_input_buffer_then_read
fi
# Oracle VM VirtualBox Extension Pack
extpacks="$(VBoxManage list extpacks 2>/dev/null)"
if [[ "$(expr match "${extpacks}" '.*Oracle VM VirtualBox Extension Pack')" -le "0" ||
"$(expr match "${extpacks}" '.*Usable:[[:blank:]]*false')" -gt "0" ]]; then
echo "Please make sure Oracle VM VirtualBox Extension Pack is installed, and that"
echo "all installed VirtualBox extensions are listed as usable when"
echo "executing the command \"VBoxManage list extpacks\""
exit
fi
# dmg2img
if [[ -z "$(dmg2img -d 2>/dev/null)" ]]; then
if [[ -z "$(cygcheck -V 2>/dev/null)" ]]; then
echo "Please install the package dmg2img."
exit
elif [[ -z "$("${PWD}/dmg2img.exe" -d 2>/dev/null)" ]]; then
echo "Locally installing dmg2img"
wget "http://vu1tur.eu.org/tools/dmg2img-1.6.6-win32.zip" \
${wgetargs} \
--output-document="dmg2img-1.6.6-win32.zip"
if [[ ! -s dmg2img-1.6.6-win32.zip ]]; then
echo "Error downloading dmg2img. Please provide the package manually."
exit
fi
unzip -oj "dmg2img-1.6.6-win32.zip" "dmg2img.exe"
rm "dmg2img-1.6.6-win32.zip"
chmod +x "dmg2img.exe"
fi
fi
# set Apple software update catalog URL according to macOS version
HighSierra_sucatalog='https://swscan.apple.com/content/catalogs/others/index-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog'
Mojave_sucatalog='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'
Catalina_sucatalog='https://swscan.apple.com/content/catalogs/others/index-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog'
if [[ "${macOS_release_name:0:1}" =~ [Cc] ]]; then
macOS_release_name="Catalina"
CFBundleShortVersionString="10.15"
sucatalog="${Catalina_sucatalog}"
if [[ ! ( "${vbox_version:0:1}" -gt 6 ||
"${vbox_version}" =~ ^6\.1\.[4-9] ||
"${vbox_version}" =~ ^6\.1\.[123][0-9] ||
"${vbox_version}" =~ ^6\.[2-9] ) ]]; then
echo -e "\nmacOS Catalina requires VirtualBox version 6.1.4 or higher."
echo "Exiting."
exit
fi
elif [[ "${macOS_release_name:0:1}" =~ [Hh] ]]; then
macOS_release_name="HighSierra"
CFBundleShortVersionString="10.13"
sucatalog="${HighSierra_sucatalog}"
else
macOS_release_name="Mojave"
CFBundleShortVersionString="10.14"
sucatalog="${Mojave_sucatalog}"
fi
print_dimly "${macOS_release_name} selected to be downloaded and installed"
}
# Done with dependencies
function prompt_delete_existing_vm() {
print_dimly "stage: prompt_delete_existing_vm"
if [[ -n "$(VBoxManage showvminfo "${vm_name}" 2>/dev/null)" ]]; then
echo -e "\nA virtual machine named \"${vm_name}\" already exists."
echo -ne "${warning_color}Delete existing virtual machine \"${vm_name}\"?${default_color}"
prompt_delete_y_n
if [[ "${delete}" == "y" ]]; then
echo "Deleting ${vm_name} virtual machine."
VBoxManage unregistervm "${vm_name}" --delete
else
echo -e "\n${highlight_color}Please assign a different VM name to variable \"vm_name\" by editing the script,${default_color}"
echo "or skip this check manually as described when executing the following command:"
would_you_like_to_know_less
exit
fi
fi
}
# Attempt to create new virtual machine named "${vm_name}"
function create_vm() {
print_dimly "stage: create_vm"
if [[ -n "$( VBoxManage createvm --name "${vm_name}" --ostype "MacOS1013_64" --register 2>&1 >/dev/null )" ]]; then
echo -e "\nError: Could not create virtual machine \"${vm_name}\"."
echo -e "${highlight_color}Please delete exising \"${vm_name}\" VirtualBox configuration files ${warning_color}manually${default_color}.\n"
echo -e "Error message:\n"
VBoxManage createvm --name "${vm_name}" --ostype "MacOS1013_64" --register 2>/dev/tty
exit
fi
}
function check_default_virtual_machine() {
print_dimly "stage: check_default_virtual_machine"
echo -e "\nChecking that VirtualBox starts the virtual machine without errors."
if [[ -n $(VBoxManage startvm "${vm_name}" 2>&1 1>/dev/null) ]]; then
echo -e "Error while starting the virtual machine.\nExiting."
exit
fi
VBoxManage controlvm "${vm_name}" poweroff 2>/dev/null
echo -e "\nChecking that VirtualBox uses hardware-supported virtualization."
vbox_log="$(VBoxManage showvminfo "${vm_name}" --log 0)"
regex='.*Attempting fall back to NEM.*' # for zsh compatibility
if [[ "${vbox_log}" =~ ${regex} ]]; then
echo -e "\nVirtualbox is not using hardware-supported virtualization features."
if [[ -n "$(cygcheck -V 2>/dev/null)" ||
"$(cat /proc/sys/kernel/osrelease 2>/dev/null)" =~ [Mm]icrosoft ]]; then
echo "Check that software such as Hyper-V, Windows Sandbox, WSL2, memory integrity"
echo "protection, and other Windows features that lock virtualization are turned off."
fi
echo "Exiting."
exit
fi
}
function prepare_macos_installation_files() {
print_dimly "stage: prepare_macos_installation_files"
# Find the correct download URL in the Apple catalog
echo -e "\nDownloading Apple macOS ${macOS_release_name} software update catalog"
wget "${sucatalog}" \
${wgetargs} \
--output-document="${macOS_release_name}_sucatalog"
# if file was not downloaded correctly
if [[ ! -s "${macOS_release_name}_sucatalog" ]]; then
wget --debug -O /dev/null -o "${macOS_release_name}_wget.log" "${sucatalog}"
echo -e "\nCouldn't download the Apple software update catalog."
if [[ "$(expr match "$(cat "${macOS_release_name}_wget.log")" '.*ERROR[[:print:]]*is not trusted')" -gt "0" ]]; then
echo -e "\nMake sure certificates from a certificate authority are installed."
echo "Certificates are often installed through the package manager with"
echo "a package named ${highlight_color}ca-certificates${default_color}"
fi
echo "Exiting."
exit
fi
echo "Trying to find macOS ${macOS_release_name} InstallAssistant download URL"
tac "${macOS_release_name}_sucatalog" | csplit - '/InstallAssistantAuto.smd/+1' '{*}' -f "${macOS_release_name}_sucatalog_" -s
for catalog in "${macOS_release_name}_sucatalog_"* "error"; do
if [[ "${catalog}" == error ]]; then
rm "${macOS_release_name}_sucatalog"*
echo "Couldn't find the requested download URL in the Apple catalog. Exiting."
exit
fi
urlbase="$(tail -n 1 "${catalog}" 2>/dev/null)"
urlbase="$(expr match "${urlbase}" '.*\(http://[^<]*/\)')"
wget "${urlbase}InstallAssistantAuto.smd" \
${wgetargs} \
--output-document="${catalog}_InstallAssistantAuto.smd"
found_version="$(head -n 6 "${catalog}_InstallAssistantAuto.smd" | tail -n 1)"
if [[ "${found_version}" == *${CFBundleShortVersionString}* ]]; then
echo -e "Found download URL: ${urlbase}\n"
rm "${macOS_release_name}_sucatalog"*
break
fi
done
echo "Downloading macOS installation files from swcdn.apple.com"
for filename in "BaseSystem.chunklist" \
"InstallInfo.plist" \
"AppleDiagnostics.dmg" \
"AppleDiagnostics.chunklist" \
"BaseSystem.dmg" \
"InstallESDDmg.pkg"; \
do wget "${urlbase}${filename}" \
${wgetargs} \
--output-document "${macOS_release_name}_${filename}"
done
echo -e "\nSplitting the several-GB InstallESDDmg.pkg into 1GB parts because"
echo "VirtualBox hasn't implemented UDF/HFS VISO support yet and macOS"
echo "doesn't support ISO 9660 Level 3 with files larger than 2GB."
split --verbose -a 2 -d -b 1000000000 "${macOS_release_name}_InstallESDDmg.pkg" "${macOS_release_name}_InstallESD.part"
if [[ ! -s "ApfsDriverLoader.efi" ]]; then
echo -e "\nDownloading open-source APFS EFI drivers used for VirtualBox 6.0 and 5.2"
[[ "${vbox_version:0:1}" -gt 6 || ( "${vbox_version:0:1}" = 6 && "${vbox_version:2:1}" -ge 1 ) ]] && echo "...even though VirtualBox version 6.1 or higher is detected."
wget 'https://github.com/acidanthera/AppleSupportPkg/releases/download/2.0.4/AppleSupport-v2.0.4-RELEASE.zip' \
${wgetargs} \
--output-document 'AppleSupport-v2.0.4-RELEASE.zip'
unzip -oj 'AppleSupport-v2.0.4-RELEASE.zip'
fi
}
function create_nvram_files() {
print_dimly "stage: create_nvram_files"
# Each NVRAM file may contain multiple entries.
# Each entry contains a namesize, datasize, name, guid, attributes, and data.
# Each entry is immediately followed by a crc32 of the entry.
# The script creates each file with only one entry for easier editing.
#
# The hex strings are stripped by xxd, so they can
# look like "0xAB 0xCD" or "hAB hCD" or "AB CD" or "ABCD" or a mix of formats
# and have extraneous characters like spaces or minus signs.
# Load the binary files into VirtualBox VM NVRAM with the builtin command dmpstore
# in the VM EFI Internal Shell, for example:
# dmpstore -all -l fs0:\system-id.bin
#
# DmpStore code is available at this URL:
# https://github.com/mdaniel/virtualbox-org-svn-vbox-trunk/blob/master/src/VBox/Devices/EFI/Firmware/ShellPkg/Library/UefiShellDebug1CommandsLib/DmpStore.c
function generate_nvram_bin_file() {
# input: name data guid (three positional arguments, all required)
# output: function outputs nothing to stdout
# but writes a binary file to working directory
local namestring="${1}" # string of chars
local filename="${namestring}"
# represent string as string-of-hex-bytes, add null byte after every byte,
# terminate string with two null bytes
local name="$( for (( i = 0 ; i < ${#namestring} ; i++ )); do printf -- "${namestring:${i}:1}" | xxd -p | tr -d '\n'; printf '00'; done; printf '0000' )"
# size of string in bytes, represented by eight hex digits, big-endian
local namesize="$(printf "%08x" $(( ${#name} / 2 )) )"
# flip four big-endian bytes byte-order to little-endian
local namesize="$(printf "${namesize}" | xxd -r -p | od -tx4 -N4 -An --endian=little)"
# strip string-of-hex-bytes representation of data of spaces, "x", "h", etc
local data="$(printf -- "${2}" | xxd -r -p | xxd -p)"
# size of data in bytes, represented by eight hex digits, big-endian
local datasize="$(printf "%08x" $(( ${#data} / 2 )) )"
# flip four big-endian bytes byte-order to little-endian
local datasize="$(printf "${datasize}" | xxd -r -p | od -tx4 -N4 -An --endian=little)"
# guid string-of-hex-bytes is five fields, 8+4+4+4+12 nibbles long
# first three are little-endian, last two big-endian
# for example, 0F1A2B3C-4D5E-6A7B-8C9D-A1B2C3D4E5F6
# is stored as 3C2B1A0F-5E4D-7B6A-8C9D-A1B2C3D4E5F6
local g="$( printf -- "${3}" | xxd -r -p | xxd -p )" # strip spaces etc
local guid="${g:6:2} ${g:4:2} ${g:2:2} ${g:0:2} ${g:10:2} ${g:8:2} ${g:14:2} ${g:12:2} ${g:16:16}"
# attributes in four bytes little-endian
local attributes="07 00 00 00"
# the data structure
local entry="${namesize} ${datasize} ${name} ${guid} ${attributes} ${data}"
# calculate crc32 using gzip, flip crc32 bytes into big-endian
local crc32="$(printf "${entry}" | xxd -r -p | gzip -c | tail -c8 | od -tx4 -N4 -An --endian=big)"
# save binary data
printf -- "${entry} ${crc32}" | xxd -r -p - "${vm_name}_${filename}.bin"
}
# MLB
MLB_b16="$(printf -- "${MLB}" | xxd -p)"
generate_nvram_bin_file MLB "${MLB_b16}" "4D1EDE05-38C7-4A6A-9CC6-4BCCA8B38C14"
# ROM
# Convert the mixed-ASCII-and-base16 ROM value
# into an ASCII string that represents a base16 number.
ROM_b16="$(for (( i=0; i<${#ROM}; )); do
if [[ "${ROM:${i}:1}" == "%" ]]; then
let j=i+1
echo -n "${ROM:${j}:2}"
let i=i+3
else
x="$(echo -n "${ROM:${i}:1}" | od -t x1 -An | tr -d ' ')"
echo -n "${x}"
let i=i+1
fi
done)"
generate_nvram_bin_file ROM "${ROM_b16}" "4D1EDE05-38C7-4A6A-9CC6-4BCCA8B38C14"
# system-id
generate_nvram_bin_file system-id "${SYSTEM_UUID}" "7C436110-AB2A-4BBB-A880-FE41995C9F82"
# SIP / csr-active-config
generate_nvram_bin_file csr-active-config "${SYSTEM_INTEGRITY_PROTECTION}" "7C436110-AB2A-4BBB-A880-FE41995C9F82"
}
function create_macos_installation_files_viso() {
print_dimly "stage: create_macos_installation_files_viso"
echo "Creating EFI startup script"
echo 'echo -off' > "${vm_name}_startup.nsh"
if [[ ( "${vbox_version:0:1}" -lt 6 ) || ( "${vbox_version:0:1}" = 6 && "${vbox_version:2:1}" = 0 ) ]]; then
echo 'load fs0:\EFI\OC\Drivers\AppleImageLoader.efi
load fs0:\EFI\OC\Drivers\AppleUiSupport.efi
load fs0:\EFI\OC\Drivers\ApfsDriverLoader.efi
map -r' >> "${vm_name}_startup.nsh"
fi
# EFI Internal Shell 2.1 (VBox 6.0) doesn't support for-loops that start with 0
echo 'if exist "fs0:\EFI\NVRAM\MLB.bin" then
dmpstore -all -l fs0:\EFI\NVRAM\MLB.bin
dmpstore -all -l fs0:\EFI\NVRAM\ROM.bin
dmpstore -all -l fs0:\EFI\NVRAM\csr-active-config.bin
dmpstore -all -l fs0:\EFI\NVRAM\system-id.bin
endif
for %a run (1 5)
if exist "fs%a:\EFI\NVRAM\MLB.bin" then
dmpstore -all -l fs%a:\EFI\NVRAM\MLB.bin
dmpstore -all -l fs%a:\EFI\NVRAM\ROM.bin
dmpstore -all -l fs%a:\EFI\NVRAM\csr-active-config.bin
dmpstore -all -l fs%a:\EFI\NVRAM\system-id.bin
endif
endfor
for %a run (1 5)
if exist "fs%a:\macOS Install Data\Locked Files\Boot Files\boot.efi" then
"fs%a:\macOS Install Data\Locked Files\Boot Files\boot.efi"
endif
endfor
for %a run (1 5)
if exist "fs%a:\System\Library\CoreServices\boot.efi" then
"fs%a:\System\Library\CoreServices\boot.efi"
endif
endfor' >> "${vm_name}_startup.nsh"
echo -e "\nCreating VirtualBox 6 virtual ISO containing the"
echo -e "installation files from swcdn.apple.com\n"
pseudouuid="$(od -tx -N16 /dev/urandom | xxd -r | xxd -p)"
pseudouuid="${pseudouuid:0:8}-${pseudouuid:8:4}-${pseudouuid:12:4}-${pseudouuid:16:4}-${pseudouuid:20:12}"
echo "--iprt-iso-maker-file-marker-bourne-sh ${pseudouuid}
--volume-id=${macOS_release_name:0:5}-files" > "${macOS_release_name}_Installation_files.viso"
# Apple macOS installation files
for filename in "BaseSystem.chunklist" \
"InstallInfo.plist" \
"AppleDiagnostics.dmg" \
"AppleDiagnostics.chunklist" \
"BaseSystem.dmg" ; do
if [[ -s "${macOS_release_name}_${filename}" ]]; then
echo "/${filename}=\"${macOS_release_name}_${filename}\"" >> "${macOS_release_name}_Installation_files.viso"
fi
done
if [[ -s "${macOS_release_name}_InstallESD.part00" ]]; then
for part in "${macOS_release_name}_InstallESD.part"*; do
echo "/InstallESD${part##*InstallESD}=\"${part}\"" >> "${macOS_release_name}_Installation_files.viso"
done
fi
# NVRAM binary files
for filename in "MLB.bin" "ROM.bin" "csr-active-config.bin" "system-id.bin"; do
if [[ -s "${vm_name}_${filename}" ]]; then
echo "/ESP/EFI/NVRAM/${filename}=\"${vm_name}_${filename}\"" >> "${macOS_release_name}_Installation_files.viso"
fi
done
# EFI drivers for VirtualBox 6.0 and 5.2
for filename in "ApfsDriverLoader.efi" "AppleImageLoader.efi" "AppleUiSupport.efi"; do
if [[ -s "${filename}" ]]; then
echo "/ESP/EFI/OC/Drivers/${filename}=\"${filename}\"" >> "${macOS_release_name}_Installation_files.viso"
fi
done
# EFI startup script
echo "/ESP/startup.nsh=\"${vm_name}_startup.nsh\"" >> "${macOS_release_name}_Installation_files.viso"
}
# Create the macOS base system virtual disk image
function create_basesystem_virtual_disk() {
print_dimly "stage: create_basesystem_virtual_disk"
if [[ -s "${macOS_release_name}_BaseSystem.${storage_format}" ]]; then
echo "${macOS_release_name}_BaseSystem.${storage_format} bootstrap virtual disk image exists."
elif [[ ! -s "${macOS_release_name}_BaseSystem.dmg" ]]; then
echo -e "\nCould not find ${macOS_release_name}_BaseSystem.dmg; exiting."
exit
else
local failed=''
echo "Converting BaseSystem.dmg to BaseSystem.img"
if [[ -n "$("${PWD}/dmg2img.exe" -d 2>/dev/null)" ]]; then
"${PWD}/dmg2img.exe" "${macOS_release_name}_BaseSystem.dmg" "${macOS_release_name}_BaseSystem.img" || local failed='failed'
else
dmg2img "${macOS_release_name}_BaseSystem.dmg" "${macOS_release_name}_BaseSystem.img" || local failed='failed'
fi
VBoxManage storagectl "${vm_name}" --remove --name SATA >/dev/null 2>&1
VBoxManage closemedium "${macOS_release_name}_BaseSystem.${storage_format}" >/dev/null 2>&1
VBoxManage convertfromraw --format "${storage_format}" "${macOS_release_name}_BaseSystem.img" "${macOS_release_name}_BaseSystem.${storage_format}" || local failed='failed'
if [[ -n "${failed}" ]]; then
echo "Failed to create \"${macOS_release_name}_BaseSystem.${storage_format}\"."
if [[ "$(cat /proc/sys/kernel/osrelease 2>/dev/null)" =~ [Mm]icrosoft ]]; then
echo -e "\nSome versions of WSL require the script to execute on a Windows filesystem path,"
echo -e "for example ${highlight_color}/mnt/c/Users/Public/Documents${default_color}"
echo -e "Switch to a path on the Windows filesystem if VBoxManage.exe fails to"
echo -e "create or open a file.\n"
fi
echo "Exiting."
exit
else
rm "${macOS_release_name}_BaseSystem.img" 2>/dev/null
fi
fi
}
# Create the target virtual disk image
function create_target_virtual_disk() {
print_dimly "stage: create_target_virtual_disk"
if [[ -w "${vm_name}.${storage_format}" ]]; then
echo "${vm_name}.${storage_format} target system virtual disk image exists."
echo -ne "${warning_color}Delete \"${vm_name}.${storage_format}\"?${default_color}"
prompt_delete_y_n
if [[ "${delete}" == "y" ]]; then
if [[ "$( VBoxManage list runningvms )" =~ \""${vm_name}"\" ]]
then
echo "\"${vm_name}.${storage_format}\" may be deleted"
echo "only when the virtual machine is powered off."
echo "Exiting."
exit
else
VBoxManage storagectl "${vm_name}" --remove --name SATA >/dev/null 2>&1
VBoxManage closemedium "${vm_name}.${storage_format}" >/dev/null 2>&1
rm "${vm_name}.${storage_format}"
fi
else
echo "Exiting."
exit
fi
fi
if [[ "${macOS_release_name}" = "Catalina" && "${storage_size}" -lt 25000 ]]; then
echo "Attempting to install macOS Catalina on a disk smaller than 25000MB will fail."
echo "Please assign a larger virtual disk image size. Exiting."
exit
elif [[ "${storage_size}" -lt 22000 ]]; then
echo "Attempting to install macOS on a disk smaller than 22000MB will fail."
echo "Please assign a larger virtual disk image size. Exiting."
exit
fi
if [[ ! -e "${vm_name}.${storage_format}" ]]; then
echo "Creating target system virtual disk image for \"${vm_name}\""
VBoxManage storagectl "${vm_name}" --remove --name SATA >/dev/null 2>&1
VBoxManage closemedium "${vm_name}.${storage_format}" >/dev/null 2>&1
VBoxManage createmedium --size="${storage_size}" \
--format "${storage_format}" \
--filename "${vm_name}.${storage_format}" \
--variant standard 2>/dev/tty
fi
}
# Create the installation media virtual disk image
function create_installer_virtual_disk() {
print_dimly "stage: create_installer_virtual_disk"
if [[ -w "${macOS_release_name} bootable installer.${storage_format}" ]]; then
echo "\"${macOS_release_name} bootable installer.${storage_format}\" virtual disk image exists."
echo -ne "${warning_color}Delete \"${macOS_release_name} bootable installer.${storage_format}\"?${default_color}"
prompt_delete_y_n
if [[ "${delete}" == "y" ]]; then
if [[ "$( VBoxManage list runningvms )" =~ \""${vm_name}"\" ]]
then
echo "\"${macOS_release_name} bootable installer.${storage_format}\" may be deleted"
echo "only when the virtual machine is powered off."
echo "Exiting."
exit
else
VBoxManage storagectl "${vm_name}" --remove --name SATA >/dev/null 2>&1
VBoxManage closemedium "${macOS_release_name} bootable installer.${storage_format}" >/dev/null 2>&1
rm "${macOS_release_name} bootable installer.${storage_format}"
fi
else
echo "Exiting."
exit
fi
fi
if [[ ! -e "${macOS_release_name} bootable installer.${storage_format}" ]]; then
echo "Creating ${macOS_release_name} installation media virtual disk image."
VBoxManage storagectl "${vm_name}" --remove --name SATA >/dev/null 2>&1
VBoxManage closemedium "${macOS_release_name} bootable installer.${storage_format}" >/dev/null 2>&1
VBoxManage createmedium --size=12000 \
--format "${storage_format}" \
--filename "${macOS_release_name} bootable installer.${storage_format}" \
--variant standard 2>/dev/tty
fi
}
function configure_vm() {
print_dimly "stage: configure_vm"
VBoxManage modifyvm "${vm_name}" --cpus "${cpu_count}" --memory "${memory_size}" \
--vram "${gpu_vram}" --pae on --boot1 none --boot2 none --boot3 none \
--boot4 none --firmware efi --rtcuseutc on --usbxhci on --chipset ich9 \
--mouse usbtablet --keyboard usb --audiocontroller hda --audiocodec stac9221
VBoxManage setextradata "${vm_name}" \
"VBoxInternal2/EfiGraphicsResolution" "${resolution}"
VBoxManage setextradata "${vm_name}" \
"VBoxInternal/Devices/efi/0/Config/DmiSystemFamily" "${DmiSystemFamily}"
VBoxManage setextradata "${vm_name}" \
"VBoxInternal/Devices/efi/0/Config/DmiSystemProduct" "${DmiSystemProduct}"
VBoxManage setextradata "${vm_name}" \
"VBoxInternal/Devices/efi/0/Config/DmiSystemSerial" "${DmiSystemSerial}"
VBoxManage setextradata "${vm_name}" \
"VBoxInternal/Devices/efi/0/Config/DmiSystemUuid" "${DmiSystemUuid}"
VBoxManage setextradata "${vm_name}" \
"VBoxInternal/Devices/efi/0/Config/DmiOEMVBoxVer" "${DmiOEMVBoxVer}"
VBoxManage setextradata "${vm_name}" \
"VBoxInternal/Devices/efi/0/Config/DmiOEMVBoxRev" "${DmiOEMVBoxRev}"
VBoxManage setextradata "${vm_name}" \
"VBoxInternal/Devices/efi/0/Config/DmiBIOSVersion" "${DmiBIOSVersion}"
VBoxManage setextradata "${vm_name}" \
"VBoxInternal/Devices/efi/0/Config/DmiBoardProduct" "${DmiBoardProduct}"
VBoxManage setextradata "${vm_name}" \
"VBoxInternal/Devices/efi/0/Config/DmiBoardSerial" "${DmiBoardSerial}"
VBoxManage setextradata "${vm_name}" \
"VBoxInternal/Devices/efi/0/Config/DmiSystemVendor" "Apple Inc."
VBoxManage setextradata "${vm_name}" \
"VBoxInternal/Devices/efi/0/Config/DmiSystemVersion" "1.0"
VBoxManage setextradata "${vm_name}" \
"VBoxInternal/Devices/smc/0/Config/DeviceKey" \
"ourhardworkbythesewordsguardedpleasedontsteal(c)AppleComputerInc"
VBoxManage setextradata "${vm_name}" \
"VBoxInternal/Devices/smc/0/Config/GetKeyFromRealSMC" 0
}
function populate_virtual_disks() {
print_dimly "stage: populate_virtual_disks"
# Attach virtual disk images of the base system, installation, and target
# to the virtual machine
VBoxManage storagectl "${vm_name}" --remove --name SATA >/dev/null 2>&1
if [[ -n $(
2>&1 VBoxManage storagectl "${vm_name}" --add sata --name SATA --hostiocache on >/dev/null
) ]]; then
echo "Could not configure virtual machine storage controller. Exiting."; exit
fi
if [[ -n $(
2>&1 VBoxManage storageattach "${vm_name}" --storagectl SATA --port 0 \
--type hdd --nonrotational on --medium "${vm_name}.${storage_format}" >/dev/null
) ]]; then
echo "Could not attach \"${vm_name}.${storage_format}\". Exiting."; exit
fi
if [[ -n $(
2>&1 VBoxManage storageattach "${vm_name}" --storagectl SATA --port 1 --hotpluggable on \
--type hdd --nonrotational on --medium "${macOS_release_name} bootable installer.${storage_format}" >/dev/null
) ]]; then
echo "Could not attach \"${macOS_release_name} bootable installer.${storage_format}\". Exiting."; exit
fi
if [[ -n $(
2>&1 VBoxManage storageattach "${vm_name}" --storagectl SATA --port 2 --hotpluggable on \
--type hdd --nonrotational on --medium "${macOS_release_name}_BaseSystem.${storage_format}" >/dev/null
) ]]; then
echo "Could not attach \"${macOS_release_name}_BaseSystem.${storage_format}\". Exiting."; exit
fi
if [[ -n $(
2>&1 VBoxManage storageattach "${vm_name}" --storagectl SATA --port 3 \
--type dvddrive --medium "${macOS_release_name}_Installation_files.viso" >/dev/null
) ]]; then
echo "Could not attach \"${macOS_release_name}_Installation_files.viso\". Exiting."; exit
fi
echo "Starting virtual machine \"${vm_name}\".
This should take a couple of minutes. If booting fails, see the documentation
for information about applying different CPU profiles."
( VBoxManage startvm "${vm_name}" >/dev/null 2>&1 )
echo -e "\nUntil the script completes, please do not interact with the virtual machine."
[[ -z "${kscd}" ]] && declare_scancode_dict
prompt_lang_utils
prompt_terminal_ready
print_dimly "Please wait"
# Assigning "physical" disks from largest to smallest to "${disks[]}" array
# Partitining largest disk as APFS
# Partition second-largest disk as JHFS+
kbstring='disks="$(diskutil list | grep -o "\*[0-9][^ ]* GB *disk[0-9]$" | grep -o "[0-9].*" | sort -gr | grep -o disk[0-9] )" && '\
'disks=(${disks[@]}) && '\
'if [ -z "${disks}" ]; then echo "Could not find disks"; fi && '\
'[ -n "${disks[0]}" ] && '\
'diskutil partitionDisk "/dev/${disks[0]}" 1 GPT APFS "'"${vm_name}"'" R && '\
'diskutil partitionDisk "/dev/${disks[1]}" 1 GPT JHFS+ "Install" R && '
send_keys
# Create secondary base system on the Install disk
# and copy macOS install app files to the app directory
kbstring='asr restore --source "/Volumes/'"${macOS_release_name:0:5}-files"'/BaseSystem.dmg" --target /Volumes/Install --erase --noprompt && '\
'app_path="$(ls -d "/Volumes/"*"Base System 1/Install"*.app)" && '\
'install_path="${app_path}/Contents/SharedSupport/" && '\
'mkdir -p "${install_path}" && cd "/Volumes/'"${macOS_release_name:0:5}-files/"'" && '\
'cp *.chunklist *.plist *.dmg "${install_path}" && '\
'echo "" && echo "Copying the several-GB InstallESD.dmg to the installer app directory" && echo "Please wait" && '\
'if [ -s "${install_path}/InstallESD.dmg" ]; then '\
'rm -f "${install_path}/InstallESD.dmg" ; fi && '\
'for part in InstallESD.part*; do echo "Concatenating ${part}"; cat "${part}" >> "${install_path}/InstallESD.dmg"; done && '\
'sed -i.bak -e "s/InstallESDDmg\.pkg/InstallESD.dmg/" -e "s/pkg\.InstallESDDmg/dmg.InstallESD/" "${install_path}InstallInfo.plist" && '\
'sed -i.bak2 -e "/InstallESD\.dmg/{n;N;N;N;d;}" "${install_path}InstallInfo.plist" && '
send_keys
# shut down the virtual machine
kbstring='shutdown -h now'
send_keys
send_enter
echo "Partitioning the target virtual disk and the installer virtual disk.
Loading base system onto the installer virtual disk. Moving installation
files to installer virtual disk, updating InstallInfo.plist, and rebooting the
virtual machine.
The virtual machine may report that disk space is critically low; this is fine.
When the installer virtual disk is finished being populated, the script will
shut down the virtual machine. After shutdown, the initial base system will be
detached from the VM and released from VirtualBox."
print_dimly "If the partitioning fails, exit the script by pressing CTRL-C
Otherwise, please wait."
while [[ "$( VBoxManage list runningvms )" =~ \""${vm_name}"\" ]]; do sleep 2 >/dev/null 2>&1; done
echo "Waiting for the VirtualBox GUI to shut off."
for (( i=10; i>0; i-- )); do echo -ne " \r${i} "; sleep 0.5; done; echo " "
# Detach the original 2GB BaseSystem virtual disk image
# and release basesystem VDI from VirtualBox configuration
if [[ -n $(
2>&1 VBoxManage storageattach "${vm_name}" --storagectl SATA --port 2 --medium none >/dev/null
2>&1 VBoxManage closemedium "${macOS_release_name}_BaseSystem.${storage_format}" >/dev/null
) ]]; then
echo "Could not detach ${macOS_release_name}_BaseSystem.${storage_format}"
echo "It's possible the VirtualBox GUI took longer than five seconds to shut off."
echo "The macOS installation may be resumed with the following command:"
echo " ${highlight_color}${0} populate_macos_target_disk${default_color}"
exit
fi
echo "${macOS_release_name}_BaseSystem.${storage_format} successfully detached from"
echo "the virtual machine and released from VirtualBox Manager."
}
function populate_macos_target_disk() {
print_dimly "stage: populate_macos_target_disk"
if [[ "$( VBoxManage list runningvms )" =~ \""${vm_name}"\" ]]; then
echo -e "${highlight_color}Please ${warning_color}manually${highlight_color} shut down the virtual machine and press enter to continue.${default_color}"
clear_input_buffer_then_read
fi
VBoxManage storagectl "${vm_name}" --remove --name SATA >/dev/null 2>&1
if [[ -n $(
2>&1 VBoxManage storagectl "${vm_name}" --add sata --name SATA --hostiocache on >/dev/null
) ]]; then
echo "Could not configure virtual machine storage controller. Exiting."; exit
fi
if [[ -n $(
2>&1 VBoxManage storageattach "${vm_name}" --storagectl SATA --port 0 \
--type hdd --nonrotational on --medium "${vm_name}.${storage_format}" >/dev/null
) ]]; then
echo "Could not attach \"${vm_name}.${storage_format}\". Exiting."; exit
fi
if [[ -n $(
2>&1 VBoxManage storageattach "${vm_name}" --storagectl SATA --port 1 --hotpluggable on \
--type hdd --nonrotational on --medium "${macOS_release_name} bootable installer.${storage_format}" >/dev/null
) ]]; then
echo "Could not attach \"${macOS_release_name} bootable installer.${storage_format}\". Exiting."; exit
fi
if [[ -n $(
2>&1 VBoxManage storageattach "${vm_name}" --storagectl SATA --port 2 \
--type dvddrive --medium "${macOS_release_name}_Installation_files.viso" >/dev/null
) ]]; then
echo "Could not attach \"${macOS_release_name}_Installation_files.viso\". Exiting."; exit
fi
echo "The VM will boot from the populated installer base system virtual disk."
( VBoxManage startvm "${vm_name}" >/dev/null 2>&1 )
[[ -z "${kscd}" ]] && declare_scancode_dict
prompt_lang_utils
prompt_terminal_ready
add_another_terminal
echo -e "\nThe second open Terminal in the virtual machine copies EFI and NVRAM files"
echo -e "to the target EFI system partition when the installer finishes preparing."
echo -e "\nAfter the installer finishes preparing and the EFI and NVRAM files are copied,"
echo -ne "macOS will install and boot up when booting the target disk.\n\n"
print_dimly "Please wait"
# execute script concurrently, catch SIGUSR1 when installer finishes preparing
kbstring='disks="$(diskutil list | grep -o "[0-9][^ ]* GB *disk[0-9]$" | sort -gr | grep -o disk[0-9])" && '\
'disks=(${disks[@]}) && '\
'printf '"'"'trap "exit 0" SIGUSR1; while true; do sleep 10; done;'"'"' | sh && '\
'mkdir -p "/Volumes/'"${vm_name}"'/tmp/mount_efi" && '\
'mount_msdos /dev/${disks[0]}s1 "/Volumes/'"${vm_name}"'/tmp/mount_efi" && '\
'cp -r "/Volumes/'"${macOS_release_name:0:5}-files"'/ESP/"* "/Volumes/'"${vm_name}"'/tmp/mount_efi/" && '\
'installer_pid=$(ps | grep startosinstall | grep -v grep | cut -d '"'"' '"'"' -f 3) && '\
'kill -SIGUSR1 ${installer_pid}'
send_keys
send_enter
sleep 1
cycle_through_terminal_windows
# Find background process PID, then
# start the installer, send SIGUSR1 to concurrent bash script,
# the other script copies files to EFI system partition,
# then sends SIGUSR1 to the installer which restarts the virtual machine
kbstring='background_pid="$(ps | grep '"'"' sh$'"'"' | cut -d '"'"' '"'"' -f 3)" && '\
'app_path="$(ls -d /Install*.app)" && '\
'cd "/${app_path}/Contents/Resources/" && '\
'./startosinstall --agreetolicense --pidtosignal ${background_pid} --rebootdelay 500 --volume "/Volumes/'"${vm_name}"'"'
send_keys
send_enter
if [[ ! ( "${vbox_version:0:1}" -gt 6
|| ( "${vbox_version:0:1}" = 6 && "${vbox_version:2:1}" -ge 1 ) ) ]]; then
echo -e "${highlight_color}When the installer finishes preparing and reboots the VM, press enter${default_color} so the script
powers off the virtual machine and detaches the device \"${macOS_release_name} bootable installer.${storage_format}\" to avoid
booting into the initial installer environment again."
clear_input_buffer_then_read
VBoxManage controlvm "${vm_name}" poweroff >/dev/null 2>&1
for (( i=10; i>0; i-- )); do echo -ne " \r${i} "; sleep 0.5; done; echo -n " "
VBoxManage storagectl "${vm_name}" --remove --name SATA >/dev/null 2>&1
VBoxManage storagectl "${vm_name}" --add sata --name SATA --hostiocache on >/dev/null 2>&1
VBoxManage storageattach "${vm_name}" --storagectl SATA --port 0 \
--type hdd --nonrotational on --medium "${vm_name}.${storage_format}"
fi
echo -e "For further information, such as applying EFI and NVRAM variables to enable
iMessage connectivity, see the documentation with the following command:\n"
would_you_like_to_know_less
echo -e "\n${highlight_color}That's it! Enjoy your virtual machine.${default_color}\n"
}
function delete_temporary_files() {
print_dimly "stage: delete_temporary_files"
if [[ ! "$(VBoxManage showvminfo "${vm_name}")" =~ State:[\ \t]*powered\ off ]]
then
echo -e "Temporary files may be deleted when the virtual machine is powered off
and without a suspended state by executing the following command at the script's
working directory:
${highlight_color}${0} delete_temporary_files${default_color}"
else
# detach temporary VDIs and attach the macOS target disk
VBoxManage storagectl "${vm_name}" --remove --name SATA >/dev/null 2>&1
VBoxManage storagectl "${vm_name}" --add sata --name SATA --hostiocache on >/dev/null 2>&1
if [[ -s "${vm_name}.${storage_format}" ]]; then
VBoxManage storageattach "${vm_name}" --storagectl SATA --port 0 \
--type hdd --nonrotational on --medium "${vm_name}.${storage_format}"
fi
VBoxManage closemedium "${macOS_release_name} bootable installer.${storage_format}" >/dev/null 2>&1
VBoxManage closemedium "${macOS_release_name}_BaseSystem.${storage_format}" >/dev/null 2>&1
echo -e "The following temporary files are safe to delete:\n"
temporary_files=("${macOS_release_name}_Apple"*
"${macOS_release_name}_BaseSystem"*
"${macOS_release_name}_Install"*
"${macOS_release_name} bootable installer"*
"${vm_name}_"*".bin"
"${vm_name}_"*".txt"
"${vm_name}_startup.nsh"
"ApfsDriverLoader.efi"
"Apple"*".efi"
"AppleSupport-v2.0.4-RELEASE.zip"
"dmg2img.exe")
ls -d "${temporary_files[@]}" 2>/dev/null
echo -e "\n${warning_color}Delete temporary files listed above?${default_color}"
prompt_delete_y_n
if [[ "${delete}" == "y" ]]; then
rm -f "${temporary_files[@]}" 2>/dev/null
fi
fi
}
function documentation() {
low_contrast_stages=""
for stage in ${stages}; do
low_contrast_stages="${low_contrast_stages}"' '"${low_contrast_color}${stage}${default_color}"$'\n'
done
echo -ne "\n ${highlight_color}NAME${default_color}
Push-button installer of macOS on VirtualBox
${highlight_color}DESCRIPTION${default_color}
The script downloads macOS High Sierra, Mojave, and Catalina from Apple servers
and installs them on VirtualBox 5.2, 6.0, and 6.1. The script doesn't install
any closed-source additions or bootloaders. A default install requires the user
press enter when prompted, less than ten times, to complete the installation.
${highlight_color}USAGE${default_color}
${low_contrast_color}${0} [STAGE]... ${default_color}
The installation is divided into stages. Stage titles may be given as command-
line arguments for the script. When the script is executed with no command-line
arguments, each of the stages is performed in succession in the order listed:
${low_contrast_stages}
Other than the stages above, the command-line arguments \"${low_contrast_color}documentation${default_color}\" and
\"${low_contrast_color}troubleshoot${default_color}\" are available. \"${low_contrast_color}troubleshoot${default_color}\" outputs system information,
VirtualBox logs, and checksums for some installation files. \"${low_contrast_color}documentation${default_color}\"
outputs the script's documentation. If \"${low_contrast_color}documentation${default_color}\" is the first argument,
no other arguments are parsed.
The stage \"${low_contrast_color}check_shell${default_color}\" is always performed when the script loads.
The stages \"${low_contrast_color}check_gnu_coreutils_prefix${default_color}\", \"${low_contrast_color}set_variables${default_color}\", and
\"${low_contrast_color}check_dependencies${default_color}\" are always performed when any stage title other than
\"${low_contrast_color}documentation${default_color}\" is specified as the first argument, and the rest of the
specified stages are performed only after the checks pass.
${highlight_color}EXAMPLES${default_color}
${low_contrast_color}${0} create_vm configure_vm${default_color}
The above stage might be used to create and configure a virtual machine on a
new VirtualBox installation, then manually attach to the new virtual machine
an existing macOS disk image that was previously created by the script.
${low_contrast_color}${0} delete_temporary_files${default_color}
The above stage might be used when no more virtual machines need to be
installed, and the temporary files can be deleted.
${low_contrast_color}${0} "'\\'"${default_color}
${low_contrast_color}configure_vm create_nvram_files create_macos_installation_files_viso${default_color}
The above stages might be used to update the EFI and NVRAM variables required
for iCloud and iMessage connectivity and other Apple-connected apps.
${highlight_color}Configuration${default_color}
The script's default configuration is stored in the ${low_contrast_color}set_variables()${default_color} function at
the top of the script. No manual configuration is required to use the script.
The configuration may be manually edited either by editing the variable
assignment in ${low_contrast_color}set_variables()${default_color} or by executing the following command:
${low_contrast_color}export macos_vm_vars_file=/path/to/variable_assignment_file${default_color}
\"${low_contrast_color}variable_assignment_file${default_color}\" is a plain text file that contains zero or more
lines with a variable assignment for any variable specified in ${low_contrast_color}set_variables()${default_color},
for example ${low_contrast_color}macOS_release_name=\"HighSierra\"${default_color} or ${low_contrast_color}DmiSystemFamily=\"iMac\"${default_color}
${highlight_color}iCloud and iMessage connectivity${default_color}
iCloud, iMessage, and other connected Apple services require a valid device
name and serial number, board ID and serial number, and other genuine
(or genuine-like) Apple parameters. These parameters may be edited at the top
of the script or loaded through a configuration file as described in the
section above. Assigning these parameters is not required when installing or
using macOS, only when connecting to the iCould app, iMessage, and other
apps that authenticate the device with Apple.
These are the variables that are required for iMessage connectivity:
${low_contrast_color}DmiSystemFamily # Model name${default_color}
${low_contrast_color}DmiSystemProduct # Model identifier${default_color}
${low_contrast_color}DmiSystemSerial # System serial number${default_color}
${low_contrast_color}DmiSystemUuid # Hardware UUID${default_color}
${low_contrast_color}DmiOEMVBoxVer # Apple ROM info (major version)${default_color}
${low_contrast_color}DmiOEMVBoxRev # Apple ROM info (revision)${default_color}
${low_contrast_color}DmiBIOSVersion # Boot ROM version${default_color}
${low_contrast_color}DmiBoardProduct # Main Logic Board identifier${default_color}
${low_contrast_color}DmiBoardSerial # Main Logic Board serial (EFI)${default_color}
${low_contrast_color}MLB # Main Logic Board serial (NVRAM)${default_color}
${low_contrast_color}ROM # ROM identifier (NVRAM)${default_color}
${low_contrast_color}SYSTEM_UUID # System identifier (NVRAM)${default_color}
The comments at the top of the script specify how to view these variables
on a genuine Mac.
${highlight_color}Applying the EFI and NVRAM parameters${default_color}
The EFI and NVRAM parameters may be set in the script before installation by
editing them at the top of the script, and applied after the last step of the
installation by resetting the virtual machine and booting into the
EFI Internal Shell. When resetting or powering up the VM, immediately press
Esc when the VirtualBox logo appears. This boots into the EFI Internal Shell or
the boot menu. If the boot menu appears, select \"Boot Manager\" and then
\"EFI Internal Shell\" and then allow the startup.nsh script to execute
automatically, applying the EFI and NVRAM variables before booting macOS.
${highlight_color}Changing the EFI and NVRAM parameters after installation${default_color}
The variables mentioned above may be edited and applied to an existing macOS
virtual machine by executing the following command and copying the generated
files to the macOS EFI partition:
${low_contrast_color}${0} "'\\'"${default_color}
${low_contrast_color}configure_vm create_nvram_files create_macos_installation_files_viso${default_color}
After executing the command, attach the resulting VISO file to the virtual
machine's storage through VirtualBox Manager or VBoxManage. Power up the VM
and boot macOS, then start Terminal and execute the following commands, making
sure to replace \"/Volumes/path/to/VISO/\" with the correct path:
${low_contrast_color}mkdir ESP${default_color}
${low_contrast_color}sudo su # this will prompt for a password${default_color}
${low_contrast_color}diskutil mount -mountPoint ESP disk0s1${default_color}
${low_contrast_color}cp -r /Volumes/path/to/VISO/ESP/* ESP/${default_color}
After copying the files, boot into the EFI Internal Shell as desribed in the
section \"Applying the EFI and NVRAM parameters\".
${highlight_color}Storage format${default_color}
The script by default assigns a target virtual disk storage format of VDI. This
format can be resized by VirtualBox as explained in the next section. The other
available format, VMDK, cannot be resized by VirtualBox but can be attached to
a QEMU virtual machine for use with Linux KVM for better performance.
${highlight_color}Storage size${default_color}
The script by default assigns a target virtual disk storage size of 80GB, which
is populated to about 20GB on the host on initial installation. After the
installation is complete, the VDI storage size may be increased. First increase
the virtual disk image size through VirtualBox Manager or VBoxManage, then in
Terminal in the virtual machine execute the following command:
${low_contrast_color}sudo diskutil repairDisk disk0${default_color}
After it completes, open Disk Utility and delete the \"Free space\" partition so
it allows the system APFS container to take up the available space, or if that
fails, execute the following command:
${low_contrast_color}sudo diskutil apfs resizeContainer disk1 0${default_color}
Both Disk Utility and ${low_contrast_color}diskutil${default_color} may fail and require successive resize attempts
separated by virtual machine reboots.
${highlight_color}Primary display resolution${default_color}
The following command assigns the virtual machine primary display resolution:
${low_contrast_color}VBoxManage setextradata \"\${vm_name}\" \\${default_color}
${low_contrast_color}\"VBoxInternal2/EfiGraphicsResolution\" \"\${resolution}\"${default_color}
The following primary display resolutions are supported by macOS on VirtualBox:
${low_contrast_color}5120x2880 2880x1800 2560x1600 2560x1440 1920x1200 1600x1200 1680x1050${default_color}
${low_contrast_color}1440x900 1280x800 1024x768 640x480${default_color}
Secondary displays can have an arbitrary resolution.
${highlight_color}CPU profiles and CPUID settings${default_color}
macOS does not supprort every CPU supported by VirtualBox. If the macOS Base
System does not boot, try applying different CPU profiles to the virtual
machine. First, while the VM is powered off, set the guest's CPU profile to the
host's CPU profile then try to boot the virtual machine:
${low_contrast_color}VBoxManage modifyvm \"\${vm_name}\" --cpu-profile host${default_color}
${low_contrast_color}VBoxManage modifyvm \"\${vm_name}\" --cpuidremoveall${default_color}
If booting fails, try assigning each of the preconfigured CPU profiles:
${low_contrast_color}VBoxManage modifyvm \"\${vm_name}\" --cpu-profile \"\${cpu_profile}\"${default_color}
Available CPU profiles:
${low_contrast_color}\"Intel Xeon X5482 3.20GHz\" \"Intel Core i7-2635QM\" \"Intel Core i7-3960X\"${default_color}
${low_contrast_color}\"Intel Core i5-3570\" \"Intel Core i7-5600U\" \"Intel Core i7-6700K\"${default_color}
If booting fails after trying each preconfigured CPU profile, the host's CPU
requires specific CPUID settings to boot macOS.
${highlight_color}Unsupported features${default_color}
Developing and maintaining VirtualBox or macOS features is beyond the scope of
this script. Some features may behave unexpectedly, such as USB device support,
audio support, FileVault boot password prompt support, and other features.
${highlight_color}Performance and deployment${default_color}
After successfully creating a working macOS virtual machine, consider importing
the virtual machine into more performant virtualization software, or packaging
it for configuration management platforms for automated deployment. These
virtualization and deployment applications require additonal configuration that
is beyond the scope of the script.
QEMU with KVM is capable of providing virtual machine hardware passthrough
for near-native performance. QEMU supports the VMDK virtual disk image format,
which can be configured to be created by the script, or converted from the
default VirtualBox VDI format into the VMDK format with the following command:
${low_contrast_color}VBoxManage clonehd --format vmdk source.vdi target.vmdk${default_color}
QEMU and KVM require additional configuration that is beyond the scope of the
script.
${highlight_color}Bootloaders${default_color}
The macOS VirtualBox guest is loaded without extra bootloaders, but it is
compatible with OpenCore. OpenCore requires additonal configuration that is
beyond the scope of the script.
${highlight_color}Display scaling${default_color}
VirtualBox does not supply an EDID for its virtual display, and macOS does not
enable display scaling (high PPI) without an EDID. The bootloader OpenCore can
inject an EDID which enables display scaling.
${highlight_color}Audio${default_color}
macOS may not support any built-in VirtualBox audio controllers. The bootloader
OpenCore may be able to load open-source audio drivers in VirtualBox.
${highlight_color}FileVault${default_color}
The VirtualBox EFI implementation does not properly load the FileVault full disk
encryption password prompt upon boot. The bootloader OpenCore is be able to
load the password prompt with the parameter \"ProvideConsoleGop\" set to \"true\".
${highlight_color}Further information${default_color}
Further information is available at the following URL:
${highlight_color}https://github.com/myspaghetti/macos-virtualbox${default_color}
"
}
function troubleshoot() {
echo -ne "\nWriting troubleshooting information to \"${highlight_color}${vm_name}_troubleshoot.txt${default_color}\"\n\n"
echo "The file will contain system information, VirtualBox paths, logs, configuration,"
echo "macOS virtual machine details including ${highlight_color}serials entered in the script${default_color},"
echo "and macOS installation file md5 checksums."
echo "When sharing this file, mind that it contains the above information."
echo ""
for wrapper in 1; do
echo "################################################################################"
head -n 5 "${0}"
if [[ -n "$(md5sum --version 2>/dev/null)" ]]; then
tail -n +60 "${0}" | md5sum 2>/dev/null
else
tail -n +60 "${0}" | md5 2>/dev/null
fi
echo "################################################################################"
echo "BASH_VERSION ${BASH_VERSION}"
vbox_ver="$(VBoxManage -v)"
echo "VBOX_VERSION ${vbox_ver//[$'\r\n']/}"
macos_ver="$(sw_vers 2>/dev/null)"
wsl_ver="$(cat /proc/sys/kernel/osrelease 2>/dev/null)"
win_ver="$(cmd.exe /d /s /c call ver 2>/dev/null)"
echo "OS VERSION ${macos_ver}${wsl_ver}${win_ver//[$'\r\n']/}"
echo "################################################################################"
echo "vbox.log"
VBoxManage showvminfo "${vm_name}" --log 0 2>&1
echo "################################################################################"
echo "vminfo"
VBoxManage showvminfo "${vm_name}" --machinereadable --details 2>&1
VBoxManage getextradata "${vm_name}" 2>&1
done > "${vm_name}_troubleshoot.txt"
echo "Written configuration and logs to \"${highlight_color}${vm_name}_troubleshoot.txt${default_color}\""
echo "Press CTRL-C to cancel checksums, or wait for checksumming to complete."
for wrapper in 1; do
echo "################################################################################"
echo "md5 hashes"
if [[ -n "$(md5sum --version 2>/dev/null)" ]]; then
md5sum "${macOS_release_name}_BaseSystem"* 2>/dev/null
md5sum "${macOS_release_name}_Install"* 2>/dev/null
md5sum "${macOS_release_name}_Apple"* 2>/dev/null
else
md5 "${macOS_release_name}_BaseSystem"* 2>/dev/null
md5 "${macOS_release_name}_Install"* 2>/dev/null
md5 "${macOS_release_name}_Apple"* 2>/dev/null
fi
echo "################################################################################"
done >> "${vm_name}_troubleshoot.txt"
if [ -s "${vm_name}_troubleshoot.txt" ]; then
echo -ne "\nFinished writing to \"${highlight_color}${vm_name}_troubleshoot.txt${default_color}\"\n"
fi
}
# GLOBAL VARIABLES AND FUNCTIONS THAT MIGHT BE CALLED MORE THAN ONCE
# terminal text colors
warning_color=$'\e[48;2;255;0;0m\e[38;2;255;255;255m' # white on red
highlight_color=$'\e[48;2;0;0;9m\e[38;2;255;255;255m' # white on black
low_contrast_color=$'\e[48;2;0;0;9m\e[38;2;128;128;128m' # grey on black
default_color=$'\033[0m'
# prints positional parameters in low contrast preceded and followed by newline
function print_dimly() {
echo -e "\n${low_contrast_color}$@${default_color}"
}
# don't need sleep when we can read!
function sleep() {
read -t "${1}" >/dev/null 2>&1
}
# QWERTY-to-scancode dictionary. Hex scancodes, keydown and keyup event.
# Virtualbox Mac scancodes found here:
# https://wiki.osdev.org/PS/2_Keyboard#Scan_Code_Set_1
# First half of hex code - press, second half - release, unless otherwise specified
function declare_scancode_dict() {
declare -gA kscd
kscd=(
["ESC"]="01 81"
["1"]="02 82"
["2"]="03 83"
["3"]="04 84"
["4"]="05 85"
["5"]="06 86"
["6"]="07 87"
["7"]="08 88"
["8"]="09 89"
["9"]="0A 8A"
["0"]="0B 8B"
["-"]="0C 8C"
["="]="0D 8D"
["BKSP"]="0E 8E"
["TAB"]="0F 8F"
["q"]="10 90"
["w"]="11 91"
["e"]="12 92"
["r"]="13 93"
["t"]="14 94"
["y"]="15 95"
["u"]="16 96"
["i"]="17 97"
["o"]="18 98"
["p"]="19 99"
["["]="1A 9A"
["]"]="1B 9B"
["ENTER"]="1C 9C"
["CTRLprs"]="1D"
["CTRLrls"]="9D"
["a"]="1E 9E"
["s"]="1F 9F"
["d"]="20 A0"
["f"]="21 A1"
["g"]="22 A2"
["h"]="23 A3"
["j"]="24 A4"
["k"]="25 A5"
["l"]="26 A6"
[";"]="27 A7"
["'"]="28 A8"
['`']="29 A9"
["LSHIFTprs"]="2A"
["LSHIFTrls"]="AA"
['\']="2B AB"
["z"]="2C AC"
["x"]="2D AD"
["c"]="2E AE"
["v"]="2F AF"
["b"]="30 B0"
["n"]="31 B1"
["m"]="32 B2"
[","]="33 B3"
["."]="34 B4"
["/"]="35 B5"
["RSHIFTprs"]="36"
["RSHIFTrls"]="B6"
["ALTprs"]="38"
["ALTrls"]="B8"
["LALT"]="38 B8"
["SPACE"]="39 B9"
[" "]="39 B9"
["CAPS"]="3A BA"
["CAPSLOCK"]="3A BA"
["F1"]="3B BB"
["F2"]="3C BC"
["F3"]="3D BD"
["F4"]="3E BE"
["F5"]="3F BF"
["F6"]="40 C0"
["F7"]="41 C1"
["F8"]="42 C2"
["F9"]="43 C3"
["F10"]="44 C4"
["UP"]="E0 48 E0 C8"
["RIGHT"]="E0 4D E0 CD"
["LEFT"]="E0 4B E0 CB"
["DOWN"]="E0 50 E0 D0"
["HOME"]="E0 47 E0 C7"
["END"]="E0 4F E0 CF"
["PGUP"]="E0 49 E0 C9"
["PGDN"]="E0 51 E0 D1"
["CMDprs"]="E0 5C"
["CMDrls"]="E0 DC"
# all codes below start with LSHIFTprs as commented in first item:
["!"]="2A 02 82 AA" # LSHIFTprs 1prs 1rls LSHIFTrls
["@"]="2A 03 83 AA"
["#"]="2A 04 84 AA"
["$"]="2A 05 85 AA"
["%"]="2A 06 86 AA"
["^"]="2A 07 87 AA"
["&"]="2A 08 88 AA"
["*"]="2A 09 89 AA"
["("]="2A 0A 8A AA"
[")"]="2A 0B 8B AA"
["_"]="2A 0C 8C AA"
["+"]="2A 0D 8D AA"
["Q"]="2A 10 90 AA"
["W"]="2A 11 91 AA"
["E"]="2A 12 92 AA"
["R"]="2A 13 93 AA"
["T"]="2A 14 94 AA"
["Y"]="2A 15 95 AA"
["U"]="2A 16 96 AA"
["I"]="2A 17 97 AA"
["O"]="2A 18 98 AA"
["P"]="2A 19 99 AA"
["{"]="2A 1A 9A AA"
["}"]="2A 1B 9B AA"
["A"]="2A 1E 9E AA"
["S"]="2A 1F 9F AA"
["D"]="2A 20 A0 AA"
["F"]="2A 21 A1 AA"
["G"]="2A 22 A2 AA"
["H"]="2A 23 A3 AA"
["J"]="2A 24 A4 AA"
["K"]="2A 25 A5 AA"
["L"]="2A 26 A6 AA"
[":"]="2A 27 A7 AA"
['"']="2A 28 A8 AA"
["~"]="2A 29 A9 AA"
["|"]="2A 2B AB AA"
["Z"]="2A 2C AC AA"
["X"]="2A 2D AD AA"
["C"]="2A 2E AE AA"
["V"]="2A 2F AF AA"
["B"]="2A 30 B0 AA"
["N"]="2A 31 B1 AA"
["M"]="2A 32 B2 AA"
["<"]="2A 33 B3 AA"
[">"]="2A 34 B4 AA"
["?"]="2A 35 B5 AA"
)
}
function clear_input_buffer_then_read() {
while read -d '' -r -t 0; do read -d '' -t 0.1 -n 10000; break; done
[[ -t 1 ]] && read
}
# read variable kbstring and convert string to scancodes and send to guest vm
function send_keys() {
scancode=$(for (( i=0; i < ${#kbstring}; i++ )); do
c[${i}]=${kbstring:${i}:1}; echo -n ${kscd[${c[${i}]}]}" "
done)
VBoxManage controlvm "${vm_name}" keyboardputscancode ${scancode} 1>/dev/null 2>&1
}
# read variable kbspecial and send keystrokes by name,
# for example "CTRLprs c CTRLrls", and send to guest vm
function send_special() {
scancode=""
for keypress in ${kbspecial}; do
scancode="${scancode}${kscd[${keypress}]}"" "
done
VBoxManage controlvm "${vm_name}" keyboardputscancode ${scancode} 1>/dev/null 2>&1
}
function send_enter() {
kbspecial="ENTER"
send_special
}
function prompt_lang_utils() {
# called after the virtual machine boots up
echo -ne "\n${highlight_color}Press enter when the Language window is ready.${default_color}"
clear_input_buffer_then_read
send_enter
echo -ne "\n${highlight_color}Press enter when the macOS Utilities window is ready.${default_color}"
clear_input_buffer_then_read
kbspecial='CTRLprs F2 CTRLrls u ENTER t ENTER'
send_special
}
function prompt_terminal_ready() {
# called after the Utilities window is ready
echo -ne "\n${highlight_color}Press enter when the Terminal command prompt is ready.${default_color}"
clear_input_buffer_then_read
}
function add_another_terminal() {
# at least one terminal has to be open before calling this function
kbspecial='CMDprs n CMDrls'
send_special
sleep 1
}
function if_num_of_terminals_lt_count_then_run_next_kbstring() {
# sleep if "${count}" or more bash shells are active
# when less than "${count}" are active, run "${next_string}"
# "${count}" and "${next_string}" need to be passed as positional parameters
local count="${1}"
local next_kbstring="${2}"
kbstring='while [ "$( ps -c | grep -c bash )" -ge '"${count}"' ]; do sleep 2; done; '"${next_kbstring}"
send_keys
send_enter
}
function cycle_through_terminal_windows() {
kbspecial='CMDprs ` CMDrls'
send_special
sleep 1
}
function would_you_like_to_know_less() {
if [[ -z "$(less --version 2>/dev/null)" ]]; then
echo -e " ${highlight_color}${0} documentation${default_color}"
else
echo -e " ${highlight_color}${0} documentation | less -R${default_color}"
fi
}
function prompt_delete_y_n() {
# workaround for zsh-bash differences in read
delete=""
if [[ -t 1 ]]; then
if [[ "${SHELL}" =~ /zsh ]]; then
read -s -q delete\?' [y/N] '
delete="${delete:l}"
elif [[ "${SHELL}" =~ /bash ]]; then
read -n 1 -p ' [y/N] ' delete
delete="${delete,,}"
fi
fi
echo ""
}
# command-line argument processing
check_shell
stages='
check_shell
check_gnu_coreutils_prefix
set_variables
welcome
check_dependencies
prompt_delete_existing_vm
create_vm
check_default_virtual_machine
prepare_macos_installation_files
create_nvram_files
create_macos_installation_files_viso
create_basesystem_virtual_disk
create_target_virtual_disk
create_installer_virtual_disk
configure_vm
populate_virtual_disks
populate_macos_target_disk
delete_temporary_files
'
[[ -z "${1}" ]] && for stage in ${stages}; do ${stage}; done && exit
[[ "${1}" = "documentation" ]] && documentation && exit
valid_arguments=(${stages//$'[\r\n]'/ } troubleshoot documentation)
for specified_arg in "$@"; do
there_is_a_match=""
# doing matching the long way to prevent delimiter confusion
for valid_arg in "${valid_arguments[@]}"; do
[[ "${valid_arg}" = "${specified_arg}" ]] && there_is_a_match="true" && break
done
if [[ -z "${there_is_a_match}" ]]; then
echo -e "\nOne or more specified arguments is not recognized."
echo -e "\nRecognized stages:\n${stages}"
echo -e "Other recognized arguments:\n\n documentation\n troubleshoot"
echo -e "\nView documentation by entering the following command:"
would_you_like_to_know_less
exit
fi
done
check_gnu_coreutils_prefix
set_variables
check_dependencies
for argument in "$@"; do ${argument}; done