├── LICENSE ├── README.md └── macos-guest-virtualbox.sh /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![macOS inside a VirtualBox window with the dock positioned on the left](https://repository-images.githubusercontent.com/156108442/c501b100-0e5a-11eb-8b49-90afd63f5d03 "macos-guest-virtualbox.sh") 3 | 4 | ## Push-button installer of macOS on VirtualBox 5 | 6 | [`macos-guest-virtualbox.sh`](https://raw.githubusercontent.com/myspaghetti/macos-guest-virtualbox/master/macos-guest-virtualbox.sh) is a Bash script that creates a macOS virtual machine guest on VirtualBox with unmodified macOS installation files downloaded directly from Apple servers. 7 | 8 | A default install only requires the user to sit patiently and, less than ten times, press enter when prompted by the script, without interacting with the virtual machine. 9 | 10 | Tested on `bash` and `zsh` on [Cygwin](https://cygwin.com/install.html). Works on macOS, CentOS 7, and Windows on x86 CPUs with VT-x or AMD-V. Should work on most modern Linux distros. 11 | 12 | macOS Catalina (10.15), Mojave (10.14), and High Sierra (10.13) currently supported. 13 | 14 | ## Maintainer wanted 15 | 16 | If you would like to become the maintainer of this repository, please see [issue #645 - maintainer wanted](https://github.com/myspaghetti/macos-virtualbox/issues/645). 17 | 18 | ## Documentation 19 | 20 | Documentation can be viewed by executing the command `./macos-guest-virtualbox.sh documentation` 21 | 22 | The majority of the script is either documentation, comments, or actionable error messages, which should make the script straightforward to inspect and understand. 23 | 24 | ## iCloud and iMessage connectivity and NVRAM 25 | 26 | 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 can be set in EFI and NVRAM by editing the script. See the [documentation command](#documentation) for further information. 27 | 28 | ## Storage size 29 | 30 | The script by default assigns a target virtual disk storage size of 80GB, which is populated to about 25GB on the host on initial installation. After the installation is complete, the storage size may be increased. See the [documentation command](#documentation) for further information. 31 | 32 | ## Primary display resolution 33 | 34 | The following primary display resolutions are supported by macOS on VirtualBox: `5120x2880` `2880x1800` `2560x1600` `2560x1440` `1920x1200` `1600x1200` `1680x1050` `1440x900` `1280x800` `1024x768` `640x480`. See the [documentation command](#documentation) for further information. 35 | 36 | ## Scope and unsupported features 37 | 38 | The scope of the script is completing a default macOS install process on VirtualBox on supported hardware. Further functioning order of VirtualBox or macOS 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, including critical functionality. 39 | 40 | ### CPU compatibility 41 | 42 | The script is designed for x86 CPU Mac hardware. macOS guests on VirtualBox are generally incompatible with other CPU models. If the guest macOS boot process hangs on “LoadKernelFromStream”, “EndRandomSeed”, or "EXITBS", see the [documentation command](#documentation) regarding VirtualBox CPU profiles and [CPUID settings](https://www.virtualbox.org/manual/ch08.html#vboxmanage-modifyvm-teleport). Some CPU models released in 2020 and later may fail to start or complete the installer, and may require manually adjusting the CPUID settings. 43 | 44 | ### Upgrading to Big Sur and Monterey 45 | 46 | The virtual machine may be upgraded to the latest macOS Big Sur (11) and macOS Monterey (12) versions through Software Update. Big Sur may be installed in-place. Monterey may require attaching another volume to the virtual machine and selecting the volume as the installation target, otherwise the upgrade is prone to failing and entering a boot loop. 47 | 48 | ### Performance and deployment 49 | 50 | After successfully creating a working macOS virtual machine, consider importing it into more performant virtualization software, or packaging it for configuration management platforms for automated deployment. These virtualization and deployment applications require additional configuration that is beyond the scope of the script. 51 | 52 | QEMU with KVM is capable of providing virtual machine hardware passthrough for near-native performance. QEMU supports the `VMDK` virtual disk image storage format, which can be configured to be created by the script. See the [documentation command](#documentation) for further information. QEMU and KVM require additional configuration that is beyond the scope of the script. 53 | 54 | #### VirtualBox Native Execution Manager (NEM) 55 | 56 | The VirtualBox Native Execution Manager (NEM) is an experimental VirtualBox feature. [VirtualBox uses NEM when access to VT-x and AMD-V is blocked by virtualization software or execution protection features such as Hyper-V, WSL2, WSLg, Windows Sandbox, memory integrity protection, Application Guard, Credential Guard, Device Guard, and other features and software.](https://docs.microsoft.com/en-us/troubleshoot/windows-client/application-management/virtualization-apps-not-work-with-hyper-v) macOS and the macOS installer have memory corruption issues under NEM virtualization. The script checks for NEM and exits with an error message if it is detected. 57 | 58 | [VirtualBox can run on WSL2 and WSLg with some kernel module compilation](https://github.com/myspaghetti/macos-virtualbox/issues/525), though performance is extremely low. At the point that kernel module compilation is required, it may be preferable to use QEMU/KVM on WSL2 and WSLg, which is orders of magnitude faster than VirtualBox on WSL2 and WSLg. WSL2, WSLg, QEMU, and KVM require additional configuration that is beyond the scope of the script. 59 | 60 | ### Bootloaders 61 | 62 | The macOS VirtualBox guest is loaded without extra bootloaders, but it is compatible with [OpenCore](https://github.com/acidanthera/OpenCorePkg/releases). OpenCore requires additional configuration that is beyond the scope of the script. 63 | 64 | ### Audio 65 | 66 | macOS may not support any built-in VirtualBox audio controllers. The bootloader [OpenCore](https://github.com/acidanthera/OpenCorePkg/releases) may be able to load open-source or built-in audio drivers in VirtualBox, providing the configuration for STAC9221 (Intel HD Audio) or SigmaTel STAC9700,83,84 (ICH AC97) is available. 67 | 68 | ### Display scaling 69 | 70 | 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](https://github.com/acidanthera/WhateverGreen/blob/master/Manual/FAQ.IntelHD.en.md#edid) which enables display scaling. 71 | 72 | ### FileVault 73 | 74 | The VirtualBox EFI implementation does not properly load the FileVault full disk encryption password prompt upon boot. The bootloader [OpenCore](https://github.com/acidanthera/OpenCorePkg/releases/tag/0.6.9) is able to load the password prompt with the parameter `ProvideConsoleGop` set to `true`. See sample [config.plist](https://github.com/myspaghetti/macos-virtualbox/files/6600860/config.plist.txt) 75 | 76 | 77 | ## Dependencies 78 | 79 | The following dependencies should be available through a package manager: 80 | `bash` `coreutils` `gzip` `unzip` `wget` `xxd` `dmg2img` `virtualbox` 81 | 82 | The following optional packages provide optical character recognition that reduces the required interaction with the script: 83 | `tesseract-ocr` `tesseract-ocr-eng` 84 | 85 | Supported versions: 86 | 87 | * [VirtualBox](https://www.virtualbox.org/wiki/Downloads) ≥ 6.1.6, though versions as low as 5.2 may work. 88 | * GNU `Bash` ≥ 4.3, on Windows run through [Cygwin](https://cygwin.com/install.html) or WSL "1", see [NEM](#virtualbox-native-execution-manager-nem) 89 | * GNU `coreutils` ≥ 8.22, GNU `gzip` ≥ 1.5, Info-ZIP `unzip` ≥ v6.0, GNU `wget` ≥ 1.14, `xxd` ≥ 1.11 90 | * `dmg2img` ≥ 1.6.5, on Cygwin the package is not available through the package manager so the script downloads it automatically. 91 | * `tesseract-ocr` ≥ 4 92 | -------------------------------------------------------------------------------- /macos-guest-virtualbox.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -i 2 | # Push-button installer of macOS on VirtualBox 3 | # (c) myspaghetti, licensed under GPL2.0 or higher 4 | # url: https://github.com/myspaghetti/macos-virtualbox 5 | # version 0.99.2.4 6 | 7 | # Dependencies: bash coreutils gzip unzip wget xxd dmg2img 8 | # Optional features: tesseract-ocr tesseract-ocr-eng 9 | # Supported versions: 10 | # VirtualBox >= 6.1.6 dmg2img >= 1.6.5 11 | # GNU bash >= 4.3 GNU coreutils >= 8.22 12 | # GNU gzip >= 1.5 GNU wget >= 1.14 13 | # Info-ZIP unzip >= 6.0 xxd with -e little endian support 14 | # tesseract-ocr >= 4 15 | 16 | function set_variables() { 17 | # Customize the installation by setting these variables: 18 | vm_name="macOS" # name of the VirtualBox virtual machine 19 | macOS_release_name="Catalina" # install "HighSierra" "Mojave" or "Catalina" 20 | storage_size=80000 # VM disk image size in MB, minimum 22000 21 | storage_format="vdi" # VM disk image file format, "vdi" or "vmdk" 22 | cpu_profile="host" # VM CPU profile, see "CPU profiles" in docs 23 | cpu_count=2 # VM CPU cores, minimum 2 24 | memory_size=4096 # VM RAM in MB, minimum 2048 25 | gpu_vram=128 # VM video RAM in MB, minimum 34, maximum 128 26 | resolution="1280x800" # VM display resolution 27 | 28 | # Values for NVRAM and EFI parameters are required by iCloud, iMessage, 29 | # and other connected Apple applications, but otherwise not required. 30 | # Parameters taken from a genuine Mac may result in a "Call customer support" 31 | # message if they do not match the genuine Mac exactly. 32 | # Non-genuine yet genuine-like parameters usually work. 33 | 34 | # Assigning the following parameters is not required when installing or using macOS. 35 | 36 | DmiSystemFamily="MacBook Pro" # Model Name 37 | DmiSystemProduct="MacBookPro11,2" # Model Identifier 38 | DmiBIOSVersion="string:MBP7.89" # Boot ROM Version 39 | DmiSystemSerial="NO_DEVICE_SN" # Serial Number (system) 40 | DmiSystemUuid="CAFECAFE-CAFE-CAFE-CAFE-DECAFFDECAFF" # Hardware UUID 41 | ROM='%aa*%bbg%cc%dd' # ROM identifier 42 | MLB="NO_LOGIC_BOARD_SN" # MLB SN stored in NVRAM 43 | DmiBoardSerial="${MLB}" # MLB SN stored in EFI 44 | DmiBoardProduct="Mac-3CBD00234E554E41" # Product (board) identifier 45 | SystemUUID="aabbccddeeff00112233445566778899" # System UUID 46 | 47 | # If the script is running on macOS and "get_parameters_from_macOS_host" is 48 | # set to "yes", the script will attempt to get the host's EFI and NVRAM 49 | # parameters and override the above EFI and NVRAM parameters. 50 | 51 | get_parameters_from_macOS_host="no" 52 | 53 | if [[ "$(sw_vers 2>/dev/null)" && "${get_parameters_from_macOS_host}" =~ [Yy] ]]; then 54 | # These values are taken from a genuine Mac... 55 | hardware_overview="$(system_profiler SPHardwareDataType)" 56 | model_name="${hardware_overview##*Model Name: }"; model_name="${model_name%%$'\n'*}" 57 | model_identifier="${hardware_overview##*Model Identifier: }"; model_identifier="${model_identifier%%$'\n'*}" 58 | boot_rom_ver="${hardware_overview##*Boot ROM Version: }"; boot_rom_ver="${boot_rom_ver%%$'\n'*}" 59 | sn_system="${hardware_overview##*Serial Number (system): }"; sn_system="${sn_system%%$'\n'*}" 60 | hardware_uuid="${hardware_overview##*Hardware UUID: }"; hardware_uuid="${hardware_uuid%%$'\n'*}" 61 | nvram_rom="$(nvram 4D1EDE05-38C7-4A6A-9CC6-4BCCA8B38C14:ROM)"; nvram_rom="${nvram_rom##*$'\t'}" 62 | nvram_mlb="$(nvram 4D1EDE05-38C7-4A6A-9CC6-4BCCA8B38C14:MLB)"; nvram_mlb="${nvram_mlb##*$'\t'}" 63 | ioreg_board_id="$(ioreg -p "IODeviceTree" -r -n / -d 1)"; ioreg_board_id="${ioreg_board_id##*board-id\" = <\"}"; ioreg_board_id="${ioreg_board_id%%\">*}" 64 | ioreg_system_id="$(ioreg -p "IODeviceTree" -n platform -r)"; ioreg_system_id="${ioreg_system_id##*system-id\" = <}"; ioreg_system_id="${ioreg_system_id%%>*}" 65 | 66 | # ...and set in VirtualBox EFI and NVRAM... 67 | DmiSystemFamily="${model_name}" # Model Name 68 | DmiSystemProduct="${model_identifier}" # Model Identifier 69 | DmiBIOSVersion="string:${boot_rom_ver}" # Boot ROM Version 70 | DmiSystemSerial="${sn_system}" # Serial Number (system) 71 | DmiSystemUuid="${hardware_uuid}" # Hardware UUID 72 | ROM="${nvram_rom}" # ROM identifier, stored in NVRAM 73 | MLB="${nvram_mlb}" # MLB SN, stored in NVRAM 74 | DmiBoardSerial="${nvram_mlb}" # MLB SN, stored in EFI 75 | DmiBoardProduct="${ioreg_board_id}" # Product (board) identifier 76 | SystemUUID="${ioreg_system_id}" # System UUID, stored in NVRAM 77 | fi 78 | 79 | system_integrity_protection='10' # '10' - enabled, '77' - disabled 80 | 81 | # Additional configurations may be saved in external files and loaded with the 82 | # following command prior to executing the script: 83 | # export macos_vm_vars_file=/path/to/variable_assignment_file 84 | # "variable_assignment_file" is a plain text file that contains zero or more 85 | # lines with a variable assignment for any variable specified above. 86 | [[ -r "${macos_vm_vars_file}" ]] && source "${macos_vm_vars_file}" 87 | } 88 | 89 | # welcome message 90 | function welcome() { 91 | echo -ne "\n${highlight_color}Push-button installer of macOS on VirtualBox${default_color} 92 | 93 | This script installs only open-source software and unmodified Apple binaries, 94 | and requires about ${highlight_color}50GB${default_color} of available storage, of which 25GB are for temporary 95 | installation files that may be deleted when the script is finished. 96 | 97 | The script interacts with the virtual machine twice, ${highlight_color}please do not interact${default_color} 98 | ${highlight_color}with the virtual machine manually${default_color} before the script is finished. 99 | 100 | Documentation about optional configuration, ${highlight_color}iCloud and iMessage connectivity${default_color}, 101 | resuming the script by stages, and other topics can be viewed with the 102 | following command: 103 | 104 | " 105 | would_you_like_to_know_less 106 | echo -ne "\n${highlight_color}Press enter to review the script configuration${default_color}" 107 | clear_input_buffer_then_read 108 | 109 | function pad_to_33_chars() { 110 | local padded="${1} " 111 | echo "${padded:0:33}${2}" 112 | } 113 | 114 | # custom settings prompt 115 | echo -e "\nvm_name=\"${vm_name}\"" 116 | pad_to_33_chars "macOS_release_name=\"${macOS_release_name}\"" "# install \"HighSierra\" \"Mojave\" \"Catalina\"" 117 | pad_to_33_chars "storage_size=${storage_size}" "# VM disk image size in MB, minimum 22000" 118 | pad_to_33_chars "storage_format=\"${storage_format}\"" "# VM disk image file format, \"vdi\" or \"vmdk\"" 119 | pad_to_33_chars "cpu_profile=\"${cpu_profile}\"" "# VM CPU profile, see \"CPU profiles\" in docs" 120 | pad_to_33_chars "cpu_count=${cpu_count}" "# VM CPU cores, minimum 2" 121 | pad_to_33_chars "memory_size=${memory_size}" "# VM RAM in MB, minimum 2048" 122 | pad_to_33_chars "gpu_vram=${gpu_vram}" "# VM video RAM in MB, minimum 34, maximum 128" 123 | pad_to_33_chars "resolution=\"${resolution}\"" "# VM display resolution" 124 | echo -ne "\nThese values may be customized as described in the documentation.\n 125 | ${highlight_color}Press enter to continue, CTRL-C to exit${default_color}" 126 | clear_input_buffer_then_read 127 | } 128 | 129 | # check dependencies 130 | 131 | function check_shell() { 132 | if [[ -n "${BASH_VERSION}" && -n "${ZSH_VERSION}" ]]; then 133 | echo "The script cannot determine if it is executed on bash or zsh." 134 | echo "Please explicitly execute the script on the same shell as the interactive shell," 135 | echo -e "for example, for zsh:\n" 136 | echo " ${highlight_color}zsh -i macos-guest-virtualbox.sh${default_color}" 137 | exit 138 | elif [[ -n "${BASH_VERSION}" ]]; then 139 | if [[ ! ( "${BASH_VERSION:0:1}" -ge 5 140 | || "${BASH_VERSION:0:3}" =~ 4\.[3-9] 141 | || "${BASH_VERSION:0:4}" =~ 4\.[12][0-9] ) ]]; then 142 | echo "Please execute this script with Bash 4.3 or higher, or zsh 5.5 or higher." 143 | if [[ -n "$(sw_vers 2>/dev/null)" ]]; then 144 | echo "macOS detected. Make sure the script is not executed with the default /bin/bash" 145 | echo "which is version 3. Explicitly type the executable path, for example for zsh:" 146 | echo " ${highlight_color}/path/to/5.5/zsh macos-guest-virtualbox.sh${default_color}" 147 | fi 148 | exit 149 | fi 150 | elif [[ -n "${ZSH_VERSION}" ]]; then 151 | if [[ ( "${ZSH_VERSION:0:1}" -ge 6 152 | || "${ZSH_VERSION:0:3}" =~ 5\.[5-9] 153 | || "${ZSH_VERSION:0:4}" =~ 5\.[1-4][0-9] ) ]]; then 154 | # make zsh parse the script (almost) like bash 155 | setopt extendedglob sh_word_split ksh_arrays posix_argzero nullglob bsd_echo 156 | else 157 | echo "Please execute this script with zsh version 5.5 or higher." 158 | exit 159 | fi 160 | else 161 | echo "The script appears to be executed on a shell other than bash or zsh. Exiting." 162 | exit 163 | fi 164 | 165 | if [[ ! $- =~ i ]]; then # terminal is not interactive 166 | echo "The shell appears to be executed non-interactively. If this is not the case," 167 | echo "please press CTRL-C and run the script under an interactive shell, for example" 168 | echo -e "${highlight_color}bash -i macos-guest-virtualbox.sh${default_color} or ${highlight_color}zsh -i macos-guest-virtualbox.sh${default_color}\n" 169 | echo "Otherwise, the script will continue running non-interactively." 170 | animated_please_wait 5 171 | echo "" 172 | tesseract_ocr="$(tesseract --version 2>/dev/null)" 173 | tesseract_lang="$(tesseract --list-langs 2>/dev/null)" 174 | regex_ver='[Tt]esseract [45]' # for zsh quoted regex compatibility 175 | if [[ ! ( "${tesseract_ocr}" =~ ${regex_ver} ) || -z "${tesseract_lang}" ]]; then 176 | echo "Running the script on a non-interactive shell requires the following packages:" 177 | echo -e " tesseract-ocr >= 4 tesseract-ocr-eng\n" 178 | echo "Exiting." 179 | animated_please_wait 5 180 | echo "" 181 | exit 182 | fi 183 | fi 184 | 185 | } 186 | 187 | function check_gnu_coreutils_prefix() { 188 | if [[ -n "$(gcsplit --help 2>/dev/null)" ]]; then 189 | function base64() { 190 | gbase64 "$@" 191 | } 192 | function csplit() { 193 | gcsplit "$@" 194 | } 195 | function expr() { 196 | gexpr "$@" 197 | } 198 | function ls() { 199 | gls "$@" 200 | } 201 | function split() { 202 | gsplit "$@" 203 | } 204 | function tac() { 205 | gtac "$@" 206 | } 207 | function seq() { 208 | gseq "$@" 209 | } 210 | function sort() { 211 | gsort "$@" 212 | } 213 | fi 214 | } 215 | 216 | function check_dependencies() { 217 | 218 | # check environment for macOS and non-GNU coreutils 219 | if [[ -n "$(sw_vers 2>/dev/null)" ]]; then 220 | # Add Homebrew GNU coreutils to PATH if path exists 221 | homebrew_gnubin="/usr/local/opt/coreutils/libexec/gnubin" 222 | if [[ -d "${homebrew_gnubin}" ]]; then 223 | PATH="${homebrew_gnubin}:${PATH}" 224 | fi 225 | # if csplit isn't GNU variant, exit 226 | if [[ -z "$(csplit --help 2>/dev/null)" ]]; then 227 | 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}" 228 | echo "Please make sure the following packages are installed and that" 229 | echo "their path is in the PATH variable:" 230 | echo -e " ${highlight_color}bash coreutils dmg2img gzip unzip wget xxd${default_color}" 231 | echo "Please make sure Bash and coreutils are the GNU variant." 232 | exit 233 | fi 234 | fi 235 | 236 | # check for gzip, unzip, coreutils, wget 237 | if [[ -z "$(gzip --help 2>/dev/null)" || 238 | -z "$(unzip -hh 2>/dev/null)" || 239 | -z "$(csplit --help 2>/dev/null)" || 240 | -z "$(wget --version 2>/dev/null)" ]]; then 241 | echo "Please make sure the following packages are installed" 242 | echo -e "and that they are of the version specified or newer:\n" 243 | echo " coreutils 8.22 wget 1.14 gzip 1.5 unzip 6.0" 244 | echo -e "\nPlease make sure the coreutils and gzip packages are the GNU variant." 245 | exit 246 | fi 247 | 248 | # check that xxd supports endianness -e flag 249 | if [[ -z "$(xxd -e -p -l 16 /dev/urandom 2>/dev/null)" ]]; then 250 | echo "Please make sure a version of xxd which supports the -e option is installed." 251 | echo -e "The -e option should be listed when executing ${low_contrast_color}xxd --help${default_color}" 252 | echo "The package vim-common-8 provides a compatible version on most modern distros." 253 | exit 254 | fi 255 | 256 | # wget supports --show-progress from version 1.16 257 | regex='1\.1[6-9]|1\.[2-9][0-9]' # for zsh quoted regex compatibility 258 | if [[ "$(wget --version 2>/dev/null | head -n 1)" =~ ${regex} ]]; then 259 | wgetargs="--quiet --continue --show-progress --timeout=60 --secure-protocol=PFS" # pretty 260 | else 261 | wgetargs="--continue" # ugly 262 | fi 263 | 264 | # VirtualBox in ${PATH} 265 | # Cygwin 266 | if [[ -n "$(cygcheck -V 2>/dev/null)" ]]; then 267 | if [[ -n "$(cmd.exe /d/s/c call VBoxManage.exe -v 2>/dev/null)" ]]; then 268 | function VBoxManage() { 269 | cmd.exe /d/s/c call VBoxManage.exe "$@" 270 | } 271 | else 272 | cmd_path_VBoxManage='C:\Program Files\Oracle\VirtualBox\VBoxManage.exe' 273 | echo "Can't find VBoxManage in PATH variable," 274 | echo "checking ${cmd_path_VBoxManage}" 275 | if [[ -n "$(cmd.exe /d/s/c call "${cmd_path_VBoxManage}" -v 2>/dev/null)" ]]; then 276 | function VBoxManage() { 277 | cmd.exe /d/s/c call "${cmd_path_VBoxManage}" "$@" 278 | } 279 | echo "Found VBoxManage" 280 | else 281 | echo "Please make sure VirtualBox version 5.2 or higher is installed, and that" 282 | echo "the path to the VBoxManage.exe executable is in the PATH variable, or assign" 283 | echo "in the script the full path including the name of the executable to" 284 | echo -e "the variable ${highlight_color}cmd_path_VBoxManage${default_color}" 285 | exit 286 | fi 287 | fi 288 | # Windows Subsystem for Linux 2 (WSL2 or WSLg) 289 | elif [[ "$(cat /proc/sys/kernel/osrelease 2>/dev/null)" =~ WSL[2Gg] ]]; then # WSL2 or WSLg 290 | if [[ -n "$(VBoxManage -v 2>/dev/null)" ]]; then 291 | if [[ ! "$(lsmod)" =~ vboxdrv ]]; then # if the vboxdrv kernel module is not present 292 | echo "The script appears to be executed on WSL2 or WSLg." 293 | echo "Mind that WSL2 and WSLg require kernel module compilation and" 294 | echo "custom configuration that is not supported by the script." 295 | echo "If the script does not detect the ${highlight_color}vboxdrv${default_color} kernel module, it exits." 296 | exit 297 | fi 298 | else 299 | echo "Please make sure VirtualBox version 5.2 or higher and its kernel module" 300 | echo "are installed, and that the path to the VBoxManage executable" 301 | echo "is in the PATH variable." 302 | exit 303 | fi 304 | # Windows Subsystem for Linux (WSL "1") 305 | elif [[ "$(cat /proc/sys/kernel/osrelease 2>/dev/null)" =~ [Mm]icrosoft ]]; then 306 | if [[ -n "$(VBoxManage.exe -v 2>/dev/null)" ]]; then 307 | function VBoxManage() { 308 | VBoxManage.exe "$@" 309 | } 310 | else 311 | wsl_path_VBoxManage='/mnt/c/Program Files/Oracle/VirtualBox/VBoxManage.exe' 312 | echo "Can't find VBoxManage in PATH variable," 313 | echo "checking ${wsl_path_VBoxManage}" 314 | if [[ -n "$("${wsl_path_VBoxManage}" -v 2>/dev/null)" ]]; then 315 | PATH="${PATH}:${wsl_path_VBoxManage%/*}" 316 | function VBoxManage() { 317 | VBoxManage.exe "$@" 318 | } 319 | echo "Found VBoxManage" 320 | else 321 | echo "Please make sure VirtualBox is installed on Windows, and that the path to the" 322 | echo "VBoxManage.exe executable is in the PATH variable, or assigned in the script" 323 | echo -e "to the variable \"${highlight_color}wsl_path_VBoxManage${default_color}\" including the name of the executable." 324 | exit 325 | fi 326 | fi 327 | # everything else (not cygwin and not wsl) 328 | elif [[ -z "$(VBoxManage -v 2>/dev/null)" ]]; then 329 | echo "Please make sure VirtualBox version 5.2 or higher and its kernel module" 330 | echo "are installed, and that the path to the VBoxManage executable" 331 | echo "is in the PATH variable." 332 | exit 333 | fi 334 | 335 | # VirtualBox version 336 | vbox_version="$(VBoxManage -v 2>/dev/null)" 337 | vbox_version="${vbox_version//[$'\r\n']/}" 338 | if [[ -z "${vbox_version}" || -z "${vbox_version:2:1}" ]]; then 339 | echo "Can't determine VirtualBox version. Exiting." 340 | exit 341 | elif [[ ! ( "${vbox_version:0:1}" -gt 5 342 | || "${vbox_version:0:3}" =~ 5\.2 ) ]]; then 343 | echo -e "\nPlease make sure VirtualBox version 5.2 or higher is installed." 344 | echo "Exiting." 345 | exit 346 | elif [[ "${vbox_version:0:1}" = 5 ]]; then 347 | echo -e "\n${highlight_color}VirtualBox version ${vbox_version} detected.${default_color} Please see the following" 348 | echo -ne "URL for issues with the VISO filesystem on VirtualBox 5.2 to 5.2.40:\n\n" 349 | echo " https://github.com/myspaghetti/macos-virtualbox/issues/86" 350 | echo -ne "\n${highlight_color}Press enter to continue, CTRL-C to exit${default_color}" 351 | clear_input_buffer_then_read 352 | fi 353 | 354 | # Oracle VM VirtualBox Extension Pack 355 | extpacks="$(VBoxManage list extpacks 2>/dev/null)" 356 | if [[ "$(expr match "${extpacks}" '.*Oracle VM VirtualBox Extension Pack')" -le "0" || 357 | "$(expr match "${extpacks}" '.*Usable:[[:blank:]]*false')" -gt "0" ]]; 358 | then 359 | echo -e "\nThe command \"VBoxManage list extpacks\" either does not list the Oracle VM" 360 | echo -e "VirtualBox Extension Pack, or lists one or more extensions as unusable." 361 | echo -e "The virtual machine will be configured without USB xHCI controllers." 362 | extension_pack_usb3_support="--usbxhci off" 363 | else 364 | extension_pack_usb3_support="--usbxhci on" 365 | fi 366 | 367 | # dmg2img 368 | if ! dmg2img >/dev/null 2>&1; then 369 | if [[ -z "$(cygcheck -V 2>/dev/null)" ]]; then 370 | echo -e "\nPlease install the package dmg2img." 371 | exit 372 | fi 373 | if [[ -z "$("${PWD%%/}/dmg2img.exe" -d 2>/dev/null)" ]]; then 374 | if [[ -z "${PWD}" ]]; then echo "PWD environment variable is not set. Exiting."; exit; fi 375 | echo "Locally installing dmg2img" 376 | wget "https://web.archive.org/web/20190322013244/http://vu1tur.eu.org/tools/dmg2img-1.6.6-win32.zip" \ 377 | ${wgetargs} \ 378 | --output-document="dmg2img-1.6.6-win32.zip" 379 | if [[ ! -s dmg2img-1.6.6-win32.zip ]]; then 380 | echo "Error downloading dmg2img. Please provide the package manually." 381 | exit 382 | fi 383 | unzip -oj "dmg2img-1.6.6-win32.zip" "dmg2img.exe" 384 | command rm "dmg2img-1.6.6-win32.zip" 385 | chmod +x "dmg2img.exe" 386 | [[ ! -x "dmg2img.exe" ]] && echo "Error setting the executable permission for dmg2img.exe. Exiting." && exit 387 | fi 388 | function dmg2img() { 389 | "${PWD%%/}/dmg2img.exe" "$@" 390 | } 391 | fi 392 | 393 | # set Apple software update catalog URL according to macOS version 394 | 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' 395 | 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' 396 | 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' 397 | if [[ "${macOS_release_name:0:1}" =~ [Cc] ]]; then 398 | if [[ ! ( "${vbox_version:0:1}" -gt 6 || 399 | "${vbox_version}" =~ ^6\.1\.[4-9] || 400 | "${vbox_version}" =~ ^6\.1\.[123][0-9] || 401 | "${vbox_version}" =~ ^6\.[2-9] ) ]]; then 402 | echo -e "\nmacOS Catalina requires VirtualBox version 6.1.4 or higher." 403 | echo "Exiting." 404 | exit 405 | fi 406 | fi 407 | if [[ "${macOS_release_name:0:1}" =~ [Cc] ]]; then 408 | macOS_release_name="Catalina" 409 | CFBundleShortVersionString="10.15" 410 | sucatalog="${Catalina_sucatalog}" 411 | elif [[ "${macOS_release_name:0:1}" =~ [Hh] ]]; then 412 | macOS_release_name="HighSierra" 413 | CFBundleShortVersionString="10.13" 414 | sucatalog="${HighSierra_sucatalog}" 415 | elif [[ "${macOS_release_name:0:1}" =~ [Mm] ]]; then 416 | macOS_release_name="Mojave" 417 | CFBundleShortVersionString="10.14" 418 | sucatalog="${Mojave_sucatalog}" 419 | else 420 | echo "Can't parse macOS_release_name. Exiting." 421 | exit 422 | fi 423 | print_dimly "${macOS_release_name} selected to be downloaded and installed" 424 | } 425 | # Done with dependencies 426 | 427 | function prompt_delete_existing_vm() { 428 | print_dimly "stage: prompt_delete_existing_vm" 429 | if [[ -n "$(VBoxManage showvminfo "${vm_name}" 2>/dev/null)" ]]; then 430 | echo -e "\nA virtual machine named \"${vm_name}\" already exists." 431 | echo -ne "${warning_color}Delete existing virtual machine \"${vm_name}\"?${default_color}" 432 | prompt_delete_y_n 433 | if [[ "${delete}" == "y" ]]; then 434 | echo "Deleting ${vm_name} virtual machine." 435 | VBoxManage unregistervm "${vm_name}" --delete 436 | else 437 | echo -e "\n${highlight_color}Please assign a different VM name to variable \"vm_name\" by editing the script,${default_color}" 438 | echo "or skip this check manually as described when executing the following command:" 439 | would_you_like_to_know_less 440 | exit 441 | fi 442 | fi 443 | } 444 | 445 | # Attempt to create new virtual machine named "${vm_name}" 446 | function create_vm() { 447 | print_dimly "stage: create_vm" 448 | if [[ -n "$( VBoxManage createvm --name "${vm_name}" --ostype "MacOS1013_64" --register 2>&1 >/dev/null )" ]]; then 449 | echo -e "\nError: Could not create virtual machine \"${vm_name}\"." 450 | echo -e "${highlight_color}Please delete exising \"${vm_name}\" VirtualBox configuration files ${warning_color}manually${default_color}.\n" 451 | echo -e "Error message:\n" 452 | VBoxManage createvm --name "${vm_name}" --ostype "MacOS1013_64" --register 2>/dev/tty 453 | exit 454 | fi 455 | } 456 | 457 | function check_default_virtual_machine() { 458 | print_dimly "stage: check_default_virtual_machine" 459 | echo -e "\nChecking that VirtualBox starts the virtual machine without errors." 460 | if [[ -n $(VBoxManage startvm "${vm_name}" 2>&1 1>/dev/null) ]]; then 461 | echo -e "Error while starting the virtual machine.\nExiting." 462 | exit 463 | fi 464 | VBoxManage controlvm "${vm_name}" poweroff 2>/dev/null 465 | echo -e "\nChecking that VirtualBox uses hardware-supported virtualization." 466 | vbox_log="$(VBoxManage showvminfo "${vm_name}" --log 0)" 467 | regex='Attempting fall back to NEM' # for zsh quoted regex compatibility 468 | if [[ "${vbox_log}" =~ ${regex} ]]; then 469 | echo -e "\nVirtualbox is not using hardware-supported virtualization features." 470 | if [[ -n "$(cygcheck -V 2>/dev/null)" || 471 | "$(cat /proc/sys/kernel/osrelease 2>/dev/null)" =~ [Mm]icrosoft ]]; then 472 | echo "Check that software such as Hyper-V, Windows Sandbox, WSL2, memory integrity" 473 | echo "protection, and other Windows features that lock virtualization are turned off." 474 | fi 475 | echo "Exiting." 476 | exit 477 | fi 478 | } 479 | 480 | function prepare_macos_installation_files() { 481 | print_dimly "stage: prepare_macos_installation_files" 482 | # Find the correct download URL in the Apple catalog 483 | echo -e "\nDownloading Apple macOS ${macOS_release_name} software update catalog" 484 | wget "${sucatalog}" \ 485 | ${wgetargs} \ 486 | --output-document="${macOS_release_name}_sucatalog" 487 | 488 | # if file was not downloaded correctly 489 | if [[ ! -s "${macOS_release_name}_sucatalog" ]]; then 490 | wget --debug --timeout=60 -O /dev/null -o "${macOS_release_name}_wget.log" "${sucatalog}" 491 | echo -e "\nCouldn't download the Apple software update catalog." 492 | if [[ "$(expr match "$(cat "${macOS_release_name}_wget.log")" '.*ERROR[[:print:]]*is not trusted')" -gt "0" ]]; then 493 | echo -e "\nMake sure certificates from a certificate authority are installed." 494 | echo "Certificates are often installed through the package manager with" 495 | echo "a package named ${highlight_color}ca-certificates${default_color}" 496 | fi 497 | echo "Exiting." 498 | exit 499 | fi 500 | 501 | echo "Trying to find macOS ${macOS_release_name} InstallAssistant download URL" 502 | tac "${macOS_release_name}_sucatalog" | csplit - '/InstallAssistantAuto.smd/+1' '{*}' -f "${macOS_release_name}_sucatalog_" -s 503 | for catalog in "${macOS_release_name}_sucatalog_"* "error"; do 504 | if [[ "${catalog}" == error ]]; then 505 | command rm "${macOS_release_name}_sucatalog"* 506 | echo "Couldn't find the requested download URL in the Apple catalog. Exiting." 507 | exit 508 | fi 509 | urlbase="$(tail -n 1 "${catalog}" 2>/dev/null)" 510 | urlbase="$(expr match "${urlbase}" '.*\(http://[^<]*/\)')" 511 | wget "${urlbase}InstallAssistantAuto.smd" \ 512 | ${wgetargs} \ 513 | --output-document="${catalog}_InstallAssistantAuto.smd" 514 | if [[ "$(cat "${catalog}_InstallAssistantAuto.smd" )" =~ Beta ]]; then 515 | continue 516 | fi 517 | found_version="$(head -n 6 "${catalog}_InstallAssistantAuto.smd" | tail -n 1)" 518 | if [[ "${found_version}" == *${CFBundleShortVersionString}* ]]; then 519 | echo -e "Found download URL: ${urlbase}\n" 520 | command rm "${macOS_release_name}_sucatalog"* 521 | break 522 | fi 523 | done 524 | echo "Downloading macOS installation files from swcdn.apple.com" 525 | for filename in "BaseSystem.chunklist" \ 526 | "InstallInfo.plist" \ 527 | "AppleDiagnostics.dmg" \ 528 | "AppleDiagnostics.chunklist" \ 529 | "BaseSystem.dmg" \ 530 | "InstallESDDmg.pkg"; \ 531 | do wget "${urlbase}${filename}" \ 532 | ${wgetargs} \ 533 | --output-document "${macOS_release_name}_${filename}" 534 | done 535 | 536 | echo -e "\nSplitting the several-GB InstallESDDmg.pkg into 1GB parts because" 537 | echo "VirtualBox hasn't implemented UDF/HFS VISO support yet and macOS" 538 | echo "doesn't support ISO 9660 Level 3 with files larger than 2GB." 539 | split --verbose -a 2 -d -b 1000000000 "${macOS_release_name}_InstallESDDmg.pkg" "${macOS_release_name}_InstallESD.part" 540 | 541 | if [[ ! -s "ApfsDriverLoader.efi" ]]; then 542 | echo -e "\nDownloading open-source APFS EFI drivers used for VirtualBox 6.0 and 5.2" 543 | [[ "${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." 544 | wget 'https://github.com/acidanthera/AppleSupportPkg/releases/download/2.0.4/AppleSupport-v2.0.4-RELEASE.zip' \ 545 | ${wgetargs} \ 546 | --output-document 'AppleSupport-v2.0.4-RELEASE.zip' 547 | unzip -oj 'AppleSupport-v2.0.4-RELEASE.zip' 548 | fi 549 | } 550 | 551 | function create_nvram_files() { 552 | print_dimly "stage: create_nvram_files" 553 | 554 | # Each NVRAM file may contain multiple entries. 555 | # Each entry contains a namesize, datasize, name, guid, attributes, and data. 556 | # Each entry is immediately followed by a crc32 of the entry. 557 | # The script creates each file with only one entry for easier editing. 558 | # 559 | # The hex strings are stripped by xxd, so they can 560 | # look like "0xAB 0xCD" or "hAB hCD" or "AB CD" or "ABCD" or a mix of formats 561 | # and have extraneous characters like spaces or minus signs. 562 | 563 | # Load the binary files into VirtualBox VM NVRAM with the builtin command dmpstore 564 | # in the VM EFI Internal Shell, for example: 565 | # dmpstore -all -l fs0:\system-id.bin 566 | # 567 | # DmpStore code is available at this URL: 568 | # https://github.com/mdaniel/virtualbox-org-svn-vbox-trunk/blob/master/src/VBox/Devices/EFI/Firmware/ShellPkg/Library/UefiShellDebug1CommandsLib/DmpStore.c 569 | 570 | function generate_nvram_bin_file() { 571 | # input: name data guid (three positional arguments, all required) 572 | # output: function outputs nothing to stdout 573 | # but writes a binary file to working directory 574 | local namestring="${1}" # string of chars 575 | local filename="${namestring}" 576 | # represent string as string-of-hex-bytes, add null byte after every byte, 577 | # terminate string with two null bytes 578 | local name="$( for (( i = 0 ; i < ${#namestring} ; i++ )); do printf -- "${namestring:${i}:1}" | xxd -p | tr -d '\n'; printf '00'; done; printf '0000' )" 579 | # size of string in bytes, represented by eight hex digits, big-endian 580 | local namesize="$(printf "%08x" $(( ${#name} / 2 )) )" 581 | # flip four big-endian bytes byte-order to little-endian 582 | local namesize="$(printf "${namesize}" | xxd -r -p | xxd -e -g 4 | xxd -r | xxd -p)" 583 | # strip string-of-hex-bytes representation of data of spaces, "x", "h", etc 584 | local data="$(printf -- "${2}" | xxd -r -p | xxd -p)" 585 | # size of data in bytes, represented by eight hex digits, big-endian 586 | local datasize="$(printf "%08x" $(( ${#data} / 2 )) )" 587 | # flip four big-endian bytes byte-order to little-endian 588 | local datasize="$(printf "${datasize}" | xxd -r -p | xxd -e -g 4 | xxd -r | xxd -p)" 589 | # guid string-of-hex-bytes is five fields, 8+4+4+4+12 nibbles long 590 | # first three are little-endian, last two big-endian 591 | # for example, 0F1A2B3C-4D5E-6A7B-8C9D-A1B2C3D4E5F6 592 | # is stored as 3C2B1A0F-5E4D-7B6A-8C9D-A1B2C3D4E5F6 593 | local g="$( printf -- "${3}" | xxd -r -p | xxd -p )" # strip spaces etc 594 | 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}" 595 | # attributes in four bytes little-endian 596 | local attributes="07 00 00 00" 597 | # the data structure 598 | local entry="${namesize} ${datasize} ${name} ${guid} ${attributes} ${data}" 599 | # calculate crc32 using gzip, flip crc32 bytes into big-endian 600 | local crc32="$(printf "${entry}" | xxd -r -p | gzip -c | tail -c8 | xxd -p -l 4)" 601 | # save binary data 602 | printf -- "${entry} ${crc32}" | xxd -r -p - "${vm_name}_${filename}.bin" 603 | } 604 | 605 | # MLB 606 | MLB_b16="$(printf -- "${MLB}" | xxd -p)" 607 | generate_nvram_bin_file "MLB" "${MLB_b16}" "4D1EDE05-38C7-4A6A-9CC6-4BCCA8B38C14" 608 | 609 | # ROM 610 | # Convert the mixed-ASCII-and-base16 ROM value 611 | # into an ASCII string that represents a base16 number. 612 | ROM_b16="$(for (( i=0; i<${#ROM}; )); do 613 | if [[ "${ROM:${i}:1}" == "%" ]]; then 614 | let j=i+1 615 | echo -n "${ROM:${j}:2}" 616 | let i=i+3 617 | else 618 | x="$(echo -n "${ROM:${i}:1}" | xxd -p | tr -d ' ')" 619 | echo -n "${x}" 620 | let i=i+1 621 | fi 622 | done)" 623 | generate_nvram_bin_file "ROM" "${ROM_b16}" "4D1EDE05-38C7-4A6A-9CC6-4BCCA8B38C14" 624 | 625 | # system-id 626 | generate_nvram_bin_file "system-id" "${SystemUUID}" "7C436110-AB2A-4BBB-A880-FE41995C9F82" 627 | 628 | # SIP / csr-active-config 629 | generate_nvram_bin_file "csr-active-config" "${system_integrity_protection}" "7C436110-AB2A-4BBB-A880-FE41995C9F82" 630 | } 631 | 632 | function create_macos_installation_files_viso() { 633 | print_dimly "stage: create_macos_installation_files_viso" 634 | echo "Creating EFI startup script" 635 | echo 'echo -off' > "${vm_name}_startup.nsh" 636 | if [[ ( "${vbox_version:0:1}" -lt 6 ) || ( "${vbox_version:0:1}" = 6 && "${vbox_version:2:1}" = 0 ) ]]; then 637 | echo 'load fs0:\EFI\OC\Drivers\AppleImageLoader.efi 638 | load fs0:\EFI\OC\Drivers\AppleUiSupport.efi 639 | load fs0:\EFI\OC\Drivers\ApfsDriverLoader.efi 640 | map -r' >> "${vm_name}_startup.nsh" 641 | fi 642 | # EFI Internal Shell 2.1 (VBox 6.0) doesn't support for-loops that start with 0 643 | echo 'if exist "fs0:\EFI\NVRAM\MLB.bin" then 644 | dmpstore -all -l fs0:\EFI\NVRAM\MLB.bin 645 | dmpstore -all -l fs0:\EFI\NVRAM\ROM.bin 646 | dmpstore -all -l fs0:\EFI\NVRAM\csr-active-config.bin 647 | dmpstore -all -l fs0:\EFI\NVRAM\system-id.bin 648 | endif 649 | for %a run (1 5) 650 | if exist "fs%a:\EFI\NVRAM\MLB.bin" then 651 | dmpstore -all -l fs%a:\EFI\NVRAM\MLB.bin 652 | dmpstore -all -l fs%a:\EFI\NVRAM\ROM.bin 653 | dmpstore -all -l fs%a:\EFI\NVRAM\csr-active-config.bin 654 | dmpstore -all -l fs%a:\EFI\NVRAM\system-id.bin 655 | endif 656 | endfor 657 | for %a run (1 5) 658 | if exist "fs%a:\macOS Install Data\Locked Files\Boot Files\boot.efi" then 659 | "fs%a:\macOS Install Data\Locked Files\Boot Files\boot.efi" 660 | endif 661 | endfor 662 | for %a run (1 5) 663 | if exist "fs%a:\System\Library\CoreServices\boot.efi" then 664 | "fs%a:\System\Library\CoreServices\boot.efi" 665 | endif 666 | endfor' >> "${vm_name}_startup.nsh" 667 | 668 | echo -e "\nCreating VirtualBox 6 virtual ISO containing the" 669 | echo -e "installation files from swcdn.apple.com\n" 670 | create_viso_header "${macOS_release_name}_installation_files.viso" "${macOS_release_name:0:5}-files" 671 | 672 | # add files to viso 673 | 674 | # Apple macOS installation files 675 | for filename in "BaseSystem.chunklist" \ 676 | "InstallInfo.plist" \ 677 | "AppleDiagnostics.dmg" \ 678 | "AppleDiagnostics.chunklist" \ 679 | "BaseSystem.dmg" ; do 680 | if [[ -s "${macOS_release_name}_${filename}" ]]; then 681 | echo "/${filename}=\"${macOS_release_name}_${filename}\"" >> "${macOS_release_name}_installation_files.viso" 682 | fi 683 | done 684 | 685 | if [[ -s "${macOS_release_name}_InstallESD.part00" ]]; then 686 | for part in "${macOS_release_name}_InstallESD.part"*; do 687 | echo "/InstallESD${part##*InstallESD}=\"${part}\"" >> "${macOS_release_name}_installation_files.viso" 688 | done 689 | fi 690 | 691 | # NVRAM binary files 692 | for filename in "MLB.bin" "ROM.bin" "csr-active-config.bin" "system-id.bin"; do 693 | if [[ -s "${vm_name}_${filename}" ]]; then 694 | echo "/ESP/EFI/NVRAM/${filename}=\"${vm_name}_${filename}\"" >> "${macOS_release_name}_installation_files.viso" 695 | fi 696 | done 697 | 698 | # EFI drivers for VirtualBox 6.0 and 5.2 699 | for filename in "ApfsDriverLoader.efi" "AppleImageLoader.efi" "AppleUiSupport.efi"; do 700 | if [[ -s "${filename}" ]]; then 701 | echo "/ESP/EFI/OC/Drivers/${filename}=\"${filename}\"" >> "${macOS_release_name}_installation_files.viso" 702 | fi 703 | done 704 | 705 | # EFI startup script 706 | echo "/ESP/startup.nsh=\"${vm_name}_startup.nsh\"" >> "${macOS_release_name}_installation_files.viso" 707 | 708 | } 709 | 710 | function configure_vm() { 711 | print_dimly "stage: configure_vm" 712 | if [[ -n "$( 713 | VBoxManage modifyvm "${vm_name}" --cpu-profile "${cpu_profile}" --cpus "${cpu_count}" \ 714 | --memory "${memory_size}" --vram "${gpu_vram}" --pae on \ 715 | --boot1 none --boot2 none --boot3 none --boot4 none \ 716 | --firmware efi --rtcuseutc on --chipset ich9 ${extension_pack_usb3_support} \ 717 | --mouse usbtablet --keyboard usb 2>&1 >/dev/null 718 | )" ]]; then 719 | echo -e "\nError: Could not configure virtual machine \"${vm_name}\"." 720 | echo -e "If the VM is powered on, power off the virtual machine and resume the script or" 721 | echo -e "execute the stage ${low_contrast_color}configure_vm${default_color}\n" 722 | echo "Exiting." 723 | exit 724 | fi 725 | 726 | VBoxManage setextradata "${vm_name}" \ 727 | "VBoxInternal2/EfiGraphicsResolution" "${resolution}" 728 | VBoxManage setextradata "${vm_name}" \ 729 | "VBoxInternal/Devices/efi/0/Config/DmiSystemFamily" "${DmiSystemFamily}" 730 | VBoxManage setextradata "${vm_name}" \ 731 | "VBoxInternal/Devices/efi/0/Config/DmiSystemProduct" "${DmiSystemProduct}" 732 | VBoxManage setextradata "${vm_name}" \ 733 | "VBoxInternal/Devices/efi/0/Config/DmiSystemSerial" "${DmiSystemSerial}" 734 | VBoxManage setextradata "${vm_name}" \ 735 | "VBoxInternal/Devices/efi/0/Config/DmiSystemUuid" "${DmiSystemUuid}" 736 | VBoxManage setextradata "${vm_name}" \ 737 | "VBoxInternal/Devices/efi/0/Config/DmiOEMVBoxVer" "${DmiOEMVBoxVer}" 738 | VBoxManage setextradata "${vm_name}" \ 739 | "VBoxInternal/Devices/efi/0/Config/DmiOEMVBoxRev" "${DmiOEMVBoxRev}" 740 | VBoxManage setextradata "${vm_name}" \ 741 | "VBoxInternal/Devices/efi/0/Config/DmiBIOSVersion" "${DmiBIOSVersion}" 742 | VBoxManage setextradata "${vm_name}" \ 743 | "VBoxInternal/Devices/efi/0/Config/DmiBoardProduct" "${DmiBoardProduct}" 744 | VBoxManage setextradata "${vm_name}" \ 745 | "VBoxInternal/Devices/efi/0/Config/DmiBoardSerial" "${DmiBoardSerial}" 746 | VBoxManage setextradata "${vm_name}" \ 747 | "VBoxInternal/Devices/efi/0/Config/DmiSystemVendor" "Apple Inc." 748 | VBoxManage setextradata "${vm_name}" \ 749 | "VBoxInternal/Devices/efi/0/Config/DmiSystemVersion" "1.0" 750 | VBoxManage setextradata "${vm_name}" \ 751 | "VBoxInternal/Devices/smc/0/Config/DeviceKey" \ 752 | "ourhardworkbythesewordsguardedpleasedontsteal(c)AppleComputerInc" 753 | VBoxManage setextradata "${vm_name}" \ 754 | "VBoxInternal/Devices/smc/0/Config/GetKeyFromRealSMC" 0 755 | VBoxManage setextradata "${vm_name}" \ 756 | "VBoxInternal/TM/TSCMode" "RealTSCOffset" # avoid boot loop when upgrading to Monterey 757 | } 758 | 759 | # Create the macOS base system virtual disk image 760 | function populate_basesystem_virtual_disk() { 761 | print_dimly "stage: populate_basesystem_virtual_disk" 762 | [[ -s "${macOS_release_name}_BaseSystem.${storage_format}" ]] && echo "${macOS_release_name}_BaseSystem.${storage_format} bootstrap virtual disk image exists." && return 763 | [[ ! -s "${macOS_release_name}_BaseSystem.dmg" ]] && echo -e "\nCould not find ${macOS_release_name}_BaseSystem.dmg; exiting." && exit 764 | echo "Converting BaseSystem.dmg to BaseSystem.img" 765 | dmg2img "${macOS_release_name}_BaseSystem.dmg" "${macOS_release_name}_BaseSystem.img" 766 | VBoxManage storagectl "${vm_name}" --remove --name SATA >/dev/null 2>&1 767 | VBoxManage closemedium "${macOS_release_name}_BaseSystem.${storage_format}" >/dev/null 2>&1 768 | local success='' 769 | VBoxManage convertfromraw --format "${storage_format}" "${macOS_release_name}_BaseSystem.img" "${macOS_release_name}_BaseSystem.${storage_format}" && local success="True" 770 | if [[ "${success}" = "True" ]]; then 771 | command rm "${macOS_release_name}_BaseSystem.img" 2>/dev/null 772 | return 773 | fi 774 | echo "Failed to create \"${macOS_release_name}_BaseSystem.${storage_format}\"." 775 | if [[ "$(cat /proc/sys/kernel/osrelease 2>/dev/null)" =~ [Mm]icrosoft ]]; then 776 | echo -e "\nSome versions of WSL require the script to execute on a Windows filesystem path," 777 | echo -e "for example ${highlight_color}/mnt/c/Users/Public/Documents${default_color}" 778 | echo -e "Switch to a path on the Windows filesystem if VBoxManage.exe fails to" 779 | echo -e "create or open a file.\n" 780 | fi 781 | echo "Exiting." 782 | exit 783 | } 784 | 785 | # Create the installation media virtual disk image 786 | function create_bootable_installer_virtual_disk() { 787 | print_dimly "stage: create_bootable_installer_virtual_disk" 788 | if [[ -w "${macOS_release_name}_bootable_installer.${storage_format}" ]]; then 789 | echo "\"${macOS_release_name}_bootable_installer.${storage_format}\" virtual disk image exists." 790 | echo -ne "${warning_color}Delete \"${macOS_release_name}_bootable_installer.${storage_format}\"?${default_color}" 791 | prompt_delete_y_n 792 | if [[ "${delete}" == "y" ]]; then 793 | if [[ "$( VBoxManage list runningvms )" =~ \""${vm_name}"\" ]] 794 | then 795 | echo "\"${macOS_release_name}_bootable_installer.${storage_format}\" may be deleted" 796 | echo "only when the virtual machine is powered off." 797 | echo "Exiting." 798 | exit 799 | else 800 | VBoxManage storagectl "${vm_name}" --remove --name SATA >/dev/null 2>&1 801 | VBoxManage closemedium "${macOS_release_name}_bootable_installer.${storage_format}" >/dev/null 2>&1 802 | command rm "${macOS_release_name}_bootable_installer.${storage_format}" 803 | fi 804 | else 805 | echo "Exiting." 806 | exit 807 | fi 808 | fi 809 | if [[ ! -e "${macOS_release_name}_bootable_installer.${storage_format}" ]]; then 810 | echo "Creating ${macOS_release_name} installation media virtual disk image." 811 | VBoxManage storagectl "${vm_name}" --remove --name SATA >/dev/null 2>&1 812 | VBoxManage closemedium "${macOS_release_name}_bootable_installer.${storage_format}" >/dev/null 2>&1 813 | VBoxManage createmedium --size=12000 \ 814 | --format "${storage_format}" \ 815 | --filename "${macOS_release_name}_bootable_installer.${storage_format}" \ 816 | --variant standard 2>/dev/tty 817 | fi 818 | } 819 | 820 | function populate_bootable_installer_virtual_disk() { 821 | print_dimly "stage: populate_bootable_installer_virtual_disk" 822 | # Attach virtual disk images of the base system, installation, and target 823 | # to the virtual machine 824 | VBoxManage storagectl "${vm_name}" --remove --name SATA >/dev/null 2>&1 825 | 826 | if [[ -n $( 827 | 2>&1 VBoxManage storagectl "${vm_name}" --add sata --name SATA --hostiocache on >/dev/null 828 | ) ]]; then echo "Could not configure virtual machine storage controller. Exiting."; exit; fi 829 | if [[ -n $( 830 | 2>&1 VBoxManage storageattach "${vm_name}" --storagectl SATA --port 1 --hotpluggable on \ 831 | --type hdd --nonrotational on --medium "${macOS_release_name}_bootable_installer.${storage_format}" >/dev/null 832 | ) ]]; then echo "Could not attach \"${macOS_release_name}_bootable_installer.${storage_format}\". Exiting."; exit; fi 833 | if [[ -n $( 834 | 2>&1 VBoxManage storageattach "${vm_name}" --storagectl SATA --port 2 \ 835 | --type dvddrive --medium "${macOS_release_name}_installation_files.viso" >/dev/null 836 | ) ]]; then echo "Could not attach \"${macOS_release_name}_installation_files.viso\". Exiting."; exit; fi 837 | if [[ -n $( 838 | 2>&1 VBoxManage storageattach "${vm_name}" --storagectl SATA --port 3 --hotpluggable on \ 839 | --type hdd --nonrotational on --medium "${macOS_release_name}_BaseSystem.${storage_format}" >/dev/null 840 | ) ]]; then echo "Could not attach \"${macOS_release_name}_BaseSystem.${storage_format}\". Exiting."; exit; fi 841 | 842 | echo -e "\nCreating VirtualBox 6 virtual ISO containing macOS Terminal script" 843 | echo -e "for partitioning and populating the bootable installer virtual disk.\n" 844 | create_viso_header "${vm_name}_populate_bootable_installer_virtual_disk.viso" "bootinst-sh" 845 | echo "/bootinst.sh=\"${vm_name}_bootinst.txt\"" >> "${vm_name}_populate_bootable_installer_virtual_disk.viso" 846 | # Assigning "physical" disks from largest to smallest to "${disks[]}" array 847 | # Partitioning largest disk as APFS 848 | # Partition second-largest disk as JHFS+ 849 | echo '# this script is executed on the macOS virtual machine' > "${vm_name}_bootinst.txt" 850 | echo 'disks="$(diskutil list | grep -o "\*[0-9][^ ]* [GTP]B *disk[0-9]$" | grep -o "[0-9].*")" && \ 851 | disks="$(echo "${disks}" | sed -e "s/\.[0-9] T/000.0 G/" -e "s/\.[0-9] P/000000.0 G/" | sort -gr | grep -o disk[0-9] )" && \ 852 | disks=(${disks[@]}) && \ 853 | if [ -z "${disks}" ]; then echo "Could not find disks"; fi && \ 854 | [ -n "${disks[0]}" ] && \ 855 | diskutil partitionDisk "/dev/${disks[0]}" 1 GPT JHFS+ "Install" R && \' >> "${vm_name}_bootinst.txt" 856 | # Create secondary base system on the Install disk 857 | # and copy macOS install app files to the app directory 858 | echo 'asr restore --source "/Volumes/'"${macOS_release_name:0:5}-files"'/BaseSystem.dmg" --target /Volumes/Install --erase --noprompt && \ 859 | app_path="$(ls -d "/Volumes/"*"Base System 1/Install"*.app)" && \ 860 | install_path="${app_path}/Contents/SharedSupport/" && \ 861 | mkdir -p "${install_path}" && cd "/Volumes/'"${macOS_release_name:0:5}-files/"'" && \ 862 | cp *.chunklist *.plist *.dmg "${install_path}" && \ 863 | echo "" && echo "Copying the several-GB InstallESD.dmg to the installer app directory" && echo "Please wait" && \ 864 | if [ -s "${install_path}/InstallESD.dmg" ]; then \ 865 | rm -f "${install_path}/InstallESD.dmg" ; fi && \ 866 | for part in InstallESD.part*; do echo "Concatenating ${part}"; cat "${part}" >> "${install_path}/InstallESD.dmg"; done && \ 867 | sed -i.bak -e "s/InstallESDDmg\.pkg/InstallESD.dmg/" -e "s/pkg\.InstallESDDmg/dmg.InstallESD/" "${install_path}InstallInfo.plist" && \ 868 | sed -i.bak2 -e "/InstallESD\.dmg/{n;N;N;N;d;}" "${install_path}InstallInfo.plist" && \' >> "${vm_name}_bootinst.txt" 869 | # shut down the virtual machine 870 | echo 'shutdown -h now' >> "${vm_name}_bootinst.txt" 871 | if [[ -n $( 872 | 2>&1 VBoxManage storageattach "${vm_name}" --storagectl SATA --port 4 \ 873 | --type dvddrive --medium "${vm_name}_populate_bootable_installer_virtual_disk.viso" >/dev/null 874 | ) ]]; then echo "Could not attach \"${vm_name}_populate_bootable_installer_virtual_disk.viso\". Exiting."; exit; fi 875 | echo -e "\nStarting virtual machine \"${vm_name}\". 876 | This should take a couple of minutes. If booting fails, exit the script by 877 | pressing CTRL-C then see the documentation for information about applying 878 | different CPU profiles in the section ${highlight_color}CPU profiles and CPUID settings${default_color}." 879 | ( VBoxManage startvm "${vm_name}" >/dev/null 2>&1 ) 880 | echo -e "\nUntil the script completes, please do not manually interact with\nthe virtual machine." 881 | [[ -z "${kscd}" ]] && declare_scancode_dict 882 | prompt_lang_utils_terminal 883 | kbstring='/Volumes/bootinst-sh/bootinst.sh' 884 | send_keys 885 | send_enter 886 | echo -e "\nPartitioning the bootable installer virtual disk; loading base system onto the 887 | installer virtual disk; moving installation files to installer virtual disk; 888 | updating the InstallInfo.plist file; and rebooting the virtual machine. 889 | 890 | The virtual machine may report that disk space is critically low; this is fine. 891 | 892 | When the bootable installer virtual disk is finished being populated, the script 893 | will shut down the virtual machine. After shutdown, the initial base system will 894 | be detached from the VM and released from VirtualBox." 895 | print_dimly "If the partitioning fails, exit the script by pressing CTRL-C" 896 | print_dimly "Otherwise, please wait." 897 | while [[ "$( VBoxManage list runningvms )" =~ \""${vm_name}"\" ]]; do sleep 2 >/dev/null 2>&1; done 898 | echo "Waiting for the VirtualBox GUI to shut off." 899 | animated_please_wait 10 900 | # Detach the original 2GB BaseSystem virtual disk image 901 | # and release basesystem VDI from VirtualBox configuration 902 | if [[ -n $( 903 | 2>&1 VBoxManage storageattach "${vm_name}" --storagectl SATA --port 3 --medium none >/dev/null 904 | 2>&1 VBoxManage closemedium "${macOS_release_name}_BaseSystem.${storage_format}" >/dev/null 905 | ) ]]; then 906 | echo "Could not detach ${macOS_release_name}_BaseSystem.${storage_format}" 907 | echo "The script will try to detach the virtual disk image. If this takes more than" 908 | echo "a few seconds, terminate the script with CTRL-C, manually shut down VirtualBox," 909 | echo "and resume with the following stages as described in the documentation:" 910 | echo " ${highlight_color}create_target_virtual_disk populate_macos_target_disk${default_color}" 911 | while [[ -n $( 912 | 2>&1 VBoxManage storageattach "${vm_name}" --storagectl SATA --port 3 --medium none >/dev/null 913 | 2>&1 VBoxManage closemedium "${macOS_release_name}_BaseSystem.${storage_format}" >/dev/null 914 | ) ]]; do 915 | animated_please_wait 10 916 | done 917 | fi 918 | echo -e "\n${macOS_release_name}_BaseSystem.${storage_format} successfully detached from" 919 | echo "the virtual machine and released from VirtualBox Manager." 920 | } 921 | 922 | function create_target_virtual_disk() { 923 | print_dimly "stage: create_target_virtual_disk" 924 | if [[ -w "${vm_name}.${storage_format}" ]]; then 925 | echo "${vm_name}.${storage_format} target system virtual disk image exists." 926 | echo -ne "${warning_color}Delete \"${vm_name}.${storage_format}\"?${default_color}" 927 | prompt_delete_y_n 928 | if [[ "${delete}" == "y" ]]; then 929 | if [[ "$( VBoxManage list runningvms )" =~ \""${vm_name}"\" ]] 930 | then 931 | echo "\"${vm_name}.${storage_format}\" may be deleted" 932 | echo "only when the virtual machine is powered off." 933 | echo "Exiting." 934 | exit 935 | else 936 | VBoxManage storagectl "${vm_name}" --remove --name SATA >/dev/null 2>&1 937 | VBoxManage closemedium "${vm_name}.${storage_format}" >/dev/null 2>&1 938 | command rm "${vm_name}.${storage_format}" 939 | fi 940 | else 941 | echo "Exiting." 942 | exit 943 | fi 944 | fi 945 | if [[ "${macOS_release_name}" = "Catalina" && "${storage_size}" -lt 25000 ]]; then 946 | echo "Attempting to install macOS Catalina on a disk smaller than 25000MB will fail." 947 | echo "Please assign a larger virtual disk image size. Exiting." 948 | exit 949 | elif [[ "${storage_size}" -lt 22000 ]]; then 950 | echo "Attempting to install macOS on a disk smaller than 22000MB will fail." 951 | echo "Please assign a larger virtual disk image size. Exiting." 952 | exit 953 | fi 954 | if [[ ! -e "${vm_name}.${storage_format}" ]]; then 955 | echo "Creating target system virtual disk image for \"${vm_name}\"" 956 | VBoxManage storagectl "${vm_name}" --remove --name SATA >/dev/null 2>&1 957 | VBoxManage closemedium "${vm_name}.${storage_format}" >/dev/null 2>&1 958 | VBoxManage createmedium --size="${storage_size}" \ 959 | --format "${storage_format}" \ 960 | --filename "${vm_name}.${storage_format}" \ 961 | --variant standard 2>/dev/tty 962 | fi 963 | } 964 | 965 | function populate_macos_target_disk() { 966 | print_dimly "stage: populate_macos_target_disk" 967 | if [[ "$( VBoxManage list runningvms )" =~ \""${vm_name}"\" ]]; then 968 | echo -e "${highlight_color}Please ${warning_color}manually${highlight_color} shut down the virtual machine and press enter to continue.${default_color}" 969 | clear_input_buffer_then_read 970 | fi 971 | VBoxManage storagectl "${vm_name}" --remove --name SATA >/dev/null 2>&1 972 | if [[ -n $( 973 | 2>&1 VBoxManage storagectl "${vm_name}" --add sata --name SATA --hostiocache on >/dev/null 974 | ) ]]; then echo "Could not configure virtual machine storage controller. Exiting."; exit; fi 975 | if [[ -n $( 976 | 2>&1 VBoxManage storageattach "${vm_name}" --storagectl SATA --port 0 \ 977 | --type hdd --nonrotational on --medium "${vm_name}.${storage_format}" >/dev/null 978 | ) ]]; then echo "Could not attach \"${vm_name}.${storage_format}\". Exiting."; exit; fi 979 | if [[ -n $( 980 | 2>&1 VBoxManage storageattach "${vm_name}" --storagectl SATA --port 1 --hotpluggable on \ 981 | --type hdd --nonrotational on --medium "${macOS_release_name}_bootable_installer.${storage_format}" >/dev/null 982 | ) ]]; then echo "Could not attach \"${macOS_release_name}_bootable_installer.${storage_format}\". Exiting."; exit; fi 983 | if [[ -n $( 984 | 2>&1 VBoxManage storageattach "${vm_name}" --storagectl SATA --port 2 \ 985 | --type dvddrive --medium "${macOS_release_name}_installation_files.viso" >/dev/null 986 | ) ]]; then echo "Could not attach \"${macOS_release_name}_installation_files.viso\". Exiting."; exit; fi 987 | 988 | echo -e "\nCreating VirtualBox 6 virtual ISO containing macOS Terminal scripts" 989 | echo -e "for partitioning and populating the target virtual disk.\n" 990 | create_viso_header "${vm_name}_populate_macos_target_disk.viso" "target-sh" 991 | echo "/nvram.sh=\"${vm_name}_configure_nvram.txt\"" >> "${vm_name}_populate_macos_target_disk.viso" 992 | echo "/startosinstall.sh=\"${vm_name}_startosinstall.txt\"" >> "${vm_name}_populate_macos_target_disk.viso" 993 | # execute script concurrently, catch SIGUSR1 when installer finishes preparing 994 | echo '# this script is executed on the macOS virtual machine' > "${vm_name}_configure_nvram.txt" 995 | echo 'printf '"'"'trap "exit 0" SIGUSR1; while true; do sleep 10; done;'"'"' | sh && \ 996 | disks="$(diskutil list | grep -o "[0-9][^ ]* [GTP]B *disk[0-9]$")" && \ 997 | disks="$(echo "${disks}" | sed -e "s/\.[0-9] T/000.0 G/" -e "s/\.[0-9] P/000000.0 G/" | sort -gr | grep -o disk[0-9])" && \ 998 | disks=(${disks[@]}) && \ 999 | mkdir -p "/Volumes/'"${vm_name}"'/tmp/mount_efi" && \ 1000 | mount_msdos /dev/${disks[0]}s1 "/Volumes/'"${vm_name}"'/tmp/mount_efi" && \ 1001 | cp -r "/Volumes/'"${macOS_release_name:0:5}-files"'/ESP/"* "/Volumes/'"${vm_name}"'/tmp/mount_efi/" && \ 1002 | installer_pid=$(ps | grep startosinstall | grep -v grep | cut -d '"'"' '"'"' -f 3) && \ 1003 | kill -SIGUSR1 ${installer_pid}' >> "${vm_name}_configure_nvram.txt" 1004 | # Find background process PID, then 1005 | # start the installer, send SIGUSR1 to concurrent bash script, 1006 | # the other script copies files to EFI system partition, 1007 | # then sends SIGUSR1 to the installer which restarts the virtual machine 1008 | echo '# this script is executed on the macOS virtual machine' > "${vm_name}_startosinstall.txt" 1009 | echo 'background_pid="$(ps | grep '"'"' sh$'"'"' | cut -d '"'"' '"'"' -f 3)" && \ 1010 | [[ "${background_pid}" =~ ^[0-9][0-9]*$ ]] && \ 1011 | disks="$(diskutil list | grep -o "[0-9][^ ]* [GTP]B *disk[0-9]$")" && \ 1012 | disks="$(echo "${disks}" | sed -e "s/\.[0-9] T/000.0 G/" -e "s/\.[0-9] P/000000.0 G/" | sort -gr | grep -o disk[0-9])" && \ 1013 | disks=(${disks[@]}) && \ 1014 | [ -n "${disks[0]}" ] && \ 1015 | diskutil partitionDisk "/dev/${disks[0]}" 1 GPT APFS "'"${vm_name}"'" R && \ 1016 | app_path="$(ls -d /Install*.app)" && \ 1017 | cd "/${app_path}/Contents/Resources/" && \ 1018 | ./startosinstall --agreetolicense --pidtosignal ${background_pid} --rebootdelay 90 --volume "/Volumes/'"${vm_name}"'"' >> "${vm_name}_startosinstall.txt" 1019 | if [[ -n $( 1020 | 2>&1 VBoxManage storageattach "${vm_name}" --storagectl SATA --port 3 \ 1021 | --type dvddrive --medium "${vm_name}_populate_macos_target_disk.viso" >/dev/null 1022 | ) ]]; then echo "Could not attach \"${vm_name}_populate_macos_target_disk.viso\". Exiting."; exit; fi 1023 | echo "The VM will boot from the populated installer base system virtual disk." 1024 | ( VBoxManage startvm "${vm_name}" >/dev/null 2>&1 ) 1025 | [[ -z "${kscd}" ]] && declare_scancode_dict 1026 | prompt_lang_utils_terminal 1027 | add_another_terminal 1028 | echo -e "\nThe second open Terminal in the virtual machine copies EFI and NVRAM files" 1029 | echo -e "to the target EFI system partition when the installer finishes preparing." 1030 | echo -e "\nAfter the installer finishes preparing and the EFI and NVRAM files are copied," 1031 | echo -ne "macOS will install and boot up when booting the target disk.\n" 1032 | print_dimly "Please wait" 1033 | kbstring='/Volumes/target-sh/nvram.sh' 1034 | send_keys 1035 | send_enter 1036 | cycle_through_terminal_windows 1037 | kbstring='/Volumes/target-sh/startosinstall.sh' 1038 | send_keys 1039 | send_enter 1040 | if [[ ! ( "${vbox_version:0:1}" -gt 6 1041 | || ( "${vbox_version:0:1}" = 6 && "${vbox_version:2:1}" -ge 1 ) ) ]]; then 1042 | echo -e "\n${highlight_color}When the installer finishes preparing and reboots the VM, press enter${default_color} so the script 1043 | powers off the virtual machine and detaches the device \"${macOS_release_name}_bootable_installer.${storage_format}\" to avoid 1044 | booting into the initial installer environment again." 1045 | clear_input_buffer_then_read 1046 | VBoxManage controlvm "${vm_name}" poweroff >/dev/null 2>&1 1047 | for (( i=10; i>0; i-- )); do echo -ne " \r${i} "; sleep 0.5; done; echo -ne "\r " 1048 | VBoxManage storagectl "${vm_name}" --remove --name SATA >/dev/null 2>&1 1049 | VBoxManage storagectl "${vm_name}" --add sata --name SATA --hostiocache on >/dev/null 2>&1 1050 | VBoxManage storageattach "${vm_name}" --storagectl SATA --port 0 \ 1051 | --type hdd --nonrotational on --medium "${vm_name}.${storage_format}" 1052 | fi 1053 | echo -e "\nFor further information, such as applying EFI and NVRAM variables to enable 1054 | iMessage connectivity, see the documentation with the following command:\n" 1055 | would_you_like_to_know_less 1056 | echo -e "\n${highlight_color}If the Terminal window progress counter reaches 100% and reboots into${default_color}" 1057 | echo -e "${highlight_color}an Apple macOS installer progress bar, that's it! Enjoy your virtual machine.${default_color}\n" 1058 | } 1059 | 1060 | function prompt_delete_temporary_files() { 1061 | print_dimly "stage: prompt_delete_temporary_files" 1062 | if [[ ! "$(VBoxManage showvminfo "${vm_name}")" =~ State:[\ \t]*powered\ off ]] 1063 | then 1064 | echo -e "Temporary files may be deleted when the virtual machine is powered off 1065 | and without a suspended state by executing the following command at the script's 1066 | working directory: 1067 | 1068 | ${highlight_color}${0} prompt_delete_temporary_files${default_color}" 1069 | else 1070 | # detach temporary VDIs and attach the macOS target disk 1071 | VBoxManage storagectl "${vm_name}" --remove --name SATA >/dev/null 2>&1 1072 | VBoxManage storagectl "${vm_name}" --add sata --name SATA --hostiocache on >/dev/null 2>&1 1073 | if [[ -s "${vm_name}.${storage_format}" ]]; then 1074 | VBoxManage storageattach "${vm_name}" --storagectl SATA --port 0 \ 1075 | --type hdd --nonrotational on --medium "${vm_name}.${storage_format}" 1076 | fi 1077 | VBoxManage closemedium "${macOS_release_name}_bootable_installer.${storage_format}" >/dev/null 2>&1 1078 | VBoxManage closemedium "${macOS_release_name}_BaseSystem.${storage_format}" >/dev/null 2>&1 1079 | echo -e "The following temporary files are safe to delete:\n" 1080 | temporary_files=("${macOS_release_name}_Apple"* 1081 | "${macOS_release_name}_BaseSystem"* 1082 | "${macOS_release_name}_Install"* 1083 | "${macOS_release_name}_bootable_installer"* 1084 | "${macOS_release_name}_"*".viso" 1085 | "${vm_name}_"*".png" 1086 | "${vm_name}_"*".bin" 1087 | "${vm_name}_"*".txt" 1088 | "${vm_name}_"*".viso" 1089 | "${vm_name}_startup.nsh" 1090 | "ApfsDriverLoader.efi" 1091 | "Apple"*".efi" 1092 | "AppleSupport-v2.0.4-RELEASE.zip" 1093 | "dmg2img.exe") 1094 | ls -d "${temporary_files[@]}" 2>/dev/null 1095 | echo -ne "\n${warning_color}Delete temporary files listed above?${default_color}" 1096 | prompt_delete_y_n 1097 | if [[ "${delete}" == "y" ]]; then 1098 | command rm -f "${temporary_files[@]}" 2>/dev/null 1099 | fi 1100 | fi 1101 | } 1102 | 1103 | function and_all_subsequent_stages() { 1104 | # if exactly two arguments were specified on the command line, and the first is a stage title, 1105 | # then perform all stages subsequent to the specified stage, otherwise do nothing. 1106 | [[ ${#specified_arguments[@]} -ne 2 ]] && return 1107 | for stage in "${stages[@]}"; do 1108 | [[ "${stages[0]}" = "${specified_arguments[0]}" ]] && break 1109 | stages=( "${stages[@]:1}" ) 1110 | done 1111 | for stage in "${stages[@]:1}"; do ${stage}; done 1112 | } 1113 | 1114 | function documentation() { 1115 | low_contrast_stages="" 1116 | for stage in "${stages[@]}"; do 1117 | low_contrast_stages="${low_contrast_stages}"' '"${low_contrast_color}${stage}${default_color}"$'\n' 1118 | done 1119 | echo -ne "\n ${highlight_color}NAME${default_color} 1120 | Push-button installer of macOS on VirtualBox 1121 | 1122 | ${highlight_color}DESCRIPTION${default_color} 1123 | The script downloads macOS High Sierra, Mojave, and Catalina from Apple servers 1124 | and installs them on VirtualBox 5.2, 6.0, and 6.1. The script doesn't install 1125 | any closed-source additions or bootloaders. A default install requires the user 1126 | press enter when prompted, less than ten times, to complete the installation. 1127 | Systems with the package ${low_contrast_color}tesseract-ocr${default_color} may automate the installation completely. 1128 | 1129 | ${highlight_color}USAGE${default_color} 1130 | ${low_contrast_color}${0} [STAGE]... ${default_color} 1131 | 1132 | The installation is divided into stages. Stage titles may be given as command- 1133 | line arguments for the script. When the script is executed with no command-line 1134 | arguments, each of the stages is performed in succession in the order listed: 1135 | 1136 | ${low_contrast_color}check_shell${default_color} 1137 | ${low_contrast_stages} 1138 | Other than the stages above, the command-line arguments \"${low_contrast_color}documentation${default_color}\", 1139 | \"${low_contrast_color}troubleshoot${default_color}\", and \"${low_contrast_color}and_all_subsequent_stages${default_color}\" are available. \"${low_contrast_color}troubleshoot${default_color}\" 1140 | generates a file with system information, VirtualBox logs, and checksums for 1141 | some installation files. \"${low_contrast_color}documentation${default_color}\" outputs the script's documentation. 1142 | If \"${low_contrast_color}documentation${default_color}\" is the first argument, no other arguments are parsed. 1143 | If the first argument is a stage title and the only other argument is 1144 | \"${low_contrast_color}and_all_subsequent_stages${default_color}\", then the speficied stage and all subsequent 1145 | stages are performed in succession in the order listed above; 1146 | otherwise, \"${low_contrast_color}and_all_subsequent_stages${default_color}\" does not perform any stages. 1147 | 1148 | The stage \"${low_contrast_color}check_shell${default_color}\" is always performed when the script loads. 1149 | 1150 | The stages \"${low_contrast_color}check_gnu_coreutils_prefix${default_color}\", \"${low_contrast_color}set_variables${default_color}\", and 1151 | \"${low_contrast_color}check_dependencies${default_color}\" are always performed when any stage title other than 1152 | \"${low_contrast_color}documentation${default_color}\" is specified as the first argument, and the rest of the 1153 | specified stages are performed only after the checks pass. 1154 | 1155 | ${highlight_color}EXAMPLES${default_color} 1156 | ${low_contrast_color}${0} create_vm configure_vm${default_color} 1157 | 1158 | The above command might be used to create and configure a virtual machine on a 1159 | new VirtualBox installation, then manually attach to the new virtual machine 1160 | an existing macOS disk image that was previously created by the script. 1161 | 1162 | ${low_contrast_color}${0} prompt_delete_temporary_files${default_color} 1163 | 1164 | The above command might be used when no more virtual machines need to be 1165 | installed, and the temporary files can be deleted. 1166 | 1167 | ${low_contrast_color}${0} configure_vm and_all_subsequent_stages${default_color} 1168 | 1169 | The above command might be used to resume the script stages after the stage 1170 | \"${low_contrast_color}configure_vm${default_color}\" failed. 1171 | 1172 | ${low_contrast_color}${0} "'\\'"${default_color} 1173 | ${low_contrast_color}configure_vm create_nvram_files create_macos_installation_files_viso${default_color} 1174 | 1175 | The above command might be used to update the EFI and NVRAM variables required 1176 | for iCloud and iMessage connectivity and other Apple-connected apps. 1177 | 1178 | ${highlight_color}Configuration${default_color} 1179 | The script's default configuration is stored in the ${low_contrast_color}set_variables()${default_color} function at 1180 | the top of the script. No manual configuration is required to use the script. 1181 | 1182 | The configuration may be manually edited either by editing the variable 1183 | assignment in ${low_contrast_color}set_variables()${default_color} or by executing the following command: 1184 | 1185 | ${low_contrast_color}export macos_vm_vars_file=/path/to/variable_assignment_file${default_color} 1186 | 1187 | \"${low_contrast_color}variable_assignment_file${default_color}\" is a plain text file that contains zero or more 1188 | lines with a variable assignment for any variable specified in ${low_contrast_color}set_variables()${default_color}, 1189 | for example ${low_contrast_color}macOS_release_name=\"HighSierra\"${default_color} or ${low_contrast_color}DmiSystemFamily=\"iMac\"${default_color} 1190 | 1191 | ${highlight_color}iCloud and iMessage connectivity${default_color} 1192 | iCloud, iMessage, and other connected Apple services require a valid device 1193 | name and serial number, board ID and serial number, and other genuine 1194 | (or genuine-like) Apple parameters. Assigning these parameters is ${low_contrast_color}not required${default_color} 1195 | when installing or using macOS, only when connecting to the iCloud app, 1196 | iMessage, and other apps that authenticate the device with Apple. 1197 | 1198 | These are the variables that are usually required for iMessage connectivity: 1199 | 1200 | ${low_contrast_color}DmiSystemFamily # Model name${default_color} 1201 | ${low_contrast_color}DmiSystemProduct # Model identifier${default_color} 1202 | ${low_contrast_color}DmiBIOSVersion # Boot ROM version${default_color} 1203 | ${low_contrast_color}DmiSystemSerial # System serial number${default_color} 1204 | ${low_contrast_color}DmiSystemUuid # Hardware unique identifier${default_color} 1205 | ${low_contrast_color}ROM # ROM identifier, stored in NVRAM${default_color} 1206 | ${low_contrast_color}MLB # Main Logic Board serial, stored in NVRAM${default_color} 1207 | ${low_contrast_color}DmiBoardSerial # Main Logic Board serial, stored in EFI${default_color} 1208 | ${low_contrast_color}DmiBoardProduct # Product (board) identifier${default_color} 1209 | ${low_contrast_color}SystemUUID # System unique identifier, stored in NVRAM${default_color} 1210 | 1211 | These parameters may be manually set in the ${low_contrast_color}set_variables()${default_color} function when the 1212 | \"${low_contrast_color}get_parameters_from_macOS_host${default_color}\" is set to \"${low_contrast_color}no${default_color}\", which is the default setting. When 1213 | the script is executed on macOS and the variable \"${low_contrast_color}get_parameters_from_macOS_host${default_color}\" is 1214 | set to \"${low_contrast_color}yes${default_color}\", the script copies the parameters from the host. 1215 | 1216 | ${highlight_color}Applying the EFI and NVRAM parameters${default_color} 1217 | The EFI and NVRAM parameters may be set in the script before installation by 1218 | editing them at the top of the script. NVRAM parameters may be applied after 1219 | the last step of the installation by resetting the virtual machine and booting 1220 | into the EFI Internal Shell. When resetting or powering up the VM, immediately 1221 | press Esc when the VirtualBox logo appears. This boots into the EFI Internal 1222 | Shell or the boot menu. If the boot menu appears, select \"Boot Manager\" and 1223 | then \"EFI Internal Shell\" and then allow the ${low_contrast_color}startup.nsh${default_color} script to execute 1224 | automatically, applying the NVRAM variables before booting macOS. 1225 | 1226 | ${highlight_color}Changing the EFI and NVRAM parameters after installation${default_color} 1227 | The variables mentioned above may be edited and applied to an existing macOS 1228 | virtual machine by deleting the ${low_contrast_color}.nvram${default_color} file from the directory where the 1229 | virtual machine ${low_contrast_color}.vbox${default_color} file is stored, then executing the following 1230 | command and copying the generated files to the macOS EFI System Partition: 1231 | 1232 | ${low_contrast_color}${0} "'\\'"${default_color} 1233 | ${low_contrast_color}configure_vm create_nvram_files create_macos_installation_files_viso${default_color} 1234 | 1235 | After executing the command, attach the resulting VISO file to the virtual 1236 | machine's storage through VirtualBox Manager or VBoxManage. Power up the VM 1237 | and boot macOS, then start Terminal and execute the following commands, making 1238 | sure to replace \"[VISO_mountpoint]\" with the correct path: 1239 | 1240 | ${low_contrast_color}mkdir ESP${default_color} 1241 | ${low_contrast_color}sudo su # this will prompt for a password${default_color} 1242 | ${low_contrast_color}diskutil mount -mountPoint ESP disk0s1${default_color} 1243 | ${low_contrast_color}cp -r /Volumes/[VISO_mountpoint]/ESP/* ESP/${default_color} 1244 | 1245 | After copying the files, boot into the EFI Internal Shell as described in the 1246 | section \"Applying the EFI and NVRAM parameters\". 1247 | 1248 | ${highlight_color}Storage format${default_color} 1249 | The script by default assigns a target virtual disk storage format of VDI. This 1250 | format can be resized by VirtualBox as explained in the next section. The other 1251 | available format, VMDK, cannot be resized by VirtualBox but can be attached to 1252 | a QEMU virtual machine for use with Linux KVM for better performance. 1253 | 1254 | ${highlight_color}Storage size${default_color} 1255 | The script by default assigns a target virtual disk storage size of 80GB, which 1256 | is populated to about 25GB on the host on initial installation. After the 1257 | installation is complete, the VDI storage size may be increased. First increase 1258 | the virtual disk image size through VirtualBox Manager or VBoxManage, then in 1259 | Terminal in the virtual machine execute the following command: 1260 | ${low_contrast_color}sudo diskutil repairDisk disk0${default_color} 1261 | After it completes, open Disk Utility and delete the \"Free space\" partition so 1262 | it allows the system APFS container to take up the available space, or if that 1263 | fails, execute the following command: 1264 | ${low_contrast_color}sudo diskutil apfs resizeContainer disk1 0${default_color} 1265 | Both Disk Utility and ${low_contrast_color}diskutil${default_color} may fail and require successive resize attempts 1266 | separated by virtual machine reboots. 1267 | 1268 | ${highlight_color}Primary display resolution${default_color} 1269 | The following command assigns the virtual machine primary display resolution: 1270 | ${low_contrast_color}VBoxManage setextradata \"\${vm_name}\" \\${default_color} 1271 | ${low_contrast_color}\"VBoxInternal2/EfiGraphicsResolution\" \"\${resolution}\"${default_color} 1272 | The following primary display resolutions are supported by macOS on VirtualBox: 1273 | ${low_contrast_color}5120x2880 2880x1800 2560x1600 2560x1440 1920x1200 1600x1200 1680x1050${default_color} 1274 | ${low_contrast_color}1440x900 1280x800 1024x768 640x480${default_color} 1275 | Secondary displays can have an arbitrary resolution. 1276 | 1277 | ${highlight_color}Unsupported features${default_color} 1278 | Developing and maintaining VirtualBox or macOS features is beyond the scope of 1279 | this script. Some features may behave unexpectedly, such as USB device support, 1280 | audio support, FileVault boot password prompt support, and other features. 1281 | 1282 | ${highlight_color}CPU profiles and CPUID settings${default_color} (unsupported) 1283 | macOS does not support every CPU supported by VirtualBox. If the macOS Base 1284 | System does not boot, try applying different CPU profiles to the virtual 1285 | machine with the ${low_contrast_color}VBoxManage${default_color} commands described below. First, while the 1286 | VM is powered off, set the guest's CPU profile to the host's CPU profile, then 1287 | try to boot the virtual machine: 1288 | ${low_contrast_color}VBoxManage modifyvm \"\${vm_name}\" --cpu-profile host${default_color} 1289 | ${low_contrast_color}VBoxManage modifyvm \"\${vm_name}\" --cpuidremoveall${default_color} 1290 | If booting fails, try assigning each of the preconfigured CPU profiles while 1291 | the VM is powered off with the following command: 1292 | ${low_contrast_color}VBoxManage modifyvm \"\${vm_name}\" --cpu-profile \"\${cpu_profile}\"${default_color} 1293 | Available CPU profiles: 1294 | ${low_contrast_color}\"Intel Xeon X5482 3.20GHz\" \"Intel Core i7-2635QM\" \"Intel Core i7-3960X\"${default_color} 1295 | ${low_contrast_color}\"Intel Core i5-3570\" \"Intel Core i7-5600U\" \"Intel Core i7-6700K\"${default_color} 1296 | If booting fails after trying each preconfigured CPU profile, the host's CPU 1297 | requires specific ${highlight_color}macOS VirtualBox CPUID settings${default_color}. 1298 | 1299 | ${highlight_color}Performance and deployment${default_color} (unsupported) 1300 | After successfully creating a working macOS virtual machine, consider importing 1301 | the virtual machine into more performant virtualization software, or packaging 1302 | it for configuration management platforms for automated deployment. These 1303 | virtualization and deployment applications require additional configuration 1304 | that is beyond the scope of the script. 1305 | 1306 | QEMU with KVM is capable of providing virtual machine hardware passthrough 1307 | for near-native performance. QEMU supports the VMDK virtual disk image format, 1308 | which can be configured to be created by the script, or converted from the 1309 | default VirtualBox VDI format into the VMDK format with the following command: 1310 | ${low_contrast_color}VBoxManage clonehd --format vmdk source.vdi target.vmdk${default_color} 1311 | QEMU and KVM require additional configuration that is beyond the scope of the 1312 | script. 1313 | 1314 | ${highlight_color}VirtualBox Native Execution Manager${default_color} (unsupported) 1315 | The VirtualBox Native Execution Manager (NEM) is an experimental VirtualBox 1316 | feature. VirtualBox uses NEM when access to VT-x and AMD-V is blocked by 1317 | virtualization software or execution protection features such as Hyper-V, 1318 | Windows Sandbox, WSL2, memory integrity protection, and other software. 1319 | macOS and the macOS installer have memory corruption issues under NEM 1320 | virtualization. The script checks for NEM and exits with an error message if 1321 | NEM is detected. 1322 | 1323 | ${highlight_color}Bootloaders${default_color} (unsupported) 1324 | The macOS VirtualBox guest is loaded without extra bootloaders, but it is 1325 | compatible with OpenCore. OpenCore requires additional configuration that is 1326 | beyond the scope of the script. 1327 | 1328 | ${highlight_color}Display scaling${default_color} (unsupported) 1329 | VirtualBox does not supply an EDID for its virtual display, and macOS does not 1330 | enable display scaling (high PPI) without an EDID. The bootloader OpenCore can 1331 | inject an EDID which enables display scaling. 1332 | 1333 | ${highlight_color}Audio${default_color} (unsupported) 1334 | macOS may not support any built-in VirtualBox audio controllers. The bootloader 1335 | OpenCore may be able to load open-source audio drivers in VirtualBox. 1336 | 1337 | ${highlight_color}FileVault${default_color} (unsupported) 1338 | The VirtualBox EFI implementation does not properly load the FileVault full disk 1339 | encryption password prompt upon boot. The bootloader OpenCore is be able to 1340 | load the password prompt with the parameter \"ProvideConsoleGop\" set to \"true\". 1341 | 1342 | ${highlight_color}Further information${default_color} 1343 | Further information is available at the following URL: 1344 | ${highlight_color}https://github.com/myspaghetti/macos-virtualbox${default_color} 1345 | 1346 | " 1347 | } 1348 | 1349 | function troubleshoot() { 1350 | echo -ne "\nWriting troubleshooting information to \"${highlight_color}${vm_name}_troubleshoot.txt${default_color}\"\n\n" 1351 | echo "The file will contain system information, VirtualBox paths, logs, configuration," 1352 | echo "macOS virtual machine details including ${highlight_color}serials entered in the script${default_color}," 1353 | echo "and macOS installation file md5 checksums." 1354 | echo "When sharing this file, mind that it contains the above information." 1355 | echo "" 1356 | for wrapper in 1; do 1357 | echo "################################################################################" 1358 | head -n 5 "${0}" 1359 | if [[ -n "$(md5sum --version 2>/dev/null)" ]]; then 1360 | tail -n +60 "${0}" | md5sum 2>/dev/null 1361 | else 1362 | tail -n +60 "${0}" | md5 2>/dev/null 1363 | fi 1364 | echo "################################################################################" 1365 | echo "BASH_VERSION ${BASH_VERSION}" 1366 | vbox_ver="$(VBoxManage -v)" 1367 | echo "VBOX_VERSION ${vbox_ver//[$'\r\n']/}" 1368 | macos_ver="$(sw_vers 2>/dev/null)" 1369 | wsl_ver="$(cat /proc/sys/kernel/osrelease 2>/dev/null)" 1370 | win_ver="$(cmd.exe /d/s/c call ver 2>/dev/null)" 1371 | echo "OS VERSION ${macos_ver}${wsl_ver}${win_ver//[$'\r\n']/}" 1372 | echo "################################################################################" 1373 | echo "vbox.log" 1374 | VBoxManage showvminfo "${vm_name}" --log 0 2>&1 1375 | echo "################################################################################" 1376 | echo "vminfo" 1377 | VBoxManage showvminfo "${vm_name}" --machinereadable --details 2>&1 1378 | VBoxManage getextradata "${vm_name}" 2>&1 1379 | done > "${vm_name}_troubleshoot.txt" 1380 | echo "Written configuration and logs to \"${highlight_color}${vm_name}_troubleshoot.txt${default_color}\"" 1381 | echo "Press CTRL-C to cancel checksums, or wait for checksumming to complete." 1382 | for wrapper in 1; do 1383 | echo "################################################################################" 1384 | echo "md5 hashes" 1385 | if [[ -n "$(md5sum --version 2>/dev/null)" ]]; then 1386 | md5sum "${macOS_release_name}_BaseSystem"* 2>/dev/null 1387 | md5sum "${macOS_release_name}_Install"* 2>/dev/null 1388 | md5sum "${macOS_release_name}_Apple"* 2>/dev/null 1389 | else 1390 | md5 "${macOS_release_name}_BaseSystem"* 2>/dev/null 1391 | md5 "${macOS_release_name}_Install"* 2>/dev/null 1392 | md5 "${macOS_release_name}_Apple"* 2>/dev/null 1393 | fi 1394 | echo "################################################################################" 1395 | done >> "${vm_name}_troubleshoot.txt" 1396 | if [ -s "${vm_name}_troubleshoot.txt" ]; then 1397 | echo -ne "\nFinished writing to \"${highlight_color}${vm_name}_troubleshoot.txt${default_color}\"\n" 1398 | fi 1399 | } 1400 | 1401 | # GLOBAL VARIABLES AND FUNCTIONS THAT MIGHT BE CALLED MORE THAN ONCE 1402 | 1403 | # terminal text colors 1404 | warning_color=$'\e[48;2;255;0;0m\e[38;2;255;255;255m' # white on red 1405 | highlight_color=$'\e[48;2;0;0;9m\e[38;2;255;255;255m' # white on black 1406 | low_contrast_color=$'\e[48;2;0;0;9m\e[38;2;128;128;128m' # grey on black 1407 | default_color=$'\033[0m' 1408 | 1409 | # prints positional parameters in low contrast preceded and followed by newline 1410 | function print_dimly() { 1411 | echo -e "\n${low_contrast_color}$@${default_color}" 1412 | } 1413 | 1414 | # don't need sleep when we can read! 1415 | function sleep() { 1416 | read -t "${1}" >/dev/null 2>&1 1417 | } 1418 | 1419 | # create a viso with no files 1420 | function create_viso_header() { 1421 | # input: filename volume-id (two positional parameters, both required) 1422 | # output: nothing to stdout, viso file to working directory 1423 | local uuid="$(xxd -p -l 16 /dev/urandom)" 1424 | local uuid="${uuid:0:8}-${uuid:8:4}-${uuid:12:4}-${uuid:16:4}-${uuid:20:12}" 1425 | echo "--iprt-iso-maker-file-marker-bourne-sh ${uuid} 1426 | --volume-id=${2}" > "${1}" 1427 | } 1428 | 1429 | # QWERTY-to-scancode dictionary. Hex scancodes, keydown and keyup event. 1430 | # Virtualbox Mac scancodes found here: 1431 | # https://wiki.osdev.org/PS/2_Keyboard#Scan_Code_Set_1 1432 | # First half of hex code - press, second half - release, unless otherwise specified 1433 | function declare_scancode_dict() { 1434 | declare -gA kscd 1435 | kscd=( 1436 | ["ESC"]="01 81" 1437 | ["1"]="02 82" 1438 | ["2"]="03 83" 1439 | ["3"]="04 84" 1440 | ["4"]="05 85" 1441 | ["5"]="06 86" 1442 | ["6"]="07 87" 1443 | ["7"]="08 88" 1444 | ["8"]="09 89" 1445 | ["9"]="0A 8A" 1446 | ["0"]="0B 8B" 1447 | ["-"]="0C 8C" 1448 | ["="]="0D 8D" 1449 | ["BKSP"]="0E 8E" 1450 | ["TAB"]="0F 8F" 1451 | ["q"]="10 90" 1452 | ["w"]="11 91" 1453 | ["e"]="12 92" 1454 | ["r"]="13 93" 1455 | ["t"]="14 94" 1456 | ["y"]="15 95" 1457 | ["u"]="16 96" 1458 | ["i"]="17 97" 1459 | ["o"]="18 98" 1460 | ["p"]="19 99" 1461 | ["["]="1A 9A" 1462 | ["]"]="1B 9B" 1463 | ["ENTER"]="1C 9C" 1464 | ["CTRLprs"]="1D" 1465 | ["CTRLrls"]="9D" 1466 | ["a"]="1E 9E" 1467 | ["s"]="1F 9F" 1468 | ["d"]="20 A0" 1469 | ["f"]="21 A1" 1470 | ["g"]="22 A2" 1471 | ["h"]="23 A3" 1472 | ["j"]="24 A4" 1473 | ["k"]="25 A5" 1474 | ["l"]="26 A6" 1475 | [";"]="27 A7" 1476 | ["'"]="28 A8" 1477 | ['`']="29 A9" 1478 | ["LSHIFTprs"]="2A" 1479 | ["LSHIFTrls"]="AA" 1480 | ['\']="2B AB" 1481 | ["z"]="2C AC" 1482 | ["x"]="2D AD" 1483 | ["c"]="2E AE" 1484 | ["v"]="2F AF" 1485 | ["b"]="30 B0" 1486 | ["n"]="31 B1" 1487 | ["m"]="32 B2" 1488 | [","]="33 B3" 1489 | ["."]="34 B4" 1490 | ["/"]="35 B5" 1491 | ["RSHIFTprs"]="36" 1492 | ["RSHIFTrls"]="B6" 1493 | ["ALTprs"]="38" 1494 | ["ALTrls"]="B8" 1495 | ["LALT"]="38 B8" 1496 | ["SPACE"]="39 B9" 1497 | [" "]="39 B9" 1498 | ["CAPS"]="3A BA" 1499 | ["CAPSLOCK"]="3A BA" 1500 | ["F1"]="3B BB" 1501 | ["F2"]="3C BC" 1502 | ["F3"]="3D BD" 1503 | ["F4"]="3E BE" 1504 | ["F5"]="3F BF" 1505 | ["F6"]="40 C0" 1506 | ["F7"]="41 C1" 1507 | ["F8"]="42 C2" 1508 | ["F9"]="43 C3" 1509 | ["F10"]="44 C4" 1510 | ["UP"]="E0 48 E0 C8" 1511 | ["RIGHT"]="E0 4D E0 CD" 1512 | ["LEFT"]="E0 4B E0 CB" 1513 | ["DOWN"]="E0 50 E0 D0" 1514 | ["HOME"]="E0 47 E0 C7" 1515 | ["END"]="E0 4F E0 CF" 1516 | ["PGUP"]="E0 49 E0 C9" 1517 | ["PGDN"]="E0 51 E0 D1" 1518 | ["CMDprs"]="E0 5C" 1519 | ["CMDrls"]="E0 DC" 1520 | # all codes below start with LSHIFTprs as commented in first item: 1521 | ["!"]="2A 02 82 AA" # LSHIFTprs 1prs 1rls LSHIFTrls 1522 | ["@"]="2A 03 83 AA" 1523 | ["#"]="2A 04 84 AA" 1524 | ["$"]="2A 05 85 AA" 1525 | ["%"]="2A 06 86 AA" 1526 | ["^"]="2A 07 87 AA" 1527 | ["&"]="2A 08 88 AA" 1528 | ["*"]="2A 09 89 AA" 1529 | ["("]="2A 0A 8A AA" 1530 | [")"]="2A 0B 8B AA" 1531 | ["_"]="2A 0C 8C AA" 1532 | ["+"]="2A 0D 8D AA" 1533 | ["Q"]="2A 10 90 AA" 1534 | ["W"]="2A 11 91 AA" 1535 | ["E"]="2A 12 92 AA" 1536 | ["R"]="2A 13 93 AA" 1537 | ["T"]="2A 14 94 AA" 1538 | ["Y"]="2A 15 95 AA" 1539 | ["U"]="2A 16 96 AA" 1540 | ["I"]="2A 17 97 AA" 1541 | ["O"]="2A 18 98 AA" 1542 | ["P"]="2A 19 99 AA" 1543 | ["{"]="2A 1A 9A AA" 1544 | ["}"]="2A 1B 9B AA" 1545 | ["A"]="2A 1E 9E AA" 1546 | ["S"]="2A 1F 9F AA" 1547 | ["D"]="2A 20 A0 AA" 1548 | ["F"]="2A 21 A1 AA" 1549 | ["G"]="2A 22 A2 AA" 1550 | ["H"]="2A 23 A3 AA" 1551 | ["J"]="2A 24 A4 AA" 1552 | ["K"]="2A 25 A5 AA" 1553 | ["L"]="2A 26 A6 AA" 1554 | [":"]="2A 27 A7 AA" 1555 | ['"']="2A 28 A8 AA" 1556 | ["~"]="2A 29 A9 AA" 1557 | ["|"]="2A 2B AB AA" 1558 | ["Z"]="2A 2C AC AA" 1559 | ["X"]="2A 2D AD AA" 1560 | ["C"]="2A 2E AE AA" 1561 | ["V"]="2A 2F AF AA" 1562 | ["B"]="2A 30 B0 AA" 1563 | ["N"]="2A 31 B1 AA" 1564 | ["M"]="2A 32 B2 AA" 1565 | ["<"]="2A 33 B3 AA" 1566 | [">"]="2A 34 B4 AA" 1567 | ["?"]="2A 35 B5 AA" 1568 | ) 1569 | } 1570 | 1571 | function clear_input_buffer_then_read() { 1572 | while read -d '' -r -t 0; do read -d '' -t 0.1 -n 10000; break; done 1573 | [[ $- =~ i ]] && read || echo "" 1574 | } 1575 | 1576 | # read variable kbstring and convert string to scancodes and send to guest vm 1577 | function send_keys() { 1578 | # It's faster to send all the scancodes at once, but some VM configurations 1579 | # accept scancodes sent by multiple VBoxManage commands concurrently instead 1580 | # of sequentially, and there's no built-in method to tell the host to wait 1581 | # until the scancodes have finished being entered. 1582 | # This leaves only the slow, keypress-by-keypress method. 1583 | for (( i=0; i < ${#kbstring}; i++ )); do 1584 | VBoxManage controlvm "${vm_name}" keyboardputscancode ${kscd[${kbstring:${i}:1}]} 1>/dev/null 2>&1 1585 | done 1586 | } 1587 | 1588 | # read variable kbspecial and send keystrokes by name, 1589 | # for example "CTRLprs c CTRLrls", and send to guest vm 1590 | function send_special() { 1591 | for keypress in ${kbspecial}; do 1592 | VBoxManage controlvm "${vm_name}" keyboardputscancode ${kscd[${keypress}]} 1>/dev/null 2>&1 1593 | done 1594 | } 1595 | 1596 | function send_enter() { 1597 | kbspecial="ENTER" 1598 | send_special 1599 | } 1600 | 1601 | function prompt_lang_utils_terminal() { 1602 | tesseract_ocr="$(tesseract --version 2>/dev/null)" 1603 | tesseract_lang="$(tesseract --list-langs 2>/dev/null)" 1604 | regex_ver='[Tt]esseract [45]' # for zsh quoted regex compatibility 1605 | if [[ "${tesseract_ocr}" =~ ${regex_ver} && "${tesseract_lang}" =~ eng ]]; then 1606 | echo -e "\n${low_contrast_color}Attempting automated recognition of virtual machine graphical user interface.${default_color}" 1607 | animated_please_wait 30 1608 | for i in $(seq 1 60); do # try automatic ocr for about 5 minutes 1609 | VBoxManage controlvm "${vm_name}" screenshotpng "${vm_name}_screenshot.png" 2>&1 1>/dev/null 1610 | ocr="$(tesseract "${vm_name}_screenshot.png" - --psm 11 --dpi 72 -l eng 2>/dev/null)" 1611 | regex_lang='Language|English|Fran.ais' # for zsh quoted regex compatibility 1612 | regex_term='Terminal.Shell|Terminal.*sh.?-' # for zsh quoted regex compatibility 1613 | if [[ "${ocr}" =~ ${regex_lang} ]]; then 1614 | animated_please_wait 20 1615 | send_enter 1616 | animated_please_wait 20 1617 | elif [[ "${ocr}" =~ Utilities ]]; then 1618 | animated_please_wait 20 1619 | kbspecial='CTRLprs F2 CTRLrls u ENTER t ENTER' # start Terminal 1620 | send_special 1621 | animated_please_wait 20 1622 | elif [[ "${ocr}" =~ ${regex_term} ]]; then 1623 | sleep 2 1624 | return 1625 | fi 1626 | animated_please_wait 10 1627 | done 1628 | echo -e "\nFailed automated recognition of virtual machine graphical user interface.\nPlease press enter as directed." 1629 | fi 1630 | echo -ne "\n${highlight_color}Press enter when the Language window is ready.${default_color}" 1631 | clear_input_buffer_then_read 1632 | send_enter 1633 | echo -ne "\n${highlight_color}Press enter when the macOS Utilities window is ready.${default_color}" 1634 | clear_input_buffer_then_read 1635 | kbspecial='CTRLprs F2 CTRLrls u ENTER t ENTER' # start Terminal 1636 | send_special 1637 | echo -ne "\n${highlight_color}Press enter when the Terminal command prompt is ready.${default_color}" 1638 | clear_input_buffer_then_read 1639 | } 1640 | 1641 | function animated_please_wait() { 1642 | # "Please wait" prompt with animated dots. 1643 | # Accepts one optional positional parameter, an integer 1644 | # The parameter specifies how many half-seconds to wait 1645 | echo -ne "\r \r${low_contrast_color}Please wait${default_color}" 1646 | specified_halfseconds=5 1647 | [[ "${1}" =~ [^0-9] || -z "${1}" ]] || specified_halfseconds=${1} 1648 | for halfsecond in $(seq 1 ${specified_halfseconds}); do 1649 | echo -ne "${low_contrast_color}.${default_color}" 1650 | sleep 0.5 1651 | if [[ $(( halfsecond % 5 )) -eq 0 ]]; then 1652 | echo -ne "\r \r${low_contrast_color}Please wait${default_color}" 1653 | fi 1654 | done 1655 | } 1656 | 1657 | function add_another_terminal() { 1658 | # at least one terminal has to be open before calling this function 1659 | kbspecial='CMDprs n CMDrls' 1660 | send_special 1661 | sleep 1 1662 | } 1663 | 1664 | function cycle_through_terminal_windows() { 1665 | kbspecial='CMDprs ` CMDrls' 1666 | send_special 1667 | sleep 1 1668 | } 1669 | 1670 | function would_you_like_to_know_less() { 1671 | if [[ -z "$(less --version 2>/dev/null)" ]]; then 1672 | echo -e " ${highlight_color}${0} documentation${default_color}" 1673 | else 1674 | echo -e " ${highlight_color}${0} documentation | less -R${default_color}" 1675 | fi 1676 | } 1677 | 1678 | function prompt_delete_y_n() { 1679 | # workaround for zsh-bash differences in read 1680 | delete="" 1681 | if [[ $- =~ i ]]; then # terminal is interactive 1682 | if [[ -n "${ZSH_VERSION}" ]]; then 1683 | read -s -q delete\?' [y/N] ' 1684 | delete="${delete:l}" 1685 | elif [[ -n "${BASH_VERSION}" ]]; then 1686 | read -n 1 -p ' [y/N] ' delete 1687 | delete="${delete,,}" 1688 | fi 1689 | fi 1690 | echo "" 1691 | } 1692 | 1693 | # command-line argument processing 1694 | check_shell 1695 | stages=( 1696 | check_gnu_coreutils_prefix 1697 | set_variables 1698 | welcome 1699 | check_dependencies 1700 | prompt_delete_existing_vm 1701 | create_vm 1702 | check_default_virtual_machine 1703 | prepare_macos_installation_files 1704 | create_nvram_files 1705 | create_macos_installation_files_viso 1706 | configure_vm 1707 | populate_basesystem_virtual_disk 1708 | create_bootable_installer_virtual_disk 1709 | populate_bootable_installer_virtual_disk 1710 | create_target_virtual_disk 1711 | populate_macos_target_disk 1712 | prompt_delete_temporary_files 1713 | ) 1714 | 1715 | other_commands=( 1716 | check_shell 1717 | troubleshoot 1718 | documentation 1719 | and_all_subsequent_stages 1720 | ) 1721 | 1722 | # script called without arguments 1723 | [[ $# -eq 0 ]] && for stage in "${stages[@]}"; do ${stage}; done && exit 1724 | 1725 | # first argument is "documentation" 1726 | [[ "${1}" == "documentation" ]] && documentation && exit 1727 | 1728 | specified_arguments=("$@") # this variable is used in the function "and_all_subsequent_stages" 1729 | sorted_unique_recognized_arguments="$( printf "%s\n" ${stages[@]} ${other_commands[@]} | sort -bu )" 1730 | sorted_unique_recognized_and_specified_arguments="$( printf "%s\n" ${stages[@]} ${other_commands[@]} $@ | sort -bu )" 1731 | # if a specified argument is different from any recognized argument 1732 | if [[ "${sorted_unique_recognized_and_specified_arguments}" != "${sorted_unique_recognized_arguments}" ]]; then 1733 | echo -e "\nOne or more specified arguments is not recognized." 1734 | echo -e "\nRecognized stages:\n" 1735 | printf ' %s\n' "${stages[@]}" 1736 | echo -e "\nOther recognized arguments:\n" 1737 | printf ' %s\n' "${other_commands[@]}" 1738 | echo -e "\nView documentation by entering the following command:" 1739 | would_you_like_to_know_less 1740 | exit 1741 | fi 1742 | check_gnu_coreutils_prefix 1743 | set_variables 1744 | check_dependencies 1745 | for argument; do ${argument}; done 1746 | --------------------------------------------------------------------------------