Files
macos-virtualbox/macos-guest-virtualbox.sh

1219 lines
50 KiB
Bash
Executable File

#!/bin/bash
# Semi-automatic installer of macOS on VirtualBox
# (c) myspaghetti, licensed under GPL2.0 or higher
# url: https://github.com/myspaghetti/macos-guest-virtualbox
# version 0.80.0
# Requirements: 40GB available storage on host
# Dependencies: bash >= 4.3, xxd, gzip, unzip, wget, dmg2img,
# VirtualBox with Extension Pack >= 6.0
function set_variables() {
# Customize the installation by setting these variables:
vmname="macOS" # name of the VirtualBox virtual machine
macOS_release_name="Mojave" # install "HighSierra" "Mojave" or "Catalina"
storagesize=80000 # VM disk image size in MB. Minimum 22000
cpucount=2 # VM CPU cores, minimum 2
memorysize=4096 # VM RAM in MB, minimum 2048
gpuvram=128 # VM video RAM in MB, minimum 34, maximum 128
resolution="1280x800" # VM display resolution
# The following commented commands, when run 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 | awk '{ print $NF }'
DmiBoardSerial="NO_LOGIC_BOARD_SN"
MLB="${DmiBoardSerial}"
# nvram 4D1EDE05-38C7-4A6A-9CC6-4BCCA8B38C14:ROM | awk '{ print $NF }'
ROM='%aa*%bbg%cc%dd'
# ioreg -l -p IODeviceTree | grep \"system-id
SYSTEM_UUID="aabbccddeeff00112233445566778899"
# csrutil status
SYSTEM_INTEGRITY_PROTECTION='0x10' # '0x10' - enabled, '0x77' - disabled
# 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() {
printf "\n${low_contrast_color}$@${default_color}\n"
}
# don't need sleep when we can read!
function sleep() {
read -t "${1}" >/dev/null 2>&1
}
# welcome message
function welcome() {
printf '
Semi-automatic installer of macOS on VirtualBox
-------------------------------------------------------------------------------
This script installs only open-source software and unmodified Apple binaries.
The script checks for dependencies and will prompt to install them if unmet.
For iCloud and iMessage connectivity, the script needs to be edited with genuine
or genuine-like Apple parameters. macOS will work without these parameters, but
Apple-connected apps will not.
The installation requires about '"${highlight_color}"'40GB'"${default_color}"' of available storage, 25GB for
temporary installation files and 15GB for the virtual machine'"'"'s dynamically
allocated storage disk image.
The script can be resumed by stages, as described in the following command:
"'"${highlight_color}${0}"' stages'"${default_color}"'"
'"${highlight_color}"'Press enter to review the script settings.'"${default_color}"
read
# custom settings prompt
printf '
vmname="'"${vmname}"'" # name of the VirtualBox virtual machine
macOS_release_name="'"${macOS_release_name}"'" # install "HighSierra" "Mojave" or "Catalina"
storagesize='"${storagesize}"' # VM disk image size in MB. minimum 22000
cpucount='"${cpucount}"' # VM CPU cores, minimum 2
memorysize='"${memorysize}"' # VM RAM in MB, minimum 2048
gpuvram='"${gpuvram}"' # VM video RAM in MB, minimum 34, maximum 128
resolution="'"${resolution}"'" # VM display resolution
These values may be customized by editing them at the top of the script file.
'"${highlight_color}"'Press enter to continue, CTRL-C to exit.'"${default_color}"
read
}
# check dependencies
function check_bash_version() {
if [ -z "${BASH_VERSION}" ]; then
echo "Can't determine BASH_VERSION. Exiting."
exit
elif [ "${BASH_VERSION:0:1}" -lt 4 ]; then
echo "Please run this script on Bash 4.3 or higher."
if [ -n "$(sw_vers 2>/dev/null)" ]; then
echo "macOS detected. Make sure the script is not running on"
echo "the default /bin/bash which is version 3."
fi
exit
elif [ "${BASH_VERSION:0:1}" -eq 4 -a "${BASH_VERSION:2:1}" -le 2 ]; then
echo "Please run this script on Bash 4.3 or higher."
exit
fi
}
function check_gnu_coreutils_prefix() {
if [ -n "$(gcsplit --help 2>/dev/null)" ]; then
function csplit() {
gcsplit "$@"
}
function tac() {
gtac "$@"
}
function split() {
gsplit "$@"
}
function base64() {
gbase64 "$@"
}
function expr() {
gexpr "$@"
}
fi
}
function check_dependencies() {
# check if running on 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 ""
printf 'macOS 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}"'.\n'
echo "Please make sure the following packages are installed and that"
echo "their path is in the PATH variable:"
printf "${highlight_color}"'bash coreutils wget unzip dmg2img'"${default_color}"'\n'
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)" \
-o -z "$(gzip --help 2>/dev/null)" \
-o -z "$(unzip -hh 2>/dev/null)" \
-o -z "$(csplit --help 2>/dev/null)" \
-o -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 package is the GNU variant."
exit
fi
# wget supports --show-progress from version 1.16
if [[ "$(wget --version 2>/dev/null | head -n 1)" =~ 1\.1[6-9]|1\.2[0-9] ]]; 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"
printf '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
osrelease="$(cat /proc/sys/kernel/osrelease 2>/dev/null)"
if [[ "${osrelease}" =~ microsoft ]]; then # WSL2
echo ""
echo "The script is not tested with WSL2, which uses Hyper-V."
echo "VirtualBox Hyper-V support is experimental."
echo ""
printf "${highlight_color}"'Press enter to continue, CTRL-C to exit.'"${default_color}"
read
elif [[ ! ( "${osrelease}" =~ 18362-Microsoft ) ]]; then
echo ""
echo "The script requires Windows 10 version 1903 or higher to run properly on WSL."
echo "For lower versions, please run the script on a path on the Windows filesystem,"
printf 'for example '"${highlight_color}"'/mnt/c/Users/Public/Documents'"${default_color}"'\n\n'
printf "${highlight_color}"'Press enter to continue, CTRL-C to exit.'"${default_color}"
read
fi
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"
printf '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)"
if [ -z "${vbox_version}" -o -z "${vbox_version:2:1}" ]; then
echo "Can't determine VirtualBox version. Exiting."
exit
elif [[ ( "${vbox_version:0:1}" -lt 5 ) || ( "${vbox_version:0:1}" = 5 && "${vbox_version:2:1}" -lt 2 ) ]]; then
echo ""
echo "Please make sure VirtualBox version 5.2 or higher is installed."
echo "Exiting."
exit
elif [[ "${vbox_version:0:1}" = 5 ]]; then
echo ""
printf 'VirtualBox version '"${white_on_black}${vbox_version}${default_color}"' detected. Please see the following\n'
echo "URL for issues with the VISO filesystem on VirtualBox 5.2 to 5.2.32:"
echo ""
echo " https://github.com/myspaghetti/macos-guest-virtualbox/issues/86"
echo ""
printf "${white_on_black}"'Press enter to continue, CTRL-C to exit.'"${default_color}"
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" \
-o "$(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 "running 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 -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}"
printf 'As of 2019-12-11, macOS Catalina 10.15.2 '"${warning_color}"'does not boot'"${default_color}"' on VirtualBox.\n'
printf "${highlight_color}"'Press enter to continue, CTRL-C to exit.'"${default_color}"
read
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 "${vmname}" 2>/dev/null)" ]; then
printf '\nA virtual machine named "'"${vmname}"'" already exists.
'"${warning_color}"'Delete existing virtual machine "'"${vmname}"'"?'"${default_color}"
delete=""
read -n 1 -p ' [y/N] ' delete
echo ""
if [ "${delete,,}" == "y" ]; then
VBoxManage unregistervm "${vmname}" --delete
else
printf '
'"${highlight_color}"'Please assign a different VM name to variable "vmname" by editing the script,'"${default_color}"'
or skip this check manually as described when running the following command:
'"${0}"' stages\n'
exit
fi
fi
}
# Attempt to create new virtual machine named "${vmname}"
function create_vm() {
print_dimly "stage: create_vm"
if [ -n "$( VBoxManage createvm --name "${vmname}" --ostype "MacOS1013_64" --register 2>&1 >/dev/null )" ]; then
printf '\nError: Could not create virtual machine "'"${vmname}"'".
'"${highlight_color}"'Please delete exising "'"${vmname}"'" VirtualBox configuration files '"${warning_color}"'manually'"${default_color}"'.
Error message:
'
VBoxManage createvm --name "${vmname}" --ostype "MacOS1013_64" --register 2>/dev/tty
exit
fi
}
function prepare_macos_installation_files() {
print_dimly "stage: prepare_macos_installation_files"
# Find the correct download URL in the Apple catalog
if [[ ! ( -s "${macOS_release_name}_BaseSystem.chunklist" && -s "${macOS_release_name}_InstallInfo.plist" && -s "${macOS_release_name}_AppleDiagnostics.dmg" && -s "${macOS_release_name}_AppleDiagnostics.chunklist" && -s "${macOS_release_name}_BaseSystem.dmg" && -s "${macOS_release_name}_InstallESDDmg.pkg" ) ]]; then
echo ""
echo "Downloading 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 ""
echo "Couldn't download the Apple software update catalog."
if [ "$(expr match "$(cat "${macOS_release_name}_wget.log")" '.*ERROR[[:print:]]*is not trusted')" -gt "0" ]; then
printf '
Make sure certificates from a certificate authority are installed.
Certificates are often installed through the package manager with
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"*
printf "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 "Found download URL: ${urlbase}"
echo ""
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
fi
if [ ! -s "${macOS_release_name}_InstallESD.part00" ]; then
echo "Splitting 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."
echo ""
split -a 2 -d -b 1000000000 "${macOS_release_name}_InstallESDDmg.pkg" "${macOS_release_name}_InstallESD.part"
fi
if [[ ( ( "${vbox_version:0:1}" -lt 6 ) || ( "${vbox_version:0:1}" = 6 && "${vbox_version:2:1}" = 0 ) ) && ! ( -s "ApfsDriverLoader.efi" ) ]]; then
echo ""
echo "Downloading open-source APFS EFI drivers used for VirtualBox 6.0 and 5.2"
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"
# The NVRAM files are read and written by the EFI utility DmpStore.
# Each NVRAM file may contain multiple entries formatted as follows:
# namesize datasize name guid attributes data
#
# 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
#
# namesize, datasize, and attributes are four bytes each, little-endian byte-order.
# name is a string of chars, each char is followed by a null byte.
# The string is terminated with a null char followed by a null byte,
# for example "a" = "61 00 00 00", "abc" = "61 00 62 00 63 00 00 00"
# namesize counts all the bytes in name, including the null bytes and the terminators
# guid and data do not have a terminator.
# datasize counts the bytes of data
# For guid, each field in the first half is little-endian,
# each field in the second half is big-endian.
#
# crc32 of the entry is appended at the end of the entry
# DmpStore will not accept an entry without a valid crc32
#
# The script creates each file with only one entry for easier editing.
# The assignments below assume datasize and namesize are under 255 bytes for simplicity.
function generate_nvram_bin_file() {
# function takes exactly three ordered positional arguments: name, data (in hex bytes), guid
local name="${1}"
local namehex="$(printf -- "${name}" | xxd -p)00"
local namesize="$(printf "%02x" $(( ${#namehex} )) ) 00 00 00"
local filename="${name}"
local name="$(for (( i=0; i<"${#name}"; i++ )); do printf -- "${name:${i}:1}" | xxd -p | tr -d '\n'; printf '00'; done; printf '0000' )"
local data="$(printf -- "${2}" | xxd -r -p | xxd -p)"
local datasize="$(printf "%02x" $(( ${#data} / 2 )) ) 00 00 00"
local g="$( printf -- "${3}" | xxd -r -p | xxd -p )"
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}"
local attributes="07 00 00 00"
local entry="${namesize} ${datasize} ${name} ${guid} ${attributes} ${data}"
local crc32="$(printf "${entry}" | xxd -r -p | gzip -c | tail -c8 | od -tx4 -N4 -An --endian=big)"
printf -- "${entry} ${crc32}" | xxd -r -p - "${vmname}_${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 let j=i+1;
if [ "${ROM:${i}:1}" == "%" ]; then
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' > "startup.nsh"
if [[ ( "${vbox_version:0:1}" -lt 6 ) || ( "${vbox_version:0:1}" = 6 && "${vbox_version:2:1}" = 0 ) ]]; then
echo 'load fs0:\EFI\driver\AppleImageLoader.efi
load fs0:\EFI\driver\AppleUiSupport.efi
load fs0:\EFI\driver\ApfsDriverLoader.efi
map -r' >> "startup.nsh"
fi
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' >> "startup.nsh"
echo ""
echo "Creating VirtualBox 6 virtual ISO containing the"
echo "installation files from swcdn.apple.com"
echo ""
echo "--iprt-iso-maker-file-marker-bourne-sh 57c0ec7d-2112-4c24-a93f-32e6f08702b9
--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 "${vmname}_${filename}" ]; then
echo "/${filename}=${vmname}_${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 "/${filename}=${filename}" >> "${macOS_release_name}_Installation_files.viso"
fi
done
# EFI startup script
echo "/startup.nsh=startup.nsh" >> "${macOS_release_name}_Installation_files.viso"
}
# Create the macOS base system virtual disk image
function create_basesystem_vdi() {
print_dimly "stage: create_basesystem_vdi"
if [ -s "${macOS_release_name}_BaseSystem.vdi" ]; then
echo "${macOS_release_name}_BaseSystem.vdi bootstrap virtual disk image ready."
elif [ ! -s "${macOS_release_name}_BaseSystem.dmg" ]; then
echo ""
echo "Could not find ${macOS_release_name}_BaseSystem.dmg; exiting."
exit
else
echo "Converting to 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"
else
dmg2img "${macOS_release_name}_BaseSystem.dmg" "${macOS_release_name}_BaseSystem.img"
fi
VBoxManage convertfromraw --format VDI "${macOS_release_name}_BaseSystem.img" "${macOS_release_name}_BaseSystem.vdi"
if [ -s "${macOS_release_name}_BaseSystem.vdi" ]; then
rm "${macOS_release_name}_BaseSystem.img" 2>/dev/null
fi
fi
}
# Create the target virtual disk image
function create_target_vdi() {
print_dimly "stage: create_target_vdi"
if [ -w "${vmname}.vdi" ]; then
echo "${vmname}.vdi target system virtual disk image ready."
elif [ "${macOS_release_name}" = "Catalina" -a "${storagesize}" -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 [ "${storagesize}" -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
else
echo "Creating ${vmname} target system virtual disk image."
VBoxManage createmedium --size="${storagesize}" \
--filename "${vmname}.vdi" \
--variant standard 2>/dev/tty
fi
}
# Create the installation media virtual disk image
function create_install_vdi() {
print_dimly "stage: create_install_vdi"
if [ -w "Install ${macOS_release_name}.vdi" ]; then
echo "Installation media virtual disk image ready."
else
echo "Creating ${macOS_release_name} installation media virtual disk image."
VBoxManage createmedium --size=12000 \
--filename "Install ${macOS_release_name}.vdi" \
--variant standard 2>/dev/tty
fi
}
function configure_vm() {
print_dimly "stage: configure_vm"
VBoxManage modifyvm "${vmname}" --cpus "${cpucount}" --memory "${memorysize}" \
--vram "${gpuvram}" --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 "${vmname}" \
"VBoxInternal2/EfiGraphicsResolution" "${resolution}"
VBoxManage setextradata "${vmname}" \
"VBoxInternal/Devices/efi/0/Config/DmiSystemFamily" "${DmiSystemFamily}"
VBoxManage setextradata "${vmname}" \
"VBoxInternal/Devices/efi/0/Config/DmiSystemProduct" "${DmiSystemProduct}"
VBoxManage setextradata "${vmname}" \
"VBoxInternal/Devices/efi/0/Config/DmiSystemSerial" "${DmiSystemSerial}"
VBoxManage setextradata "${vmname}" \
"VBoxInternal/Devices/efi/0/Config/DmiSystemUuid" "${DmiSystemUuid}"
VBoxManage setextradata "${vmname}" \
"VBoxInternal/Devices/efi/0/Config/DmiOEMVBoxVer" "${DmiOEMVBoxVer}"
VBoxManage setextradata "${vmname}" \
"VBoxInternal/Devices/efi/0/Config/DmiOEMVBoxRev" "${DmiOEMVBoxRev}"
VBoxManage setextradata "${vmname}" \
"VBoxInternal/Devices/efi/0/Config/DmiBIOSVersion" "${DmiBIOSVersion}"
VBoxManage setextradata "${vmname}" \
"VBoxInternal/Devices/efi/0/Config/DmiBoardProduct" "${DmiBoardProduct}"
VBoxManage setextradata "${vmname}" \
"VBoxInternal/Devices/efi/0/Config/DmiBoardSerial" "${DmiBoardSerial}"
VBoxManage setextradata "${vmname}" \
"VBoxInternal/Devices/efi/0/Config/DmiSystemVendor" "Apple Inc."
VBoxManage setextradata "${vmname}" \
"VBoxInternal/Devices/efi/0/Config/DmiSystemVersion" "1.0"
VBoxManage setextradata "${vmname}" \
"VBoxInternal/Devices/smc/0/Config/DeviceKey" \
"ourhardworkbythesewordsguardedpleasedontsteal(c)AppleComputerInc"
VBoxManage setextradata "${vmname}" \
"VBoxInternal/Devices/smc/0/Config/GetKeyFromRealSMC" 0
}
# 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
declare -A ksc=(
["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"
)
# 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 ${ksc[${c[i]}]}" "; done)
VBoxManage controlvm "${vmname}" 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}${ksc[${keypress}]}"" "
done
VBoxManage controlvm "${vmname}" 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
printf '\n'"${highlight_color}"'Press enter when the Language window is ready.'"${default_color}"
read
send_enter
printf '\n'"${highlight_color}"'Press enter when the macOS Utilities window is ready.'"${default_color}"
read
kbspecial='CTRLprs F2 CTRLrls u ENTER t ENTER'
send_special
}
function prompt_terminal_ready() {
# called after the Utilities window is ready
printf '\n'"${highlight_color}"'Press enter when the Terminal command prompt is ready.'"${default_color}"
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 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 macOS --remove --name SATA >/dev/null 2>&1
VBoxManage storagectl "${vmname}" --add sata --name SATA --hostiocache on >/dev/null 2>&1
VBoxManage storageattach "${vmname}" --storagectl SATA --port 0 \
--type hdd --nonrotational on --medium "${vmname}.vdi" >/dev/null 2>&1
VBoxManage storageattach "${vmname}" --storagectl SATA --port 1 --hotpluggable on \
--type hdd --nonrotational on --medium "Install ${macOS_release_name}.vdi" >/dev/null 2>&1
VBoxManage storageattach "${vmname}" --storagectl SATA --port 2 --hotpluggable on \
--type hdd --nonrotational on --medium "${macOS_release_name}_BaseSystem.vdi" >/dev/null 2>&1
VBoxManage storageattach "${vmname}" --storagectl SATA --port 3 \
--type dvddrive --medium "${macOS_release_name}_Installation_files.viso" >/dev/null 2>&1
echo "Starting virtual machine ${vmname}. This should take a couple of minutes."
( VBoxManage startvm "${vmname}" >/dev/null 2>&1 )
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[@]}) && '\
'diskutil partitionDisk "/dev/${disks[0]}" 1 GPT APFS "'"${vmname}"'" 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/OS X 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" && '\
'cat InstallESD.part* > "${install_path}/InstallESD.dmg" && '\
'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
printf 'Partitioning the optional assistive virtual disk and copying the optional
NVRAM files. Partitioning the target, the installer, and the assistive virtual
disks. Loading base system onto the installer virtual dis. 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.
'"${highlight_color}"'If the partitioning fails, exit the script by pressing CTRL-C.'"${default_color}"
print_dimly "Otherwise, please wait."
# Detach the original 2GB BaseSystem.vdi
while [[ "$( VBoxManage list runningvms )" =~ ^\""${vmname}" ]]; do sleep 2 >/dev/null 2>&1; done;
# Release basesystem vdi from VirtualBox configuration
VBoxManage storageattach "${vmname}" --storagectl SATA --port 2 --medium none >/dev/null 2>&1
VBoxManage closemedium "${macOS_release_name}_BaseSystem.vdi" >/dev/null 2>&1
echo "${macOS_release_name}_BaseSystem.vdi detached from the virtual machine"
echo "and released from VirtualBox Manager."
}
function configure_nvram_parameters() {
print_dimly "stage: configure_nvram_parameters"
echo "Configuring the NVRAM is not required to run macOS."
echo ""
echo "The NVRAM parameter assignment can be edited at the top of the script."
echo "If the NVRAM parameters are set to the defaults, or if the virtual machine"
echo "is powered up, or if the BaseSystem.vdi is not available, the NVRAM"
echo "configuration will be skipped."
if [[ ! ( "$( VBoxManage list runningvms )" =~ ^\""${vmname}" ) && ( -s "${macOS_release_name}_BaseSystem.vdi") && ! ( "${DmiBoardSerial}" = "NO_LOGIC_BOARD_SN" && "${MLB}" = "${DmiBoardSerial}" && "${ROM}" = '%aa*%bbg%cc%dd' && "${SYSTEM_UUID}" = "aabbccddeeff00112233445566778899" && "${SYSTEM_INTEGRITY_PROTECTION}" = '0x10' ) ]]
then
if [ -w "${vmname}_nvram_files.vdi" ]; then
echo "NVRAM files virtual disk image ready."
else
echo "Creating ${vmname} NVRAM files virtual disk image."
VBoxManage createmedium --size=100 \
--filename "${vmname}_nvram_files.vdi" \
--variant standard 2>/dev/tty
fi
print_dimly "The following stages are executed as part of configure_nvram_parameters:"
configure_vm
create_nvram_files
create_macos_installation_files_viso
echo "Attaching the base system disk image, the VISO,"
echo "and the NVRAM files disk image to the virtual machine."
VBoxManage storagectl macOS --remove --name SATA >/dev/null 2>&1
VBoxManage storagectl "${vmname}" --add sata --name SATA --hostiocache on >/dev/null 2>&1
VBoxManage storageattach "${vmname}" --storagectl SATA --port 1 --hotpluggable on \
--type hdd --nonrotational on --medium "${macOS_release_name}_BaseSystem.vdi" >/dev/null 2>&1
VBoxManage storageattach "${vmname}" --storagectl SATA --port 2 \
--type dvddrive --medium "${macOS_release_name}_Installation_files.viso" >/dev/null 2>&1
VBoxManage storageattach "${vmname}" --storagectl SATA --port 0 --hotpluggable on \
--type hdd --nonrotational on --medium "${vmname}_nvram_files.vdi" >/dev/null 2>&1
echo "Starting virtual machine ${vmname}. This should take a couple of minutes."
( VBoxManage startvm "${vmname}" >/dev/null 2>&1 )
prompt_lang_utils
prompt_terminal_ready
# Find the 104MB nvram files disk and partition as FAT32; not necesasry for installing macOS
kbstring='disks="$(diskutil list | grep -o "104[^ ]* MB *disk[0-9]$" | sort -gr | grep -o disk[0-9] )" && disks=(${disks[@]}) && '\
'diskutil partitionDisk "/dev/${disks[0]}" 1 GPT FAT32 "NVRAMFILES" R && '\
'mkdir -p "/Volumes/NVRAMFILES/EFI/NVRAM" && '\
'cp "/Volumes/'"${macOS_release_name:0:5}-files"'/"*.bin "/Volumes/NVRAMFILES/EFI/NVRAM/" && '\
'cp "/Volumes/'"${macOS_release_name:0:5}-files"'/"*.nsh "/Volumes/NVRAMFILES/" && '\
'shutdown -h now'
send_keys
send_enter
printf '
'"${highlight_color}"'Please start the "'"${vmname}"'" virtual machine '"${warning_color}"'manually'"${highlight_color}"' and press Esc'"${default_color}"'
'"${highlight_color}"'immediately when the VirtualBox logo appears to enter the boot menu.'"${default_color}"'
Choose "'"${highlight_color}"'Boot Manager'"${default_color}"'" and then "'"${highlight_color}"'EFI Internal Shell'"${default_color}"'".
The EFI shell startup script will run and update the virtual machine'"'"'s NVRAM
parameters. It is not necessary to allow macOS to continue booting after the
script has run.
Power off the virtual machine then press enter to continue.'
read
else
printf '
The following command can be used to create and transfer the NVRAM files to the
fully installed macOS VM at any time:
'"${0} create_nvram_files create_macos_installation_files_viso"'
Attach the VISO file to the VM and copy the bin files from the VISO to the
macOS boot EFI partition and run the EFI Internal Shell script. This is only
required if the NVRAM parameters are updated after the installation. Otherwise,
the current NVRAM files will already be present and the script does not need to
be run again.'
fi
}
function populate_macos_target() {
print_dimly "stage: populate_macos_target"
if [[ "$( VBoxManage list runningvms )" =~ ^\""${vmname}" ]]; then
printf "${highlight_color}"'Please '"${warning_color}"'manually'"${highlight_color}"' shut down the virtual machine and press enter to continue.'"${default_color}"
read
fi
VBoxManage storagectl macOS --remove --name SATA >/dev/null 2>&1
VBoxManage storagectl "${vmname}" --add sata --name SATA --hostiocache on >/dev/null 2>&1
VBoxManage storageattach "${vmname}" --storagectl SATA --port 0 \
--type hdd --nonrotational on --medium "${vmname}.vdi" >/dev/null 2>&1
VBoxManage storageattach "${vmname}" --storagectl SATA --port 1 --hotpluggable on \
--type hdd --nonrotational on --medium "Install ${macOS_release_name}.vdi" >/dev/null 2>&1
VBoxManage storageattach "${vmname}" --storagectl SATA --port 2 \
--type dvddrive --medium "${macOS_release_name}_Installation_files.viso" >/dev/null 2>&1
echo "The VM will boot from the populated installer base system virtual disk."
( VBoxManage startvm "${vmname}" >/dev/null 2>&1 )
prompt_lang_utils
prompt_terminal_ready
add_another_terminal
echo ""
echo "The second open Terminal in the virtual machine copies EFI and NVRAM files"
echo "to the target EFI partition when the installer finishes preparing."
# run 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/'"${vmname}"'/tmp/mount_efi" && '\
'mount_msdos /dev/${disks[0]}s1 "/Volumes/'"${vmname}"'/tmp/mount_efi" && '\
'mkdir -p "/Volumes/'"${vmname}"'/tmp/mount_efi/EFI/driver/" && '\
'mkdir -p "/Volumes/'"${vmname}"'/tmp/mount_efi/EFI/NVRAM/" && '\
'cp "/Volumes/'"${macOS_release_name:0:5}-files"'/"*.efi "/Volumes/'"${vmname}"'/tmp/mount_efi/EFI/driver/" && '\
'cp "/Volumes/'"${macOS_release_name:0:5}-files"'/"*.bin "/Volumes/'"${vmname}"'/tmp/mount_efi/EFI/NVRAM/" && '\
'cp "/Volumes/'"${macOS_release_name:0:5}-files"'/startup.nsh" "/Volumes/'"${vmname}"'/tmp/mount_efi/startup.nsh" && '\
'installer_pid=$(ps | grep startosinstall | 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 partition,
# then sends SIGUSR1 to the installer which restarts the virtual machine
echo ""
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/'"${vmname}"'"'
send_keys
send_enter
printf "${highlight_color}"'When the VM reboots, press enter'"${default_color}"' or alternatively
manually detach the virtual storage device "'"Install ${macOS_release_name}.vdi"'"
to avoid booting into the installer environment again.'
read
VBoxManage controlvm "${vmname}" poweroff >/dev/null 2>&1
for (( i=10; i>5; i-- )); do printf ' \r'"${i}"; sleep 0.5; done
VBoxManage storagectl macOS --remove --name SATA >/dev/null 2>&1
VBoxManage storagectl "${vmname}" --add sata --name SATA --hostiocache on >/dev/null 2>&1
VBoxManage storageattach "${vmname}" --storagectl SATA --port 0 \
--type hdd --nonrotational on --medium "${vmname}.vdi"
echo ""
for (( i=5; i>0; i-- )); do printf ' \r'"${i}"; sleep 0.5; done
printf '
'"${highlight_color}"'That'"'"'s it! Enjoy your virtual machine.'"${default_color}"'\n'
}
function delete_temporary_files() {
print_dimly "stage: delete_temporary_files"
if [[ "$( VBoxManage list runningvms )" =~ ^\""${vmname}" ]];
then
printf 'Temporary files may be deleted when the virtual machine is shut down
by running the following command at the script'"'"'s working directory:
'"${0} delete_temporary_files"'\n'
else
# detach temporary VDIs and attach the macOS target disk
VBoxManage storagectl macOS --remove --name SATA >/dev/null 2>&1
VBoxManage storagectl "${vmname}" --add sata --name SATA --hostiocache on >/dev/null 2>&1
VBoxManage storageattach "${vmname}" --storagectl SATA --port 0 \
--type hdd --nonrotational on --medium "${vmname}.vdi"
VBoxManage closemedium "Install ${macOS_release_name}.vdi" >/dev/null 2>&1
VBoxManage closemedium "${macOS_release_name}_BaseSystem.vdi" >/dev/null 2>&1
printf 'The follwing files are safe to delete:
"'"${macOS_release_name}_Apple"*'"
"'"${macOS_release_name}_BaseSystem"*'"
"'"${macOS_release_name}_Install"*'"
"'"Install ${macOS_release_name}.vdi"'"
"'"${vmname}_"*".bin"'"
"'"startup.nsh"'"\n'
if [ -w "ApfsDriverLoader.efi" ]; then
printf ' "'"ApfsDriverLoader.efi"'"
"'"Apple"*".efi"'"
"'"AppleSupport-v2.0.4-RELEASE.zip"'"\n'
fi
if [ -w "dmg2img.exe" ]; then
printf ' "'"dmg2img.exe"'"\n'
fi
echo ""
printf "${warning_color}"'Delete temporary files?'"${default_color}"
delete=""
read -n 1 -p " [y/N] " delete
echo ""
if [ "${delete,,}" == "y" ]; then
rm "${macOS_release_name}_Apple"* \
"${macOS_release_name}_BaseSystem"* \
"${macOS_release_name}_Install"* \
"Install ${macOS_release_name}.vdi" \
"${vmname}_"*".bin" \
"startup.nsh" 2>/dev/null
rm "ApfsDriverLoader.efi" \
"Apple"*".efi" \
"AppleSupport-v2.0.4-RELEASE.zip" 2>/dev/null
rm "dmg2img.exe" 2>/dev/null
fi
fi
}
function stages() {
printf '\nUSAGE: '"${highlight_color}${0}"' [STAGE]...'"${default_color}"'
The script is divided into stages that run as separate functions.
Add one or more stage titles to the command line to run the corresponding
function. If the first argument is "stages" all others are ignored.
Some examples:
"'"${0}"' populate_virtual_disks populate_installer_app"
These stages might be useful by themselves if the VDI files and the VM are
already initialized.
"'"${0}"' configure_vm"
This stage might be useful after copying an existing VM VDI to a different
VirtualBox installation and having the script automatically configure the VM.
Dependency-checking and variable-setting is always performed first, whether
or not these stages are specified.
'
printf 'Press enter to continue.'
read
printf '
Available stage titles:
check_bash_version - configure_vm
check_gnu_coreutils_prefix / populate_virtual_disks
set_variables | configure_nvram_parameters
welcome | populate_macos_target
check_dependencies | delete_temporary_files
prompt_delete_existing_vm |
create_vm |
prepare_macos_installation_files |
create_nvram_files |
create_macos_installation_files_viso |
create_basesystem_vdi |
create_target_vdi |
create_install_vdi ------------------/
'
}
if [ -z "${1}" ]; then
check_bash_version
check_gnu_coreutils_prefix
set_variables
welcome
check_dependencies
prompt_delete_existing_vm
create_vm
prepare_macos_installation_files
create_nvram_files
create_macos_installation_files_viso
create_basesystem_vdi
create_target_vdi
create_install_vdi
configure_vm
populate_virtual_disks
configure_nvram_parameters
populate_macos_target
delete_temporary_files
else
if [ "${1}" != "stages" ]; then
check_bash_version
check_gnu_coreutils_prefix
set_variables
check_dependencies
for argument in "$@"; do ${argument}; done
else
stages
fi
fi