handle spaces in paths and filenames correctly

- default VM name changed to "macOS Mojave"
- VBoxManage now works with spaces in paths and filenames
- wget check is more robust
- fixed temporary files cleanup for WSL
- corrected command line argument usage logic
This commit is contained in:
img2tab
2019-05-12 10:09:46 +00:00
committed by GitHub
parent dec702eb7e
commit 27ae878573
+80 -56
View File
@@ -2,14 +2,14 @@
# One-key semi-automatic installer of macOS on VirtualBox # One-key semi-automatic installer of macOS on VirtualBox
# (c) img2tab, licensed under GPL2.0 or higher # (c) img2tab, licensed under GPL2.0 or higher
# url: https://github.com/img2tab/macos-guest-virtualbox # url: https://github.com/img2tab/macos-guest-virtualbox
# version 0.60.2 # version 0.61
# Requirements: 37.5GB available storage on host # Requirements: 37.5GB available storage on host
# Dependencies: bash>=4.0, unzip, wget, dmg2img, # Dependencies: bash>=4.0, unzip, wget, dmg2img,
# VirtualBox with Extension Pack >=5.2 # VirtualBox with Extension Pack >=5.2
# Customize the installation by setting these variables: # Customize the installation by setting these variables:
vmname="Mojave" # name of the VirtualBox virtual machine vmname="macOS Mojave" # name of the VirtualBox virtual machine
storagesize=22000 # VM disk image size in MB. minimum 22000 storagesize=22000 # VM disk image size in MB. minimum 22000
cpucount=2 # VM CPU cores, minimum 2 cpucount=2 # VM CPU cores, minimum 2
memorysize=4096 # VM RAM in MB, minimum 2048 memorysize=4096 # VM RAM in MB, minimum 2048
@@ -35,7 +35,7 @@ This script installs only open-source software and unmodified Apple binaries.
The script checks for dependencies and will prompt to install them if unmet. The script checks for dependencies and will prompt to install them if unmet.
Some stages may fail due to errant keyboard presses; run the script with Some stages may fail due to errant keyboard presses; run the script with
"'${whiteonblack}${0}' stages'${defaultcolor}'" to see how to run only certain stages. "'${whiteonblack}"${0}"' stages'${defaultcolor}'" to see how to run only certain stages.
For iCloud and iMessage connectivity, you will need to provide a valid For iCloud and iMessage connectivity, you will need to provide a valid
Apple serial number. macOS will work without it, but not Apple-connected apps. Apple serial number. macOS will work without it, but not Apple-connected apps.
@@ -48,17 +48,17 @@ read
# custom settings prompt # custom settings prompt
printf ' printf '
vmname="'${vmname}'" # name of the VirtualBox virtual machine vmname="'"${vmname}"'" # name of the VirtualBox virtual machine
storagesize='${storagesize}' # VM disk image size in MB. minimum 22000 storagesize='"${storagesize}"' # VM disk image size in MB. minimum 22000
cpucount='${cpucount}' # VM CPU cores, minimum 2 cpucount='"${cpucount}"' # VM CPU cores, minimum 2
memorysize='${memorysize}' # VM RAM in MB, minimum 2048 memorysize='"${memorysize}"' # VM RAM in MB, minimum 2048
gpuvram='${gpuvram}' # VM video RAM in MB, minimum 34, maximum 128 gpuvram='"${gpuvram}"' # VM video RAM in MB, minimum 34, maximum 128
resolution="'${resolution}'" # VM display resolution resolution="'"${resolution}"'" # VM display resolution
serialnumber="'${serialnumber}'" # valid serial required for iCloud, iMessage. serialnumber="'"${serialnumber}"'" # valid serial required for iCloud, iMessage.
# Structure: PPPYWWUUUMMM - Plant, Year, Week, Unique identifier, Model # Structure: PPPYWWUUUMMM - Plant, Year, Week, Unique identifier, Model
# Whether the serial is valid depends on the device name and board, below: # Whether the serial is valid depends on the device name and board, below:
devicename="'${devicename}'" # personalize to match serial if desired devicename="'"${devicename}"'" # personalize to match serial if desired
boardid="'${boardid}'" boardid="'"${boardid}"'"
These values may be customized by editing them at the top of the script file. These values may be customized by editing them at the top of the script file.
@@ -68,6 +68,13 @@ read
# check dependencies # check dependencies
function check_dependencies() { function check_dependencies() {
# check if running on macOS
if [ -n "$(sw_vers 2>/dev/null)" ]; then
printf '\nThis script is not tested on macOS. Exiting.\n'
exit
fi
# check Bash version
if [ -z "${BASH_VERSION}" ]; then if [ -z "${BASH_VERSION}" ]; then
echo "Can't determine BASH_VERSION. Exiting." echo "Can't determine BASH_VERSION. Exiting."
exit exit
@@ -76,14 +83,17 @@ elif [ "${BASH_VERSION:0:1}" -lt 4 ]; then
exit exit
fi fi
# check for unzip, coreutils, wget
if [ -z "$(unzip -hh 2>/dev/null)" \ if [ -z "$(unzip -hh 2>/dev/null)" \
-o -z "$(head --version 2>/dev/null)" \
-o -z "$(wget --version 2>/dev/null)" ]; then -o -z "$(wget --version 2>/dev/null)" ]; then
echo "Please install the packages 'unzip' and 'wget'." echo "Please make sure the following packages are installed:"
echo "coreutils unzip wget"
exit exit
fi fi
# wget supports --show-progress from version 1.16 # wget supports --show-progress from version 1.16
if [ "$(wget --version | head -n 1 | cut -c 12-13)" -ge "16" 2>/dev/null ]; then if [[ "$(wget --version 2>/dev/null | head -n 1)" =~ 1\.1[6-9]|1\.2[0-9] ]]; then
wgetargs="--quiet --continue --show-progress" # pretty wgetargs="--quiet --continue --show-progress" # pretty
else else
wgetargs="--continue" # ugly wgetargs="--continue" # ugly
@@ -91,18 +101,23 @@ fi
# VirtualBox in ${PATH} # VirtualBox in ${PATH}
if [ -z "$(VBoxManage -v 2>/dev/null)" ]; then if [ -z "$(VBoxManage -v 2>/dev/null)" ]; then
if [ -n "$('/mnt/c/Program Files/Oracle/VirtualBox/VBoxManage.exe' -v 2>/dev/null)" ]; then absolute_path_VBoxManage='C:\Program Files\Oracle\VirtualBox\VBoxManage.exe'
# If VBoxManage.exe is in the standard install location, use it. echo "Can't find VBoxManage in PATH variable,"
echo "checking ${absolute_path_VBoxManage}"
if [ -n "$(cmd.exe /d /s /c call "${absolute_path_VBoxManage}" -v 2>/dev/null)" ]; then
function VBoxManage() { function VBoxManage() {
'/mnt/c/Program Files/Oracle/VirtualBox/VBoxManage.exe' $@ cmd /d /s /c call "${absolute_path_VBoxManage}" "$@"
} }
elif [ -n "$('/cygdrive/c/Program Files/Oracle/VirtualBox/VBoxManage.exe' -v 2>/dev/null)" ]; then echo "Found VBoxManage"
elif [ -n "$("${absolute_path_VBoxManage}" -v 2>/dev/null)" ]; then
function VBoxManage() { function VBoxManage() {
'/cygdrive/c/Program Files/Oracle/VirtualBox/VBoxManage.exe' $@ "${absolute_path_VBoxManage}" "$@"
} }
echo "Found VBoxManage"
else else
echo "Please make sure VirtualBox is installed, and that the path to" echo "Please make sure VirtualBox is installed, and that the path to"
echo "the VBoxManage executable is in the PATH variable." echo "the VBoxManage executable is in the PATH variable, or assigned"
echo "in the script, with the executable, to the variable absolute_path_VBoxManage"
exit exit
fi fi
fi fi
@@ -117,26 +132,18 @@ if [ "$(expr match "${extpacks}" '.*Oracle VM VirtualBox Extension Pack')" -le "
fi fi
# Windows Subsystem for Linux (WSL) # Windows Subsystem for Linux (WSL)
microsoft_path="" # if the path contains spaces, please assign a read-write-able path without spaces microsoft_path='' # a read-write-able path so cmd.exe doesn't use C:\Windows\System32
if [[ -z "${microsoft_path}" && "$(cat /proc/sys/kernel/osrelease 2>/dev/null)" == *"Microsoft"* ]]; then if [[ -z "${microsoft_path}" && "$(cat /proc/sys/kernel/osrelease 2>/dev/null)" =~ Microsoft ]]; then
microsoft_path="$(cmd.exe /C cd)" # returns working directory in Microsoft-style (backslash) path microsoft_path="$(cmd.exe /C cd)" # returns working directory in Microsoft-style (backslash) path
microsoft_path="${microsoft_path:0:-1}"'\' # remove trailing newline \M microsoft_path="${microsoft_path:0:-1}"'\' # remove trailing newline \M
if [[ "${microsoft_path,,}" =~ .*sytem32.* ]]; then echo "Please run this script without elevated privileges.\n"; exit; fi if [[ "${microsoft_path,,}" =~ system32 ]]; then
if [[ ${microsoft_path} == *" "* || ${vmname} == *" "* ]]; then echo "Please assign the script's microsoft_path variable as a Microsoft-style path"
printf 'VBoxManage behaves unexpectedly when interpreting variables with whitespace echo 'ending with a slash, for example C:\Users\Public\Desktop\'
on Windows Subsystem for Linux. '${whiteonblack}'Please assign values without whitespace'${defaultcolor}' to the echo ""
variables "microsoft_path", a Windows-style backslash-terminated path for a readable
and writable working directory (for example "C:\\Users\\Default\\"), and "vmname".\n'
exit exit
fi fi
fi fi
# macOS
if [ -n "$(sw_vers 2>/dev/null)" ]; then
printf '\nThis script is not tested on macOS. Exiting.\n'
exit
fi
# dmg2img # dmg2img
if [ -z "$(dmg2img -d 2>/dev/null)" ]; then if [ -z "$(dmg2img -d 2>/dev/null)" ]; then
if [ -z "$(cygcheck -V 2>/dev/null)" ]; then if [ -z "$(cygcheck -V 2>/dev/null)" ]; then
@@ -158,11 +165,11 @@ if [ -z "$(dmg2img -d 2>/dev/null)" ]; then
fi fi
# Find the correct download URL in the Apple catalog # Find the correct download URL in the Apple catalog
echo "Downloading Apple macOS Mojave software update catalog" echo "Searching Apple macOS Mojave software update catalog"
wget 'http://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' \ wget 'http://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' \
${wgetargs} \ ${wgetargs} \
--output-document='apple.sucatalog.tmp' --output-document='apple.sucatalog.tmp'
echo "Finding latest macOS InstallAssistant entry" echo "Trying to find latest macOS InstallAssistant entry"
tac "apple.sucatalog.tmp" | csplit - '/InstallAssistantAuto.smd/+1' -f "applecatalog.tmp." -s tac "apple.sucatalog.tmp" | csplit - '/InstallAssistantAuto.smd/+1' -f "applecatalog.tmp." -s
urlbase="$(tail -n 1 "applecatalog.tmp.00")" urlbase="$(tail -n 1 "applecatalog.tmp.00")"
urlbase="$(expr match "${urlbase}" '.*\(http://[^<]*/\)')" urlbase="$(expr match "${urlbase}" '.*\(http://[^<]*/\)')"
@@ -182,8 +189,8 @@ echo "Found BaseSystem.dmg URL: ${urlbase}BaseSystem.dmg"
# Prompt to delete existing virtual machine config: # Prompt to delete existing virtual machine config:
function prompt_delete_existing_vm() { function prompt_delete_existing_vm() {
if [ -n "$(VBoxManage showvminfo "${vmname}" 2>/dev/null)" ]; then if [ -n "$(VBoxManage showvminfo "${vmname}" 2>/dev/null)" ]; then
printf '\n'"${vmname}"' virtual machine already exists. printf '\n"'"${vmname}"'" virtual machine already exists.
'${whiteonred}'Delete existing virtual machine "'${vmname}'"?'${defaultcolor} '${whiteonred}'Delete existing virtual machine "'"${vmname}"'"?'${defaultcolor}
delete="" delete=""
read -n 1 -p " [y/n] " delete 2>/dev/tty read -n 1 -p " [y/n] " delete 2>/dev/tty
echo "" echo ""
@@ -191,7 +198,7 @@ if [ -n "$(VBoxManage showvminfo "${vmname}" 2>/dev/null)" ]; then
VBoxManage unregistervm "${vmname}" --delete VBoxManage unregistervm "${vmname}" --delete
else else
printf '\n'${whiteonblack}'Please assign a different VM name to variable "vmname" by editing the script,'${defaultcolor}' printf '\n'${whiteonblack}'Please assign a different VM name to variable "vmname" by editing the script,'${defaultcolor}'
or skip this check manually as described in "'${0}' stages".\n' or skip this check manually as described in "'"${0}"' stages".\n'
exit exit
fi fi
fi fi
@@ -200,8 +207,8 @@ fi
# Attempt to create new virtual machine named "${vmname}" # Attempt to create new virtual machine named "${vmname}"
function create_vm() { function create_vm() {
if [ -n "$(VBoxManage createvm --name "${vmname}" --ostype "MacOS1013_64" --register 2>&1 1>/dev/null)" ]; then if [ -n "$(VBoxManage createvm --name "${vmname}" --ostype "MacOS1013_64" --register 2>&1 1>/dev/null)" ]; then
printf '\nError: Could not create virtual machine "'${vmname}'". printf '\nError: Could not create virtual machine "'"${vmname}"'".
'${whiteonblack}'Please delete exising "'${vmname}'" VirtualBox configuration files '${whiteonred}'manually'${defaultcolor}'. '${whiteonblack}'Please delete exising "'"${vmname}"'" VirtualBox configuration files '${whiteonred}'manually'${defaultcolor}'.
Error message: Error message:
' '
@@ -254,12 +261,12 @@ fi
# Create the installation media virtual disk image: # Create the installation media virtual disk image:
function create_install_vdi() { function create_install_vdi() {
if [ -w "Install_${vmname}.vdi" ]; then if [ -w "Install ${vmname}.vdi" ]; then
echo "Installation media virtual disk image ready." echo "Installation media virtual disk image ready."
else else
echo "Creating ${vmname} installation media virtual disk image." echo "Creating ${vmname} installation media virtual disk image."
VBoxManage createmedium --size=12000 \ VBoxManage createmedium --size=12000 \
--filename "${microsoft_path}Install_${vmname}.vdi" \ --filename "${microsoft_path}Install ${vmname}.vdi" \
--variant fixed 2>/dev/tty --variant fixed 2>/dev/tty
fi fi
} }
@@ -271,7 +278,7 @@ VBoxManage storagectl "${vmname}" --add sata --name SATA --hostiocache on
VBoxManage storageattach "${vmname}" --storagectl SATA --port 0 \ VBoxManage storageattach "${vmname}" --storagectl SATA --port 0 \
--type hdd --nonrotational on --medium "${microsoft_path}${vmname}.vdi" --type hdd --nonrotational on --medium "${microsoft_path}${vmname}.vdi"
VBoxManage storageattach "${vmname}" --storagectl SATA --port 1 \ VBoxManage storageattach "${vmname}" --storagectl SATA --port 1 \
--type hdd --nonrotational on --medium "${microsoft_path}Install_${vmname}.vdi" --type hdd --nonrotational on --medium "${microsoft_path}Install ${vmname}.vdi"
VBoxManage storageattach "${vmname}" --storagectl SATA --port 2 \ VBoxManage storageattach "${vmname}" --storagectl SATA --port 2 \
--type hdd --nonrotational on --medium "${microsoft_path}BaseSystem.vdi" --type hdd --nonrotational on --medium "${microsoft_path}BaseSystem.vdi"
} }
@@ -610,7 +617,7 @@ sendkeys
echo "" echo ""
printf 'In the VM, '${whiteonred}'manually'${defaultcolor}' right-click on AppleSupport-v2.0.4-RELEASE.zip printf 'In the VM, '${whiteonred}'manually'${defaultcolor}' right-click on AppleSupport-v2.0.4-RELEASE.zip
and click '${whiteonblack}'"Download Linked File As..."'${defaultcolor}', then from the dropdown menu and click '${whiteonblack}'"Download Linked File As..."'${defaultcolor}', then from the dropdown menu
select '${vmname}' for 'Where:', then unbind the mouse cursor from the virtual select "'"${vmname}"'" for "Where:", then unbind the mouse cursor from the virtual
machine with the '${whiteonblack}'right control key'${defaultcolor}' or "host key".' machine with the '${whiteonblack}'right control key'${defaultcolor}' or "host key".'
echo "" echo ""
read -p "Click here and press enter when the download is complete." read -p "Click here and press enter when the download is complete."
@@ -638,6 +645,8 @@ sendkeys
promptterminalready promptterminalready
# create startup.nsh EFI script # create startup.nsh EFI script
echo ""
echo "Creating EFI startup script that searches for boot.efi"
kbstring='cd "/Volumes/'"${vmname}"'/mount_efi/" && vim startup.nsh' kbstring='cd "/Volumes/'"${vmname}"'/mount_efi/" && vim startup.nsh'
sendkeys sendkeys
@@ -680,20 +689,30 @@ VBoxManage storageattach "${vmname}" --storagectl SATA --port 1 --medium none
function boot_macos_installer() { function boot_macos_installer() {
echo "The VM will boot from the target virtual disk image." echo "The VM will boot from the target virtual disk image."
VBoxManage startvm "${vmname}" VBoxManage startvm "${vmname}"
echo ""
echo "macOS Mojave 10.14 will now install and start up."
echo ""
printf '\nmacOS Mojave 10.14 will now install and start up. # temporary files cleanup
VBoxManage closemedium "${microsoft_path}BaseSystem.vdi"
Temporary files are safe to delete. '${whiteonred}'Delete temporary files?'${defaultcolor} VBoxManage closemedium "${microsoft_path}Install ${vmname}.vdi"
if [ -z "${microsoft_path}" ]; then
printf 'Temporary files are safe to delete. '${whiteonred}'Delete temporary files?'${defaultcolor}
delete="" delete=""
read -n 1 -p " [y/n] " delete 2>/dev/tty read -n 1 -p " [y/n] " delete 2>/dev/tty
echo "" echo ""
if [ "${delete}" == "y" ]; then if [ "${delete}" == "y" ]; then
# temporary files cleanup rm "BaseSystem.vdi" "Install ${vmname}.vdi"
VBoxManage closemedium "${microsoft_path}BaseSystem.vdi"
VBoxManage closemedium "${microsoft_path}Install_${vmname}.vdi"
rm "BaseSystem.vdi" "Install_${vmname}.vdi"
rm "apple.sucatalog.tmp" "applecatalog.tmp.00" "applecatalog.tmp.01" rm "apple.sucatalog.tmp" "applecatalog.tmp.00" "applecatalog.tmp.01"
fi fi
else
echo "You may manually delete the following temporary files:"
echo "\"${microsoft_path}BaseSystem.vdi\""
echo "\"${microsoft_path}Install ${vmname}.vdi\""
echo "and the temporary files \"apple*.tmp\" \"apple*.tmp.*\""
echo "from the script's working directory."
echo ""
fi
printf 'macOS Mojave 10.14 installation should complete in a few minutes. printf 'macOS Mojave 10.14 installation should complete in a few minutes.
@@ -712,11 +731,12 @@ printf '\nUSAGE: '${whiteonblack}${0}' [STAGE]...'${defaultcolor}'
The script is divided into stages that run as separate functions. 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 Add one or more stage titles to the command line to run the corresponding
function. Some examples: function. If the first argument is "stages" all others are ignored.
"'${0}' populate_virtual_disks install_the_installer" Some examples:
"'"${0}"' populate_virtual_disks install_the_installer"
These stages might be useful by themselves if the VDI files and the VM are These stages might be useful by themselves if the VDI files and the VM are
already set. already set.
"'${0}' configure_vm" "'"${0}"' configure_vm"
This stage might be useful after copying an existing VDI to a different This stage might be useful after copying an existing VDI to a different
VirtualBox installation and having the script automatically configure the VM. VirtualBox installation and having the script automatically configure the VM.
@@ -750,6 +770,10 @@ if [ -z "${1}" ]; then
install_the_installer install_the_installer
boot_macos_installer boot_macos_installer
else else
if [ "${1}" != "stages" ]; then
check_dependencies check_dependencies
for argument in $@; do ${argument}; done for argument in "$@"; do ${argument}; done
else
stages
fi
fi fi