-
-
Notifications
You must be signed in to change notification settings - Fork 126
/
runMacOSVirtualbox.sh
executable file
·421 lines (386 loc) · 15.6 KB
/
runMacOSVirtualbox.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
#!/usr/bin/env bash
#
# DESCRIPTION
#
# Run macOS Big Sur and older versions in Virtualbox.
#
# CREDITS
#
# Author : Alexander Willner
# License : Whatever. Use at your own risk.
# Source : https://github.com/AlexanderWillner/runMacOSinVirtualBox
###############################################################################
# Logging #####################################################################
readonly FILE_LOG="$HOME/Library/Logs/runMacOSVirtualbox.log"
echo "Logfile: $FILE_LOG"
exec 3>&1
exec 4>&2
exec 1>>"$FILE_LOG"
exec 2>&1
###############################################################################
# Core parameters #############################################################
echo "Collecting system information..."
readonly PATH="$PATH:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/X11/bin"
readonly SCRIPTPATH="$(
cd "$(dirname "$0")" || exit
pwd -P
)"
readonly INST_VERS="$(find /Applications -maxdepth 1 -type d -name 'Install macOS *' | wc -l | tr -d '[:space:]')"
readonly INST_VER="$(find /Applications -maxdepth 1 -type d -name 'Install macOS *' -print -quit)"
readonly INST_BIN="$INST_VER/Contents/Resources/createinstallmedia"
readonly DST_DIR="${DST_DIR:-$HOME/VirtualBox VMs}"
readonly VM_NAME="${VM_NAME:-macOS-VM}"
readonly VM_DIR="${VM_DIR:-$DST_DIR/$VM_NAME}"
readonly VM_SIZE="${VM_SIZE:-131072}"
readonly VM_RES="${VM_RES:-1680x1050}"
readonly VM_SCALE="${VM_SCALE:-1.0}"
readonly VM_RAM="${VM_RAM:-4096}"
readonly VM_VRAM="${VM_VRAM:-128}"
readonly VM_CPU="${VM_CPU:-2}"
readonly DST_DMG="$DST_DIR/$VM_NAME.dmg"
readonly DST_VOL="/Volumes/$VM_NAME"
readonly DST_ISO="$DST_DIR/$VM_NAME.iso.cdr"
readonly DST_SPARSE="$DST_DIR/$VM_NAME.efi.sparseimage"
readonly DST_SPARSE2="$DST_DIR/$VM_NAME.sparseimage"
readonly FILE_EFI="/usr/standalone/i386/apfs.efi"
readonly HOST_SERIAL="$(ioreg -c IOPlatformExpertDevice -d 2 | awk -F\" '/IOPlatformSerialNumber/{print $(NF-1)}')"
readonly HOST_ID="$(ioreg -l | grep "board-id" | awk -F\" '/board-id/{print $(NF-1)}')"
readonly HOST_UUID="$(ioreg -l -p IODeviceTree | awk -F"\<|>" '/"system-id"/{print $(NF-1)}')"
readonly VM_SYSTEM_FAMILY="$(system_profiler SPHardwareDataType | awk -F': ' ' /Model Name/ { print $2 } ')"
readonly VM_SYSTEM_PRODUCT="$(system_profiler SPHardwareDataType | awk -F': ' ' /Model Identifier/ { print $2 } ')"
readonly VM_SYSTEM_UUID="$(system_profiler SPHardwareDataType | awk -F': ' ' /Hardware UUID/ { print $2 } ')"
readonly VM_SYSTEM_VER="string:1"
readonly VM_SYSTEM_REV="string:.23456"
readonly VM_SYSTEM_BIOS="$(system_profiler SPHardwareDataType | awk -F': ' ' /Boot ROM Version/ { print $2 } ')"
readonly VM_SYSTEM_SN="$(system_profiler SPHardwareDataType | awk -F': ' ' /Serial Number \(system\)/ { print $2 } ')"
###############################################################################
# Define methods ##############################################################
debug() {
echo "DEBUG: $1" >&3
log "$1"
}
error() {
echo "ERROR: $1" >&4
log "$1"
}
info() {
echo -n "$1" >&3
log "$1"
}
result() {
echo "$1" >&3
log "$1"
}
log() {
datestring="$(date +'%Y-%m-%d %H:%M:%S')"
echo "[$datestring] $1" >>"$FILE_LOG"
}
runChecks() {
info "Running checks..." 0
result "."
if [[ ! $HOME == /Users* ]]; then
error "\$HOME should point to the users home directory. See issue #63."
fi
if [ "$INST_VERS" = "0" ]; then
error "No macOS installer found at /Applications. Download the installer first (e.g. via 'installinstallmacos.py') - press enter in the terminal when done..."
read -r
exit 6
fi
if [ ! "$INST_VERS" = "1" ]; then
error "$INST_VERS macOS installers found at /Applications. Don't know which one to select."
exit 7
fi
if [ ! -d "$INST_VER/Contents/SharedSupport/" ]; then
error "Partial macOS installer found at /Applications. Download the full installer first (e.g. via 'installinstallmacos.py') - press enter in the terminal when done..."
read -r
exit 8
fi
if [ ! -x "$INST_BIN" ]; then
error "'$INST_BIN' not found."
exit 1
fi
if ! type VBoxManage >/dev/null 2>&1; then
error "'VBoxManage' not installed. Trying to install automatically, if you've brew installed..."
if type brew >/dev/null 2>&1; then
brew install virtualbox || exit 2
else
exit 2
fi
fi
awk >/dev/null 2>&1
if [ ! $? -eq 1 ]; then
error "Something is wrong with your 'awk' installation. Trying to fix it automatically, if you've brew installed..."
if type brew >/dev/null 2>&1; then
brew upgrade awk || exit 6
else
exit 6
fi
fi
# Not needed anymore
# if [ "$(VBoxManage list extpacks | grep 'USB 3.0')" = "" ]; then
# error "VirtualBox USB 3.0 Extension Pack not installed. Will not install it automatically, due to licensing issues!"
# error "Install e.g. via brew install virtualbox-extension-pack"
# exit 4
# fi
if ! diskutil listFilesystems | grep -q APFS; then
error "This host does not support required APFS filesystem. You must upgrade to High Sierra or later and try again."
exit 11
fi
}
function rmIfEmpty() {
if [ -d "$1" ] && [ -w "$1" ]; then
if [ -z "$(ls -A $1)" ] ; then
rm -r "$1"
else
error "$1 not empty"
fi
fi
}
ejectAll() {
# todo: replace this brute-force error-prone approach by a more coordinated approach
hdiutil info | grep 'Install macOS' | awk '{print $1}' | while read -r i; do
hdiutil detach -force "$i" 2>/dev/null || true
done
hdiutil info | grep 'OS X Base System' | awk '{print $1}' | while read -r i; do
hdiutil detach -force "$i" 2>/dev/null || true
done
hdiutil info | grep 'InstallESD' | awk '{print $1}' | while read -r i; do
hdiutil detach -force "$i" 2>/dev/null || true
done
hdiutil detach -force "$DST_VOL" 2>/dev/null || true
rmIfEmpty "$DST_VOL"
hdiutil detach -force /Volumes/EFI 2>/dev/null || true
rmIfEmpty "/Volumes/EFI/EFI/drivers"
rmIfEmpty "/Volumes/EFI/EFI"
rmIfEmpty "/Volumes/EFI"
find /Volumes/ -maxdepth 1 -name "NO NAME*" -exec hdiutil detach -force {} \; 2>/dev/null || true
rmIfEmpty "/Volumes/NO NAME"
}
createImage() {
version="$(/usr/libexec/PlistBuddy -c 'Print CFBundleShortVersionString' "$INST_VER/Contents/Info.plist")"
info "Creating image '$DST_DMG' (takes a while, version $version, will need sudo)..." 30
if [ ! -e "$DST_DMG" ]; then
result "."
ejectAll
mkdir -p "$DST_DIR"
hdiutil create -o "$DST_DMG" -size 16g -layout SPUD -fs HFS+J &&
hdiutil attach "$DST_DMG" -mountpoint "$DST_VOL" &&
sudo "$INST_BIN" --nointeraction --volume "$DST_VOL" --applicationpath "$INST_VER" ||
error "Could create or run installer. Please look in the log file..."
ejectAll
else
result "already exists."
fi
info "Creating iso '$DST_ISO'..." 40
if [ ! -e "$DST_ISO" ]; then
result "."
mkdir -p "$DST_DIR"
hdiutil convert "$DST_DMG" -format UDTO -o "$DST_ISO"
else
result "already exists."
fi
}
patchEFI() {
info "Adding APFS drivers to EFI in '$DST_DIR/$VM_NAME.efi.vdi'..."
result "."
ejectAll
if [ ! -f "$DST_SPARSE" ]; then
hdiutil create -size 1m -fs MS-DOS -volname "EFI" "$DST_SPARSE"
fi
EFI_DEVICE=$(hdiutil attach -nomount "$DST_SPARSE" 2>&1)
result="$?"
if [ "$result" -ne "0" ]; then
error "Couldn't mount EFI disk: $EFI_DEVICE"
exit 92
fi
EFI_DEVICE=$(echo $EFI_DEVICE|egrep -o '/dev/disk[[:digit:]]{1}' |head -n1)
# add APFS driver to EFI
if [ -d "/Volumes/EFI/" ]; then
error "The folder '/Volumes/EFI/' already exists!"
exit 94
fi
diskutil mount "${EFI_DEVICE}s1"
mkdir -p /Volumes/EFI/EFI/drivers >/dev/null 2>&1||true
cp "$FILE_EFI" /Volumes/EFI/EFI/drivers/
# create startup script to boot macOS or the macOS installer
cat <<EOT > /Volumes/EFI/startup.nsh
@echo -off
#fixme startup delay
set StartupDelay 0
load "fs0:\EFI\drivers\apfs.efi"
#fixme bcfg driver add 0 "fs0:\\EFI\\drivers\\apfs.efi" "APFS Filesystem Driver"
map -r
echo "Trying to find a bootable device..."
for %p in "macOS Install Data" "macOS Install Data\Locked Files\Boot Files" "OS X Install Data" "Mac OS X Install Data" "System\Library\CoreServices" ".IABootFiles"
for %d in fs2 fs3 fs4 fs5 fs6 fs1
if exist "%d:\%p\boot.efi" then
echo "Booting: %d:\%p\boot.efi ..."
#fixme: bcfg boot add 0 "%d:\\%p\\boot.efi" "macOS"
"%d:\%p\boot.efi"
endif
endfor
endfor
echo "Failed."
EOT
# close disk again
diskutil unmount "${EFI_DEVICE}s1"
VBoxManage convertfromraw "${EFI_DEVICE}" "$DST_DIR/$VM_NAME.efi.vdi" --format VDI
diskutil eject "${EFI_DEVICE}"
}
createVM() {
if [ ! -e "$VM_DIR" ]; then
mkdir -p "$VM_DIR"
fi
info "Creating VM HDD '$DST_DIR/$VM_NAME.vdi' (takes a while)..." 90
if [ ! -e "$DST_DIR/$VM_NAME.vdi" ]; then
result "."
ejectAll
result "Creating $DST_SPARSE2..."
if [ ! -e "$DST_SPARSE2" ]; then
hdiutil create -size "$VM_SIZE"MB -fs "APFS" -volname "macOS" -type SPARSE "$DST_SPARSE2"
if [ "$?" -ne "0" ]; then
error "Couldn't create $DST_SPARSE2"
exit 95
fi
else
result "...already exists"
fi
MACOS_DEVICE=$(hdiutil attach -nomount "$DST_SPARSE2" 2>&1)
if [ "$?" -ne "0" ]; then
error "Couldn't mount target disk: $MACOS_DEVICE"
exit
fi
MACOS_DEVICE=$(echo $MACOS_DEVICE|egrep -o '/dev/disk[[:digit:]]{1}' |head -n1)
result "Converting virtual macOS disk: $MACOS_DEVICE"
VBoxManage convertfromraw "${MACOS_DEVICE}" "$DST_DIR/$VM_NAME.vdi" --format VDI
diskutil eject "${MACOS_DEVICE}"
else
result "already exists."
fi
if [ ! -e "$DST_DIR/$VM_NAME.efi.vdi" ]; then
patchEFI
fi
info "Creating VM '$VM_NAME'..." 99
if ! VBoxManage showvminfo "$VM_NAME" >/dev/null 2>&1; then
result "."
VBoxManage createvm --register --name "$VM_NAME" --basefolder "$DST_DIR" --ostype MacOS1013_64
VBoxManage modifyvm "$VM_NAME" --usbxhci on --memory "$VM_RAM" --vram "$VM_VRAM" --cpus "$VM_CPU" \
--firmware efi --chipset ich9 --mouse usbtablet --keyboard usb --nested-hw-virt off --nestedpaging off \
--cpu-profile "Intel Core i7-6700K" --cpuidset 00000001 000106e5 00100800 0098e3fd bfebfbff
VBoxManage setextradata "$VM_NAME" "CustomVideoMode1" "${VM_RES}x32"
VBoxManage setextradata "$VM_NAME" VBoxInternal2/EfiGraphicsResolution "$VM_RES"
VBoxManage setextradata "$VM_NAME" GUI/ScaleFactor "$VM_SCALE"
VBoxManage setextradata "$VM_NAME" "VBoxInternal/Devices/efi/0/Config/DmiBoardProduct" "string:${HOST_ID}"
VBoxManage setextradata "$VM_NAME" "VBoxInternal/Devices/efi/0/Config/DmiSystemSerial" "string:${HOST_SERIAL}"
VBoxManage setextradata "$VM_NAME" "VBoxInternal/Devices/efi/0/Config/DmiSystemFamily" "${VM_SYSTEM_FAMILY}"
VBoxManage setextradata "$VM_NAME" "VBoxInternal/Devices/efi/0/Config/DmiSystemProduct" "${VM_SYSTEM_PRODUCT}"
VBoxManage setextradata "$VM_NAME" "VBoxInternal/Devices/efi/0/Config/DmiSystemUuid" "${VM_SYSTEM_UUID}"
VBoxManage setextradata "$VM_NAME" "VBoxInternal/Devices/efi/0/Config/DmiOEMVBoxVer" "${VM_SYSTEM_VER}"
VBoxManage setextradata "$VM_NAME" "VBoxInternal/Devices/efi/0/Config/DmiOEMVBoxRev" "${VM_SYSTEM_REV}"
VBoxManage setextradata "$VM_NAME" "VBoxInternal/Devices/efi/0/Config/DmiBIOSVersion" "${VM_SYSTEM_BIOS}"
VBoxManage setextradata "$VM_NAME" "VBoxInternal/Devices/efi/0/Config/DmiBoardSerial" "${VM_SYSTEM_SN}"
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
VBoxManage setextradata "$VM_NAME" "VBoxInternal/TM/TSCMode" "RealTSCOffset"
VBoxManage storagectl "$VM_NAME" --name "SATA Controller" --add sata --controller IntelAHCI --hostiocache on --portcount 4
VBoxManage storageattach "$VM_NAME" --storagectl "SATA Controller" --port 0 --device 0 --type hdd --nonrotational on --medium "$DST_DIR/$VM_NAME.efi.vdi"
VBoxManage storageattach "$VM_NAME" --storagectl "SATA Controller" --port 1 --device 0 --type hdd --nonrotational on --medium "$DST_DIR/$VM_NAME.vdi"
addInstaller
VBoxManage modifyvm "$VM_NAME" --boot1 disk
VBoxManage modifyvm "$VM_NAME" --boot2 disk
VBoxManage modifyvm "$VM_NAME" --boot3 dvd
else
result "already exists."
fi
}
runVM() {
info "Starting VM '$VM_NAME'..." 100
if ! VBoxManage showvminfo "$VM_NAME" | grep "State:" | grep -i running >/dev/null; then
result "."
VBoxManage startvm "$VM_NAME" --type gui
else
result "already running."
fi
}
runClean() {
rm -f "$FILE_LOG" "$DST_DMG" "$DST_ISO" "$DST_SPARSE" "$DST_SPARSE2" || true
}
waitVM() {
info "Waiting for VM '$VM_NAME' to shutdown..."
result "."
while VBoxManage showvminfo "$VM_NAME"|grep -E "State:.*running" >/dev/null; do
sleep 5
done
true
}
stopVM() {
info "Press enter to stop the VM and to eject the installer medium (to avoid an installation loop for macOS < 10.16)..."
result "."
read
VBoxManage controlvm "$VM_NAME" poweroff||true
sleep 5
}
removeInstaller() {
info "Ejecting installer DVD for VM '$VM_NAME'..."
result "."
# Skip installation DVD to boot from new disk
VBoxManage storageattach "$VM_NAME" --storagectl "SATA Controller" --port 2 --device 0 --type dvddrive --medium emptydrive >/dev/null 2>&1||true
}
download() {
info "Downloading macOS (need sudo)..."
result "."
exec 1>&3
sudo ./installinstallmacos.py
info "Please mount the produced .dmg file and move the contained installer to /Applications now."
}
addInstaller() {
info "Adding installer DVD for VM '$VM_NAME'..."
result "."
# Skip installation DVD to boot from new disk
VBoxManage storageattach "$VM_NAME" --storagectl "SATA Controller" --port 2 --device 0 --type dvddrive --hotpluggable on --medium "$DST_ISO"
}
cleanup() {
local err="${1:-}"
local line="${2:-}"
local linecallfunc="${3:-}"
local command="${4:-}"
local funcstack="${5:-}"
ejectAll
if [[ $err -ne "0" ]]; then
debug "line $line - command '$command' exited with status: $err."
debug "In $funcstack called at line $linecallfunc."
debug "From function ${funcstack[0]} (line $linecallfunc)."
error "Look at $FILE_LOG for details (or use Console.app). Press enter in the terminal when done..."
read -r
fi
}
main() {
while [ "$#" -ne 0 ]; do
ARG="$1"
shift # get rid of $1, we saved in ARG already
case "$ARG" in
check) runChecks ;;
clean) runClean ;;
cleanup) cleanup ;;
stash) VBoxManage unregistervm --delete "$VM_NAME"||true ;;
stashvm) VBoxManage storageattach "$VM_NAME" --storagectl "SATA Controller" --port 0 --device 0 --type dvddrive --medium emptydrive >/dev/null 2>&1||true ; VBoxManage unregistervm --delete "$VM_NAME"||true ;;
installer) createImage ;;
patch) patchEFI ;;
vm) createVM ;;
run) runVM ;;
wait) waitVM ;;
stop) stopVM ;;
eject) removeInstaller ;;
add) addInstaller ;;
download) download ;;
all) runChecks && createImage && createVM && patchEFI && runVM && stopVM && removeInstaller && runVM ;;
*) echo "Possible commands: clean, stash, all, check, installer, patch, vm, run, stop, wait, eject, add" >&4 ;;
esac
done
}
###############################################################################
# Run script ##################################################################
[[ ${BASH_SOURCE[0]} == "${0}" ]] && trap 'cleanup "${?}" "${LINENO}" "${BASH_LINENO}" "${BASH_COMMAND}" $(printf "::%s" ${FUNCNAME[@]:-})' EXIT && main "${@:-}"
###############################################################################