From 21eaba8ffb69f6513c7c66629ae22ec0775b17ae Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Fri, 25 Aug 2023 14:11:21 +0200 Subject: [PATCH 01/28] Make script executable in git --- createinstalliso | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 createinstalliso diff --git a/createinstalliso b/createinstalliso old mode 100644 new mode 100755 From 4bafc70b86ddd934c210a811d56ba45c2f59868f Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Wed, 21 Feb 2024 21:06:57 +0100 Subject: [PATCH 02/28] Add support for macOS Monterey, Ventura and Sonoma --- README.md | 78 +++++++++++++++++++------ createinstalliso | 144 ++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 185 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index ab4abc7..0418e6a 100644 --- a/README.md +++ b/README.md @@ -44,26 +44,64 @@ To run **createinstalliso** you need: ## Compatibility +### macOS version + +The table below shows: + +* Whether a macOS version can run **createinstalliso**. +* Which installer applications can be used with this particular macOS version. + +| macOS | Version | Can run **createinstalliso** | Can use installer for | +| --------------------- | ------- |:----------------------------:| --------------------- | +| Mac OS X Cheetah | 10.0 | No | - | +| Mac OS X Puma | 10.1 | No | - | +| Mac OS X Jaguar | 10.2 | No | - | +| Mac OS X Panther | 10.3 | No | - | +| Mac OS X Tiger | 10.4 | No | - | +| Mac OS X Leopard | 10.5 | No | - | +| Mac OS X Snow Leopard | 10.6 | Yes | 10.7 - 10.11 | +| Mac OS X Lion | 10.7 | Yes | 10.7 - 10.11 | +| OS X Mountain Lion | 10.8 | Yes | 10.7 - 10.15 | +| OS X Mavericks | 10.9 | Yes | 10.7 - 12 | +| OS X Yosemite | 10.10 | Yes | 10.7 - 12 | +| OS X El Capitan | 10.11 | Yes | 10.7 - 13 | +| macOS Sierra | 10.12 | Yes | 10.7 - 13 | +| macOS High Sierra | 10.13 | Yes | 10.7 - 14 | +| macOS Mojave | 10.14 | Yes | 10.7 - 14 | +| macOS Catalina | 10.15 | Yes | 10.7 - 14 | +| macOS Big Sur | 11 | Yes | 10.7 - 14 | +| macOS Monterey | 12 | Yes | 10.7 - 14 | +| macOS Ventura | 13 | Yes | 10.7 - 14 | +| macOS Sonoma | 14 | Yes | 10.7 - 14 | + +### Installer version + The table below shows: -* If a macOS version can be used to *run* **createinstalliso**. -* If the installer application for a macOS version can be *used* by **createinstalliso** to create an ISO image. - -| Name | Version | Installer can be used | Can run **createinstalliso** | -| --------------------- | ------- |:---------------------:|:----------------------------:| -| Mac OS X Snow Leopard | 10.6 | No | Yes | -| Mac OS X Lion | 10.7 | Yes | Yes | -| OS X Mountain Lion | 10.8 | Yes | Yes | -| OS X Mavericks | 10.9 | Yes | Yes | -| OS X Yosemite | 10.10 | Yes | Yes | -| OS X El Capitan | 10.11 | Yes | Yes | -| macOS Sierra | 10.12 | Yes | Yes | -| macOS High Sierra | 10.13 | Yes | Yes | -| macOS Mojave | 10.14 | Yes | Yes | -| macOS Catalina | 10.15 | Yes | Yes | -| macOS Big Sur | 11 | Yes | Yes | - -**Note:** You can use *any* of the compatible macOS versions to create an ISO image from *any* of the compatible installer applications. +* Which macOS version is required to use **createinstalliso** with this particular installer. + +| Installer for | Version | Required macOS version | +| --------------------- | ------- |:----------------------:| +| Mac OS X Cheetah | 10.0 | - | +| Mac OS X Puma | 10.1 | - | +| Mac OS X Jaguar | 10.2 | - | +| Mac OS X Panther | 10.3 | - | +| Mac OS X Tiger | 10.4 | - | +| Mac OS X Leopard | 10.5 | - | +| Mac OS X Snow Leopard | 10.6 | - | +| Mac OS X Lion | 10.7 | 10.6 or later | +| OS X Mountain Lion | 10.8 | 10.6 or later | +| OS X Mavericks | 10.9 | 10.6 or later | +| OS X Yosemite | 10.10 | 10.6 or later | +| OS X El Capitan | 10.11 | 10.6 or later | +| macOS Sierra | 10.12 | 10.8 or later | +| macOS High Sierra | 10.13 | 10.8 or later | +| macOS Mojave | 10.14 | 10.8 or later | +| macOS Catalina | 10.15 | 10.8 or later | +| macOS Big Sur | 11 | 10.9 or later | +| macOS Monterey | 12 | 10.9 or later | +| macOS Ventura | 13 | 10.11 or later | +| macOS Sonoma | 14 | 10.13 or later | ## Installation @@ -159,6 +197,9 @@ The known installer application types are: | macOS Mojave | 10.14 | 3 | | macOS Catalina | 10.15 | 3 | | macOS Big Sur | 11 | 4 | +| macOS Monterey | 12 | 4 | +| macOS Ventura | 13 | 4 | +| macOS Sonoma | 14 | 4 | ### Required external commands @@ -214,6 +255,7 @@ The table below lists all possible exit status and corresponding messages: | 224 | Failed to convert disk image into bootable install media. | | 223 | Failed to create ISO image. | | 222 | \[FILE\] contains a broken createinstallmedia command. | +| 221 | Couldn't get installer application minimum macOS version. | ## License diff --git a/createinstalliso b/createinstalliso index a48d537..93e033c 100755 --- a/createinstalliso +++ b/createinstalliso @@ -57,6 +57,9 @@ declare -r -a const_supported_macos_version_strings_regex=( '^10\.14(\.[[:digit:]+])?$' # macOS Mojave 10.14 '^10\.15(\.[[:digit:]+])?$' # macOS Catalina 10.15 '^11\.[[:digit:]]+(\.[[:digit:]+])?$' # macOS Big Sur 11 + '^12\.[[:digit:]]+(\.[[:digit:]+])?$' # macOS Monterey 12 + '^13\.[[:digit:]]+(\.[[:digit:]+])?$' # macOS Ventura 13 + '^14\.[[:digit:]]+(\.[[:digit:]+])?$' # macOS Sonoma 14 ) # This script has been tested with the following installer applications. @@ -71,6 +74,9 @@ declare -r -a const_supported_installer_application_display_names=( "Install macOS Mojave" # 10.14 "Install macOS Catalina" # 10.15 "Install macOS Big Sur" # 11 + "Install macOS Monterey" # 12 + "Install macOS Ventura" # 13 + "Install macOS Sonoma" # 14 ) # List of all known macOS version names and their corresponding version @@ -94,6 +100,9 @@ declare -r -a const_known_macos_names_and_version_strings_regex=( '^10\.14.*$ # macOS Mojave' # 10.14 '^10\.15.*$ # macOS Catalina' # 10.15 '^11\..*$ # macOS Big Sur' # 11 + '^12\..*$ # macOS Monterey' # 12 + '^13\..*$ # macOS Ventura' # 13 + '^14\..*$ # macOS Sonoma' # 14 ) # Make sure that macOS Big Sur reports 11 as the macOS version instead @@ -314,31 +323,18 @@ main() { # ------------------------------------------------------------------ local installer_minimum_macos_version_string - local -i installer_minimum_macos_version_for_compare + local -i minimum_macos_version_for_compare if ! is_supported_installer_application_display_name "${installer_application_display_name}"; then print_warning "${installer_application_display_name} has not been tested with this tool." fi - # An installer application type '3' requires OS X Mountain Lion 10.8 - # or higher to run 'createinstallmedia'. - if [[ "${installer_application_type}" == 3 ]]; then - installer_minimum_macos_version_string="10.8" - fi + installer_minimum_macos_version_string="$(get_minimum_macos_version_string "${installer_application_path}" "${installer_application_display_name}")" || exit_with_status 221 "Couldn't get installer application minimum macOS version." + minimum_macos_version_for_compare="$(create_macos_version_for_compare "${installer_minimum_macos_version_string}")" || exit_with_status 247 "Invalid macOS version." - # An installer application type '4' requires OS X Mavericks 10.9 - # or higher to run 'createinstallmedia'. - if [[ "${installer_application_type}" == 4 ]]; then - installer_minimum_macos_version_string="10.9" - fi - - if [[ ("${installer_application_type}" == 3) || ("${installer_application_type}" == 4) ]]; then - installer_minimum_macos_version_for_compare="$(create_macos_version_for_compare "${installer_minimum_macos_version_string}")" || exit_with_status 247 "Invalid macOS version." - - if (( installer_minimum_macos_version_for_compare > macos_version_for_compare )); then - macos_name_and_version="$(create_macos_name_and_version "${installer_minimum_macos_version_string}")" || exit_with_status 247 "Invalid macOS version." - exit_with_status 238 "You need ${macos_name_and_version} or later to create an ISO image from this installer application." - fi + if (( minimum_macos_version_for_compare > macos_version_for_compare )); then + macos_name_and_version="$(create_macos_name_and_version "${installer_minimum_macos_version_string}")" || exit_with_status 247 "Invalid macOS version." + exit_with_status 238 "You need ${macos_name_and_version} or later to create an ISO image from this installer application." fi # ------------------------------------------------------------------ @@ -417,6 +413,9 @@ main() { # Installer application type '4' for: # - macOS Big Sur 11 + # - macOS Monterey 12 + # - macOS Ventura 13 + # - macOS Sonoma 14 4 ) (( required_in_global_tmp_directory = $(get_disk_space_used_by_file_or_directory "${installer_application_path}") )) (( required_in_global_tmp_directory = required_in_global_tmp_directory + 800000000 )) @@ -514,8 +513,14 @@ main() { # - macOS High Sierra 10.13 # - macOS Mojave 10.14 # - macOS Catalina 10.15 + # + # or + # # Installer application type '4' for: # - macOS Big Sur 11 + # - macOS Monterey 12 + # - macOS Ventura 13 + # - macOS Sonoma 14 3 | 4 ) echo "Creating empty disk image..." install_disk_image="${global_tmp_directory}/InstallDisk.sparseimage" @@ -1360,6 +1365,9 @@ is_valid_installer_application_path() { local installer_application_identifier local valid_installer_application_identifier_regex='^com\.apple\.InstallAssistant\.[a-zA-Z0-9.-]+$' + local installer_application_minimum_system_version + local valid_installer_application_minimum_system_version='^[[:digit:]]+\.[[:digit:]]+\.?[[:digit:]]*$' + local system_image_url is_not_empty "${installer_application_path}" || return 1 @@ -1371,6 +1379,18 @@ is_valid_installer_application_path() { installer_application_display_name="$(get_installer_application_display_name "${installer_application_path}")" || return 1 is_not_empty "${installer_application_display_name}" || return 1 + # This file is required because it contains the value for + # 'LSMinimumSystemVersion', which is the minimum system version + # required for the installer application. This version is used in + # case the installer application is not explicitly listed in the + # function 'get_minimum_macos_version_string'. + if is_file "${installer_application_path}/Contents/Info.plist"; then + installer_application_minimum_system_version="$(get_installer_application_minimum_system_version "${installer_application_path}")" || return 1 + [[ "${installer_application_minimum_system_version}" =~ ${valid_installer_application_minimum_system_version} ]] || return 1 + else + return 1 + fi + installer_application_type="$(get_installer_application_type "${installer_application_path}")" || return 1 case "${installer_application_type}" in @@ -1456,6 +1476,9 @@ is_valid_installer_application_path() { # Installer application type '4' for: # - macOS Big Sur 11 + # - macOS Monterey 12 + # - macOS Ventura 13 + # - macOS Sonoma 14 4 ) # Apple's command 'createinstallmedia' is required because # it will be used to create the installer disk image. @@ -1558,6 +1581,9 @@ get_installer_application_display_name() { ## - macOS Catalina 10.15 ## * Installer application type '4' for: ## - macOS Big Sur 11 +## - macOS Monterey 12 +## - macOS Ventura 13 +## - macOS Sonoma 14 ## ## @param $1 The path to an installer application. ## @@ -1593,6 +1619,70 @@ get_installer_application_type() echo "${installer_application_type}" } +## +## @brief Gets the minimum macOS version required to create an ISO +## image from the installer application. +## +## @param $1 The path to an installer application. +## @param $2 The installer application display name. +## +## @return The minimum macOS version string. +## +get_minimum_macos_version_string() { + local -r installer_application_path="$1" + local -r installer_application_display_name="$2" + local minimum_macos_version_string + + case "${installer_application_display_name}" in + + # Installer application for: + # - Mac OS X Lion 10.7 + # - OS X Mountain Lion 10.8 + # - OS X Mavericks 10.9 + # - OS X Yosemite 10.10 + # - OS X El Capitan 10.11 + "Install Mac OS X Lion" | "Install OS X Mountain Lion" | "Install OS X Mavericks" | "Install OS X Yosemite" | "Install OS X El Capitan" ) + minimum_macos_version_string="10.6" + ;; + + # Installer application for: + # - macOS Sierra 10.12 + # - macOS High Sierra 10.13 + # - macOS Mojave 10.14 + # - macOS Catalina 10.15 + "Install macOS Sierra" | "Install macOS High Sierra" | "Install macOS Mojave" | "Install macOS Catalina" ) + minimum_macos_version_string="10.8" + ;; + + # Installer application for: + # - macOS Big Sur 11 + # - macOS Monterey 12 + "Install macOS Big Sur" | "Install macOS Monterey" ) + minimum_macos_version_string="10.9" + ;; + + # Installer application for: + # - macOS Ventura 13 + "Install macOS Ventura" ) + minimum_macos_version_string="10.11" + ;; + + # Installer application for: + # - macOS Sonoma 14 + "Install macOS Sonoma" ) + minimum_macos_version_string="10.13" + ;; + + # Unknown installer application + * ) + minimum_macos_version_string="$(get_installer_application_minimum_system_version "${installer_application_path}")" || return 1 + ;; + + esac + + echo "${minimum_macos_version_string}" +} + ## ## @brief Gets the total non empty bytes of a disk image. ## @@ -1615,6 +1705,22 @@ get_disk_image_total_non_empty_bytes() { get_plist_value ":Size Information:Total Non-Empty Bytes" "${plist_file}" } +## +## @brief Gets the 'minimum system version' of an installer +## application. +## +## @param $1 The path to an installer application. +## +## @return The installer application 'minimum system version'. +## +get_installer_application_minimum_system_version() { + local -r installer_application_path="$1" + + is_not_empty "${installer_application_path}" || return 1 + + get_plist_value ":LSMinimumSystemVersion" "${installer_application_path}/Contents/Info.plist" +} + ## ## @brief Gets the 'short version string' of an installer ## application. From 69e9b2d9c26b5ecdd0e82d78c749a956c2ab280c Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Thu, 22 Feb 2024 16:27:25 +0100 Subject: [PATCH 03/28] Fix regular expressions for known macOS versions --- createinstalliso | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/createinstalliso b/createinstalliso index 93e033c..e0317a3 100755 --- a/createinstalliso +++ b/createinstalliso @@ -83,26 +83,26 @@ declare -r -a const_supported_installer_application_display_names=( # string regex. The regex expression and the macOS version name must # be separated by the '#' character. declare -r -a const_known_macos_names_and_version_strings_regex=( - '^10\.0.*$ # Mac OS X Cheetah' # 10.0 - '^10\.1.*$ # Mac OS X Puma' # 10.1 - '^10\.2.*$ # Mac OS X Jaguar' # 10.2 - '^10\.3.*$ # Mac OS X Panther' # 10.3 - '^10\.4.*$ # Mac OS X Tiger' # 10.4 - '^10\.5.*$ # Mac OS X Leopard' # 10.5 - '^10\.6.*$ # Mac OS X Snow Leopard' # 10.6 - '^10\.7.*$ # Mac OS X Lion' # 10.7 - '^10\.8.*$ # OS X Mountain Lion' # 10.8 - '^10\.9.*$ # OS X Mavericks' # 10.9 - '^10\.10.*$ # OS X Yosemite' # 10.10 - '^10\.11.*$ # OS X El Capitan' # 10.11 - '^10\.12.*$ # macOS Sierra' # 10.12 - '^10\.13.*$ # macOS High Sierra' # 10.13 - '^10\.14.*$ # macOS Mojave' # 10.14 - '^10\.15.*$ # macOS Catalina' # 10.15 - '^11\..*$ # macOS Big Sur' # 11 - '^12\..*$ # macOS Monterey' # 12 - '^13\..*$ # macOS Ventura' # 13 - '^14\..*$ # macOS Sonoma' # 14 + '^10\.0(\.[[:digit:]+])?$ # Mac OS X Cheetah' # 10.0 + '^10\.1(\.[[:digit:]+])?$ # Mac OS X Puma' # 10.1 + '^10\.2(\.[[:digit:]+])?$ # Mac OS X Jaguar' # 10.2 + '^10\.3(\.[[:digit:]+])?$ # Mac OS X Panther' # 10.3 + '^10\.4(\.[[:digit:]+])?$ # Mac OS X Tiger' # 10.4 + '^10\.5(\.[[:digit:]+])?$ # Mac OS X Leopard' # 10.5 + '^10\.6(\.[[:digit:]+])?$ # Mac OS X Snow Leopard' # 10.6 + '^10\.7(\.[[:digit:]+])?$ # Mac OS X Lion' # 10.7 + '^10\.8(\.[[:digit:]+])?$ # OS X Mountain Lion' # 10.8 + '^10\.9(\.[[:digit:]+])?$ # OS X Mavericks' # 10.9 + '^10\.10(\.[[:digit:]+])?$ # OS X Yosemite' # 10.10 + '^10\.11(\.[[:digit:]+])?$ # OS X El Capitan' # 10.11 + '^10\.12(\.[[:digit:]+])?$ # macOS Sierra' # 10.12 + '^10\.13(\.[[:digit:]+])?$ # macOS High Sierra' # 10.13 + '^10\.14(\.[[:digit:]+])?$ # macOS Mojave' # 10.14 + '^10\.15(\.[[:digit:]+])?$ # macOS Catalina' # 10.15 + '^11\.[[:digit:]]+(\.[[:digit:]+])?$ # macOS Big Sur' # 11 + '^12\.[[:digit:]]+(\.[[:digit:]+])?$ # macOS Monterey' # 12 + '^13\.[[:digit:]]+(\.[[:digit:]+])?$ # macOS Ventura' # 13 + '^14\.[[:digit:]]+(\.[[:digit:]+])?$ # macOS Sonoma' # 14 ) # Make sure that macOS Big Sur reports 11 as the macOS version instead From 972c70385d41ef6ed321b79819350ace8443b0f2 Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Thu, 22 Feb 2024 16:49:54 +0100 Subject: [PATCH 04/28] Use '-eq' operator to compare integer values --- createinstalliso | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/createinstalliso b/createinstalliso index e0317a3..a0d5bb6 100755 --- a/createinstalliso +++ b/createinstalliso @@ -1988,7 +1988,7 @@ parse_create_install_media() { script_createinstallmedia_canceled_by_sigint() { local -r -i exit_status="$1" - [[ "${exit_status}" == 2 ]] + [[ "${exit_status}" -eq 2 ]] } ## @@ -2005,7 +2005,7 @@ script_createinstallmedia_canceled_by_sigint() { script_createinstallmedia_canceled_by_sigquit() { local -r -i exit_status="$1" - [[ "${exit_status}" == 3 ]] + [[ "${exit_status}" -eq 3 ]] } ## @@ -2202,7 +2202,7 @@ script_hdiutil_makehybrid_canceled_by_sigint() { script_hdiutil_makehybrid_canceled_by_sigquit() { local -r -i exit_status="$1" - [[ "${exit_status}" == 3 ]] + [[ "${exit_status}" -eq 3 ]] } ## From 6eb31e2f775ac5813e28e9b0e1fb5e664fb90977 Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Fri, 1 Mar 2024 21:02:12 +0100 Subject: [PATCH 05/28] Adapt check for sufficient disk space --- README.md | 2 +- createinstalliso | 184 ++++++++++++++++++++++++++++++++--------------- 2 files changed, 127 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 0418e6a..5bd8736 100644 --- a/README.md +++ b/README.md @@ -203,7 +203,7 @@ The known installer application types are: ### Required external commands -**createinstalliso** uses a number of external commands, which must be available on your system: `awk`, `bless`, `cp`, `df`, `du`, `hdiutil`, `mktemp`, `ps`, `pwd`, `rm`, `script`, `seq`, `stat`, `sw_vers`, `tput`, `uname` and `/usr/libexec/PlistBuddy`. +**createinstalliso** uses a number of external commands, which must be available on your system: `awk`, `bless`, `cp`, `cut`, `df`, `du`, `hdiutil`, `mktemp`, `ps`, `pwd`, `rm`, `script`, `seq`, `stat`, `sw_vers`, `tput`, `uname` and `/usr/libexec/PlistBuddy`. Unless you have deliberately modified your system, all of the above commands are available on the macOS versions listed in the section "[Compatibility](#user-content-compatibility)". diff --git a/createinstalliso b/createinstalliso index a0d5bb6..75758b9 100755 --- a/createinstalliso +++ b/createinstalliso @@ -30,10 +30,12 @@ declare -r const_script_name="createinstalliso" declare -r const_minimum_required_macos_version_string="10.6" declare -r -i const_root_uid=0 -# This script requires /usr/libexec/PlistBuddy to read values from +# List of unsupported macOS versions. +# +# NOTE: This script requires /usr/libexec/PlistBuddy to read values from # property list files. PlistBuddy is available on Mac OS X 10.5 Leopard -# or later. However on Mac OS X 10.5 Leopard PlistBuddy does not handle -# large integer values correctly. Therefore, this script requires +# or later. However, on Mac OS X 10.5 Leopard, PlistBuddy does not +# handle large integer values correctly. Therefore, this script requires # Mac OS X 10.6 Snow Leopard or later. declare -r -a const_unsupported_macos_version_strings_regex=( '^10\.0(\.[[:digit:]+])?$' # Mac OS X Cheetah 10.0 @@ -44,7 +46,7 @@ declare -r -a const_unsupported_macos_version_strings_regex=( '^10\.5(\.[[:digit:]+])?$' # Mac OS X Leopard 10.5 ) -# This script has been tested on the following macOS versions. +# List of all supported macOS versions. declare -r -a const_supported_macos_version_strings_regex=( '^10\.6(\.[[:digit:]+])?$' # Mac OS X Snow Leopard 10.6 '^10\.7(\.[[:digit:]+])?$' # Mac OS X Lion 10.7 @@ -62,21 +64,26 @@ declare -r -a const_supported_macos_version_strings_regex=( '^14\.[[:digit:]]+(\.[[:digit:]+])?$' # macOS Sonoma 14 ) -# This script has been tested with the following installer applications. -declare -r -a const_supported_installer_application_display_names=( - "Install Mac OS X Lion" # 10.7 - "Install OS X Mountain Lion" # 10.8 - "Install OS X Mavericks" # 10.9 - "Install OS X Yosemite" # 10.10 - "Install OS X El Capitan" # 10.11 - "Install macOS Sierra" # 10.12 - "Install macOS High Sierra" # 10.13 - "Install macOS Mojave" # 10.14 - "Install macOS Catalina" # 10.15 - "Install macOS Big Sur" # 11 - "Install macOS Monterey" # 12 - "Install macOS Ventura" # 13 - "Install macOS Sonoma" # 14 +# List of all supported installer applications and their disk space +# offset requirements for the installer application types '3' and '4' +# (see 'get_offset_for_global_tmp_directory' and +# 'get_offset_for_iso_directory_path'). The disk space offset +# requirements must each be separated by the '#' character each. The +# disk space offset requirements are specified in millions of bytes. +declare -r -a const_supported_installer_application_display_names_and_offsets=( + 'Install Mac OS X Lion # # ' # 10.7 + 'Install OS X Mountain Lion # # ' # 10.8 + 'Install OS X Mavericks # # ' # 10.9 + 'Install OS X Yosemite # # ' # 10.10 + 'Install OS X El Capitan # # ' # 10.11 + 'Install macOS Sierra # 100 # 100' # 10.12 + 'Install macOS High Sierra # 100 # 100' # 10.13 + 'Install macOS Mojave # 100 # 100' # 10.14 + 'Install macOS Catalina # 100 # 100' # 10.15 + 'Install macOS Big Sur # 800 # 800' # 11 + 'Install macOS Monterey # 1900 # 1300' # 12 + 'Install macOS Ventura # 1450 # 1300' # 13 + 'Install macOS Sonoma # 2100 # 1350' # 14 ) # List of all known macOS version names and their corresponding version @@ -370,6 +377,8 @@ main() { local -i available_in_iso_directory_path local -i required_in_global_tmp_directory local -i required_in_iso_directory_path + local -i offset_for_global_tmp_directory + local -i offset_for_iso_directory_path local -i missing_in_global_tmp_directory local -i missing_in_iso_directory_path @@ -404,23 +413,24 @@ main() { # - macOS High Sierra 10.13 # - macOS Mojave 10.14 # - macOS Catalina 10.15 - 3 ) - (( required_in_global_tmp_directory = $(get_disk_space_used_by_file_or_directory "${installer_application_path}") )) - (( required_in_global_tmp_directory = required_in_global_tmp_directory + 100000000 )) - - (( required_in_iso_directory_path = required_in_global_tmp_directory )) - ;; - + # + # or + # # Installer application type '4' for: # - macOS Big Sur 11 # - macOS Monterey 12 # - macOS Ventura 13 # - macOS Sonoma 14 - 4 ) + 3 | 4 ) (( required_in_global_tmp_directory = $(get_disk_space_used_by_file_or_directory "${installer_application_path}") )) - (( required_in_global_tmp_directory = required_in_global_tmp_directory + 800000000 )) + offset_for_global_tmp_directory="$(get_offset_for_global_tmp_directory "${installer_application_display_name}")" || offset_for_global_tmp_directory=0 + (( offset_for_global_tmp_directory = offset_for_global_tmp_directory * 1000000 )) + (( required_in_global_tmp_directory = required_in_global_tmp_directory + offset_for_global_tmp_directory )) - (( required_in_iso_directory_path = required_in_global_tmp_directory )) + (( required_in_iso_directory_path = $(get_disk_space_used_by_file_or_directory "${installer_application_path}") )) + offset_for_iso_directory_path="$(get_offset_for_iso_directory_path "${installer_application_display_name}")" || offset_for_iso_directory_path=0 + (( offset_for_iso_directory_path = offset_for_iso_directory_path * 1000000 )) + (( required_in_iso_directory_path = required_in_iso_directory_path + offset_for_iso_directory_path )) ;; esac @@ -752,30 +762,6 @@ is_not_empty() { [[ -n "${string}" ]] } -## -## @brief Determines whether an item is contained in an array. The -## array is passed to the function by reference via its -## name. -## -## @param $1 An item. -## @param $2 The name of the array as string. -## -## @retval `true` The item is contained in the array. -## @retval `false` Otherwise. -## -is_array_item() { - local -r item="$1" - local -r array_name="$2[@]" - local current_item - - for current_item in "${!array_name}"; do - if [[ "${item}" == "${current_item}" ]]; then - return 0 - fi - done - return 1 -} - ## ## @brief Determines whether an item matches a regex expression in ## an array. The array is passed to the function by @@ -829,6 +815,51 @@ get_absolute_logical_path() { return 0 } +## +## @brief Gets the item in a string array starting with the +## specified string. The array is passed to the function by +## reference via its name. +## +## @param $1 The string. +## @param $2 The name of the array as string. +## +## @return The item starting with the string. +## +get_item_starting_with_string() { + local -r string="$1" + local -r array_name="$2[@]" + local current_item + + for current_item in "${!array_name}"; do + if [[ "${current_item}" == "${string}"* ]]; then + echo "${current_item}" + return 0 + fi + done + return 1 +} + +## +## @brief Gets the content of the field specified by the field +## number from a string containing fields delimited by the +## '#' character. The content of the field is trimmed to +## remove leading and trailing whitespace. +## +## @param $1 The string. +## @param $2 The field number starting from number 1. +## +## @return The trimmed field content. +## +get_trimmed_field_content() { + local -r string="$1" + local -r -i field_number="$2" + local field + + field="$(cut -d# -f"${field_number}" <<<"${string}")" || return 1 + + trim "${field}" +} + ## ## @brief Asks the user for consent on the command line. Any user ## input starting with 'y' or 'Y' will be treated as @@ -1100,13 +1131,18 @@ print_error_usage() { ## output to the user (e.g. 'Install macOS Sierra.app'). ## create_example_installer_application_name() { - local -r -i number_of_elements="${#const_supported_installer_application_display_names[@]}" - local example_installer_application_name="${const_supported_installer_application_display_names[${number_of_elements}-1]}.app" + local -r -i number_of_elements="${#const_supported_installer_application_display_names_and_offsets[@]}" + local item="${const_supported_installer_application_display_names_and_offsets[${number_of_elements}-1]}" + local example_installer_application_name="$(get_trimmed_field_content "${item}" 1).app" local -i i + local installer_application_name for (( i = number_of_elements - 1; i >= 0; i-- )); do - if is_valid_installer_application_path "/Applications/${const_supported_installer_application_display_names[${i}]}.app"; then - example_installer_application_name="${const_supported_installer_application_display_names[${i}]}.app" + item="${const_supported_installer_application_display_names_and_offsets[${i}]}" + installer_application_name="$(get_trimmed_field_content "${item}" 1).app" + + if is_valid_installer_application_path "/Applications/${installer_application_name}"; then + example_installer_application_name="${installer_application_name}" break fi done @@ -1539,7 +1575,7 @@ is_invalid_macos_sierra_installer_application() { is_supported_installer_application_display_name() { local -r installer_application_display_name="$1" - is_array_item "${installer_application_display_name}" "const_supported_installer_application_display_names" + get_item_starting_with_string "${installer_application_display_name}" "const_supported_installer_application_display_names_and_offsets" >/dev/null } ## @@ -1705,6 +1741,38 @@ get_disk_image_total_non_empty_bytes() { get_plist_value ":Size Information:Total Non-Empty Bytes" "${plist_file}" } +## +## @brief Gets the offset for the required disk space on the volume +## where the global temporary directory is located. +## +## @param $1 The installer application display name. +## +## @return The offset in bytes. +## +get_offset_for_global_tmp_directory() { + local -r installer_application_display_name="$1" + local item + + item="$(get_item_starting_with_string "${installer_application_display_name}" "const_supported_installer_application_display_names_and_offsets")" || return 1 + get_trimmed_field_content "${item}" 2 +} + +## +## @brief Gets the offset for the required disk space on the volume +## where the ISO image file will be created. +## +## @param $1 The installer application display name. +## +## @return The offset in bytes. +## +get_offset_for_iso_directory_path() { + local -r installer_application_display_name="$1" + local item + + item="$(get_item_starting_with_string "${installer_application_display_name}" "const_supported_installer_application_display_names_and_offsets")" || return 1 + get_trimmed_field_content "${item}" 3 +} + ## ## @brief Gets the 'minimum system version' of an installer ## application. From b85627807e6ae84df1d2e3680cee52c1e6d73728 Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Sun, 10 Mar 2024 10:31:01 +0100 Subject: [PATCH 06/28] Remove '--openfolder' option from 'bless' command This option is no longer available on Mac computers with Apple silicon and regardless, removing this option has no effect on the final ISO image. --- createinstalliso | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/createinstalliso b/createinstalliso index 75758b9..2f9bc19 100755 --- a/createinstalliso +++ b/createinstalliso @@ -503,7 +503,7 @@ main() { echo "Making disk image bootable..." core_services_directory="${global_install_disk_mountpoint}/System/Library/CoreServices" - bless --folder "${core_services_directory}" --file "${core_services_directory}/boot.efi" --openfolder "${global_install_disk_mountpoint}" --label "${installer_application_display_name}" >/dev/null || exit_with_status 229 "The bless of the installer disk image failed." + bless --folder "${core_services_directory}" --file "${core_services_directory}/boot.efi" --label "${installer_application_display_name}" >/dev/null || exit_with_status 229 "The bless of the installer disk image failed." echo "Copying installer image..." cp -P -p "${base_system_image}" "${global_install_disk_mountpoint}/" >/dev/null || exit_with_status 228 "Failed to copy BaseSystem.dmg." From 6015b7346e235626d10bd7e0aa26bfc5fb46ceab Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Mon, 11 Mar 2024 21:04:57 +0100 Subject: [PATCH 07/28] Remove unnecessary '-shadow' option The image is attached with the '-readonly' option, therefore a shadow file is not required. --- createinstalliso | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/createinstalliso b/createinstalliso index 2f9bc19..0d8c22a 100755 --- a/createinstalliso +++ b/createinstalliso @@ -482,9 +482,8 @@ main() { # - OS X El Capitan 10.11 2 ) install_esd_image="${installer_application_path}/Contents/SharedSupport/InstallESD.dmg" - install_esd_shadow="${global_tmp_directory}/InstallESD.shadow" global_install_esd_mountpoint="${global_tmp_directory}/InstallESD" - hdiutil attach "${install_esd_image}" -shadow "${install_esd_shadow}" -readonly -mountpoint "${global_install_esd_mountpoint}" -nobrowse -noverify >/dev/null || exit_with_status 236 "Mount of outer dmg failed." + hdiutil attach "${install_esd_image}" -readonly -mountpoint "${global_install_esd_mountpoint}" -nobrowse -noverify >/dev/null || exit_with_status 236 "Mount of outer dmg failed." base_system_image="${global_install_esd_mountpoint}/BaseSystem.dmg" is_file "${base_system_image}" || exit_with_status 235 "BaseSystem missing from the outer dmg, damaged installer image?" From a0b665f57dc6fe1a0ce15b874c3a00e0336990db Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Sat, 23 Mar 2024 11:28:51 +0100 Subject: [PATCH 08/28] Increase virtual image size of SPARSE images --- createinstalliso | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/createinstalliso b/createinstalliso index 0d8c22a..2349114 100755 --- a/createinstalliso +++ b/createinstalliso @@ -493,7 +493,7 @@ main() { echo "Creating disk image from installer image..." hdiutil convert "${base_system_image}" -format UDSP -o "${install_disk_image}" -pmap >/dev/null || exit_with_status 234 "Failed to convert BaseSystem.dmg." - hdiutil resize -size 16g "${install_disk_image}" >/dev/null || exit_with_status 233 "Failed to resize disk image." + hdiutil resize -size 32g "${install_disk_image}" >/dev/null || exit_with_status 233 "Failed to resize disk image." hdiutil attach "${install_disk_image}" -readwrite -mountpoint "${global_install_disk_mountpoint}" -nobrowse -noverify >/dev/null || exit_with_status 232 "The disk image did not mount." echo "Copying installer files to disk image..." @@ -534,7 +534,7 @@ main() { echo "Creating empty disk image..." install_disk_image="${global_tmp_directory}/InstallDisk.sparseimage" install_disk_volname="InstallMedia.${random_string}" - hdiutil create -size 16g "${install_disk_image}" -type SPARSE -fs HFS+J -volname "${install_disk_volname}" >/dev/null || exit_with_status 225 "Failed to create disk image." + hdiutil create -size 32g "${install_disk_image}" -type SPARSE -fs HFS+J -volname "${install_disk_volname}" >/dev/null || exit_with_status 225 "Failed to create disk image." global_install_disk_device_node="$(hdiutil attach "${install_disk_image}" -readwrite -nobrowse -noverify 2>/dev/null | (IFS=$' \t\n' read -r device_node remainder; echo "${device_node}"))" || exit_with_status 232 "The disk image did not mount." create_install_media "${installer_application_path}" "${install_disk_volname}" || exit_with_status 224 "Failed to convert disk image into bootable install media." From bfd285f7471cb78631c25b7a6a4ec2c23bfe08df Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Sat, 23 Mar 2024 11:47:28 +0100 Subject: [PATCH 09/28] Simplify some 'printf' statements --- createinstalliso | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/createinstalliso b/createinstalliso index 2349114..773a44e 100755 --- a/createinstalliso +++ b/createinstalliso @@ -1961,13 +1961,13 @@ parse_create_install_media() { 'Done.' 'Install media now available at ' ) - local -r trigger_text="$(printf "%s" "${separator}"; printf "%s${separator}" "${trigger_text_array[@]}")" + local -r trigger_text="$(printf "${separator}"; printf "%s${separator}" "${trigger_text_array[@]}")" local -r -a trigger_line_mute_array=( 'Copy complete.' 'Done.' 'Install media now available at ' ) - local -r trigger_line_mute="$(printf "%s" "${separator}"; printf "%s${separator}" "${trigger_line_mute_array[@]}")" + local -r trigger_line_mute="$(printf "${separator}"; printf "%s${separator}" "${trigger_line_mute_array[@]}")" local -r trigger_newline_mute_array=( '100%...' ' 100%' @@ -1977,12 +1977,12 @@ parse_create_install_media() { 'Copying installer files to disk...' 'Copying to disk: 0%...' ) - local -r trigger_copying_line="$(printf "%s" "${separator}"; printf "%s${separator}" "${trigger_copying_line_array[@]}")" - local -r trigger_newline_mute="$(printf "%s" "${separator}"; printf "%s${separator}" "${trigger_newline_mute_array[@]}")" + local -r trigger_copying_line="$(printf "${separator}"; printf "%s${separator}" "${trigger_copying_line_array[@]}")" + local -r trigger_newline_mute="$(printf "${separator}"; printf "%s${separator}" "${trigger_newline_mute_array[@]}")" local -r trigger_making_line_array=( 'Making disk bootable...' ) - local -r trigger_making_line="$(printf "%s" "${separator}"; printf "%s${separator}" "${trigger_making_line_array[@]}")" + local -r trigger_making_line="$(printf "${separator}"; printf "%s${separator}" "${trigger_making_line_array[@]}")" local is_line_mute="false" local is_newline_mute="false" @@ -2125,20 +2125,20 @@ parse_create_iso_image() { 'hdiutil: ' 'canceling...' ) - local -r trigger_text="$(printf "%s" "${separator}"; printf "%s${separator}" "${trigger_text_array[@]}")" + local -r trigger_text="$(printf "${separator}"; printf "%s${separator}" "${trigger_text_array[@]}")" local -r -a trigger_line_mute_array=( 'Creating hybrid image...' 'hdiutil: ' ) - local -r trigger_line_mute="$(printf "%s" "${separator}"; printf "%s${separator}" "${trigger_line_mute_array[@]}")" + local -r trigger_line_mute="$(printf "${separator}"; printf "%s${separator}" "${trigger_line_mute_array[@]}")" local -r -a trigger_progress_line_array=( 'Creating hybrid image...' ) - local -r trigger_progress_line="$(printf "%s" "${separator}"; printf "%s${separator}" "${trigger_progress_line_array[@]}")" + local -r trigger_progress_line="$(printf "${separator}"; printf "%s${separator}" "${trigger_progress_line_array[@]}")" local -r -a trigger_mute_array=( 'canceling...' ) - local -r trigger_mute="$(printf "%s" "${separator}"; printf "%s${separator}" "${trigger_mute_array[@]}")" + local -r trigger_mute="$(printf "${separator}"; printf "%s${separator}" "${trigger_mute_array[@]}")" local is_line_mute="false" local is_mute="false" local is_progress_line="false" From 6aaa8dbf5f53291b7143fedda9b3a970446253ac Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Sun, 21 Apr 2024 18:39:16 +0200 Subject: [PATCH 10/28] Update and improve README.md --- README.md | 133 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 74 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 5bd8736..4263355 100644 --- a/README.md +++ b/README.md @@ -9,38 +9,38 @@ Creates a bootable ISO image from a macOS installer application. This image can 3. [Compatibility](#user-content-compatibility) 4. [Installation](#user-content-installation) 5. [Usage](#user-content-usage) - 1. [Example for the macOS Catalina installer](#user-content-example-for-the-macos-catalina-installer) - 2. [Command Line Arguments](#user-content-command-line-arguments) + 1. [Command line arguments](#user-content-command-line-arguments) + 2. [Example for the macOS Catalina installer](#user-content-example-for-the-macos-catalina-installer) 6. [Troubleshooting](#user-content-troubleshooting) - 1. [Alert: This copy of the Install ... can't ...](#user-content-alert-this-copy-of-the-install--cant-) + 1. [Alert during macOS installation from ISO image](#user-content-alert-during-macos-installation-from-iso-image) 2. [Error message: ... contains a broken createinstallmedia command.](#user-content-error-message--contains-a-broken-createinstallmedia-command) 7. [References](#user-content-references) - 1. [Installer Application Types](#user-content-installer-application-types) + 1. [Installer application types](#user-content-installer-application-types) 2. [Required external commands](#user-content-required-external-commands) 3. [Exit status and messages](#user-content-exit-status-and-messages) 8. [License](#user-content-license) ## Introduction -If you have ever needed to install macOS on a new computer, you have probably discovered the ability to create a bootable USB flash drive or other volume by using the `createinstallmedia` command included in the downloaded macOS installer: [How to create a bootable installer for macOS](https://support.apple.com/en-us/HT201372). +If you have ever needed to install macOS on a new computer, you have probably discovered the ability to create a bootable USB flash drive or other volume by using the `createinstallmedia` command included in the downloaded macOS installer: [Create a bootable installer](https://support.apple.com/en-us/101578). -In case you need a **bootable ISO image** instead, you can find a lot of shell scripts in forums that create such an ISO image for certain macOS versions. However, they are often only written for a single macOS version and cannot adapt to the specific macOS version contained in the installation program. +In case you need a **bootable ISO image** instead, you can find a lot of shell scripts in forums that create such an ISO image for certain macOS versions. However, they are often only written for a single macOS version and cannot adapt to the specific macOS version contained in the installer. -The purpose of this program **createinstalliso** is to create a bootable ISO image from a downloaded macOS installer application. In addition, the program should: +The purpose of this program **createinstalliso** is to create a bootable ISO image from a downloaded full macOS installer. In addition, the program should: * Automatically adapt the strategy used to create the ISO image to the installer application type, thereby supporting a wide range of macOS versions (see: [Compatibility](#user-content-compatibility)). * Look and feel like Apple's `createinstallmedia` command (e.g. handling of command line arguments, wording for error and progress messages, status codes used when exiting). -* Behaving like a standard command line program, even though it is a Bash script (e.g. Ctrl-C interruptibility, proper cleanup of resources before exiting). +* Behaving like a standard command line program (e.g. Ctrl-C interruptibility, proper cleanup of resources before exiting), even though it is a Bash script. ## Requirements To run **createinstalliso** you need: * A machine running Mac OS X Snow Leopard 10.6 or later. -* An administrator account that has a password to execute a `sudo` command. -* A downloaded full size macOS installer application for Mac OS X Lion 10.7 or later (see: [Compatibility](#user-content-compatibility)). +* An administrator user and password required for the use of the `sudo` command. +* A downloaded full macOS installer for Mac OS X Lion 10.7 or later. -**Note:** Make sure that you have downloaded a *full size* macOS installer application which should have a size of at least 5 GB. +**Note:** Make sure that you have downloaded a *full* macOS installer which should have a size of at least 5 GB. ## Compatibility @@ -49,16 +49,16 @@ To run **createinstalliso** you need: The table below shows: * Whether a macOS version can run **createinstalliso**. -* Which installer applications can be used with this particular macOS version. +* Which macOS installers can be used to create an ISO image with this particular macOS version. | macOS | Version | Can run **createinstalliso** | Can use installer for | | --------------------- | ------- |:----------------------------:| --------------------- | -| Mac OS X Cheetah | 10.0 | No | - | -| Mac OS X Puma | 10.1 | No | - | -| Mac OS X Jaguar | 10.2 | No | - | -| Mac OS X Panther | 10.3 | No | - | -| Mac OS X Tiger | 10.4 | No | - | -| Mac OS X Leopard | 10.5 | No | - | +| Mac OS X Cheetah | 10.0 | No | N/A | +| Mac OS X Puma | 10.1 | No | N/A | +| Mac OS X Jaguar | 10.2 | No | N/A | +| Mac OS X Panther | 10.3 | No | N/A | +| Mac OS X Tiger | 10.4 | No | N/A | +| Mac OS X Leopard | 10.5 | No | N/A | | Mac OS X Snow Leopard | 10.6 | Yes | 10.7 - 10.11 | | Mac OS X Lion | 10.7 | Yes | 10.7 - 10.11 | | OS X Mountain Lion | 10.8 | Yes | 10.7 - 10.15 | @@ -78,17 +78,17 @@ The table below shows: The table below shows: -* Which macOS version is required to use **createinstalliso** with this particular installer. +* Which macOS version is required to use **createinstalliso** with a particular macOS installer. | Installer for | Version | Required macOS version | | --------------------- | ------- |:----------------------:| -| Mac OS X Cheetah | 10.0 | - | -| Mac OS X Puma | 10.1 | - | -| Mac OS X Jaguar | 10.2 | - | -| Mac OS X Panther | 10.3 | - | -| Mac OS X Tiger | 10.4 | - | -| Mac OS X Leopard | 10.5 | - | -| Mac OS X Snow Leopard | 10.6 | - | +| Mac OS X Cheetah | 10.0 | N/A | +| Mac OS X Puma | 10.1 | N/A | +| Mac OS X Jaguar | 10.2 | N/A | +| Mac OS X Panther | 10.3 | N/A | +| Mac OS X Tiger | 10.4 | N/A | +| Mac OS X Leopard | 10.5 | N/A | +| Mac OS X Snow Leopard | 10.6 | N/A | | Mac OS X Lion | 10.7 | 10.6 or later | | OS X Mountain Lion | 10.8 | 10.6 or later | | OS X Mavericks | 10.9 | 10.6 or later | @@ -105,21 +105,29 @@ The table below shows: ## Installation -* Open Terminal, which is in the Utilities folder of your Applications folder and type or paste the following commands in Terminal. +Open Terminal, which is in the Utilities folder of your Applications folder, and type or paste the following commands into Terminal: + * Download the **createinstalliso** file. + ``` curl -fOSs https://raw.githubusercontent.com/BITespresso/createinstalliso/master/createinstalliso ``` + * Make the downloaded script executable. + ``` chmod +x createinstalliso ``` -* A good practice is to keep user shell scripts in the `bin` folder in your home directory. If it does not already exist, create it and move the script into this folder. + +* A good practice is to keep your shell scripts in the `bin` folder in your home directory. If it does not already exist, create it and move the script to this folder. + ``` mkdir -p ~/bin mv createinstalliso ~/bin/ ``` + * You might also want to hide the `bin` folder from the Finder. + ``` chflags hidden ~/bin ``` @@ -128,62 +136,69 @@ The table below shows: ## Usage -### Example for the macOS Catalina installer +Just like Apple's `createinstallmedia` command **createinstalliso** must be run as root user. Depending on where you saved the script, the command will look like: -* Open Terminal, which is in the Utilities folder of your Applications folder. -* Type or paste the following command in Terminal. This assumes that the installer is still in your Applications folder, and you have moved **createinstalliso** to the `~/bin` folder. - ``` - sudo ~/bin/createinstalliso --isodirectory ~/Desktop/ --applicationpath /Applications/Install\ macOS\ Catalina.app/ - ``` -* Press Return after typing the command. -* When prompted, type your administrator password and press Return again. Terminal doesn't show any characters as you type your password. -* Terminal shows the progress as the bootable installer is created. -* When Terminal says that it's done, you will find the bootable ISO image file `Install macOS Catalina.iso` in the folder you specified (here: `~/Desktop`). -* You can now quit Terminal. +``` +sudo ~/bin/createinstalliso --isodirectory --applicationpath [--nointeraction] +``` -### Command Line Arguments +### Command line arguments The command **createinstalliso** requires the following two command line arguments: * `--isodirectory` (or `-i`) must be followed by a path to a directory where the installer ISO image file will be created. -* `--applicationpath` (or `-a`) must be followed by the path to the OS installer application that should be used to create the bootable ISO image. +* `--applicationpath` (or `-a`) must be followed by a path to a copy of the OS installer application to create the bootable ISO image from. -The name of the ISO image to be created is made up of the name of the installer application and has the extension `iso` (e.g. `Install macOS Catalina.iso`). If such a file already exists in the specified destination directory, the user will be prompted for confirmation before this file is overwritten. If you do not care about overwriting an existing file and do not want to be prompted, you can add the command line option: +The name of the ISO image to be created is made up of the name of the installer and has the extension `iso` (e.g. `Install macOS Catalina.iso`). If such a file already exists in the specified destination directory, you will be prompted for confirmation before this file is overwritten. If you do not care about overwriting an existing file and do not want to be prompted, you can add the command line option: * `--nointeraction` (or `-n`) which causes an existing ISO image file to **always** be overwritten. -## Troubleshooting +### Example for the macOS Catalina installer + +The following example show the steps required to create an ISO image from a macOS Catalina installer: -### Alert: This copy of the Install ... can't ... +* Open Terminal, which is in the Utilities folder of your Applications folder. +* Type or paste the following command into Terminal, then press Return to enter the command. This assumes that the installer is still in your Applications folder, and you have moved **createinstalliso** to the `~/bin` folder. + + ``` + sudo ~/bin/createinstalliso --isodirectory ~/Desktop/ --applicationpath /Applications/Install\ macOS\ Catalina.app/ + ``` + +* When prompted, type your administrator password. Terminal doesn't show any characters as you type. Then press Return. +* Terminal shows the progress as the bootable ISO image is created. +* When Terminal says that it's done, you will find the bootable ISO image `Install macOS Catalina.iso` in the folder you specified (here: `~/Desktop`). +* You can now quit Terminal. + +## Troubleshooting -If you try to install macOS using the ISO image you created, you may receive an error dialog with a message like ***"This copy of the Install OS X El Capitan application can't be verified. It may have been corrupted or tampered with during downloading."*** or ***"This copy of the Install macOS Mojave application is damaged, and can't be used to install macOS."*** +### Alert during macOS installation from ISO image -The reason for these rather misleading error messages is that the certificates included in many macOS installer applications expired on October 24, 2019, leading to the error: [If an installer says it can't be verified or was signed with a certificate that has expired](https://support.apple.com/en-us/HT208052) +If you try to install macOS using the ISO image you created, you may see an alert that says the application can't be verified and may have been corrupted or tampered with during downloading. Or it says that the package was signed with a certificate that has expired and may not be authentic (see: [If an installer says it can't be verified or was signed with a certificate that has expired](https://support.apple.com/en-us/100622)). -A *simple* workaround is to download the updated installers with *new* certificates that Apple provides on this page: [If an installer says it can't be verified or was signed with a certificate that has expired](https://support.apple.com/en-us/HT208052) +The most likely reason for this message is that the installer was signed with a certificate that expired on October 24, 2019. You must therefore either get a *new* installer signed with a currently valid certificate or temporarily set the system date to a date in the past when the certificate was still valid. -But unfortunately there is a bug in the **updated** installer for **macOS Sierra** which makes this particular installer unusable (see: "[Error message: ... is not a valid volume mount point.](#user-content-error-message--is-not-a-valid-volume-mount-point)"). In this case, you must manually set the system date to a date in the past when the certificate was still valid: +To manually set the system date to a value in the past, perform the following steps after the above warning is displayed during the macOS installation: -* Close the error dialog. -* Open "Terminal" from the "Utilities" menu of the macOS Utilities. -* In the terminal window type `date 071900002017`. This sets the system date to July 19, 2017, 0:00 am, the release date of macOS Sierra. -* Quit the terminal window and resume the installation of macOS Sierra. +* Close the alert. +* Open Terminal from the Utilities menu. +* Type `date 100800002019` into Terminal, then press Return. This sets the system date to October 8, 2019, 0:00 am. +* Quit Terminal and resume the macOS installation. ### Error message: ... contains a broken createinstallmedia command. -If **createinstalliso** terminates with the error message `... contains a broken createinstallmedia command.` you are using the *newer* **macOS Sierra** installer like the one in: [How to upgrade to macOS Sierra](https://support.apple.com/en-us/HT208202) +If **createinstalliso** terminates with the error message `... contains a broken 'createinstallmedia' command.` you are using the *new* **macOS Sierra** installer. -While this *newer* installer application for macOS Sierra contains updated certificates (see: "[Alert: This copy of the Install ... can't ...](#user-content-alert-this-copy-of-the-install--cant-)"), it unfortunately introduced a bug that makes the included `createinstallmedia` command useless because it **always** complains about the mount point. Since `createinstallmedia` is used internally by **createinstalliso**, it is not possible to use this *newer* version of the macOS Sierra installer application. +While this *new* installer solves the problem with an expired certificate, it unfortunately introduced a bug that makes the included `createinstallmedia` command unusable because it **always** complains about the mount point. Since `createinstallmedia` is used internally by **createinstalliso**, it is *not* possible to use this *new* version of the macOS Sierra installer. -Therefore, the only solution is to obtain an *older* macOS Sierra installer application and follow the procedure described in the section "[Alert: This copy of the Install ... can't ...](#user-content-alert-this-copy-of-the-install--cant-)" to work around the problem with the expired certificates in the *older* installer application. +Therefore, the only solution is to obtain an *old* macOS Sierra installer and follow the procedure described in the section "[Alert during macOS installation from ISO image](#user-content-alert-during-macos-installation-from-iso-image)". ## References -### Installer Application Types +### Installer application types -Apple has used different internal structures for its macOS installers over time. Therefore, **createinstalliso** examines the internal structure of the installer application and determines which 'type' it is. Based on this 'type', the necessary steps to create a bootable ISO image are then performed. +Apple has used different internal structures for its macOS installers over time. Therefore, **createinstalliso** examines the internal structure of the installer and determines which 'type' it is. Based on this 'type', the necessary steps to create a bootable ISO image are performed. -The known installer application types are: +The installer application types are: | Name | Version | Type | | --------------------- | ------- |:----:| @@ -259,6 +274,6 @@ The table below lists all possible exit status and corresponding messages: ## License -Copyright (C) 2021 Michael Berger (BITespresso) +Copyright (C) 2021-2024 Michael Berger (BITespresso) **createinstalliso** is licensed under the **GNU General Public License v3 (GPL-3)** (http://www.gnu.org/copyleft/gpl.html). From 7e4d8bf9105c39bc7c39b219ca66b4fb6a1d9e52 Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Thu, 9 May 2024 16:25:06 +0200 Subject: [PATCH 11/28] Add patching of defective macOS Sierra installer application --- README.md | 19 +++---- createinstalliso | 134 +++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 129 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 4263355..ee4f594 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Creates a bootable ISO image from a macOS installer application. This image can 2. [Example for the macOS Catalina installer](#user-content-example-for-the-macos-catalina-installer) 6. [Troubleshooting](#user-content-troubleshooting) 1. [Alert during macOS installation from ISO image](#user-content-alert-during-macos-installation-from-iso-image) - 2. [Error message: ... contains a broken createinstallmedia command.](#user-content-error-message--contains-a-broken-createinstallmedia-command) + 2. [Install macOS Sierra (Version 12.6.06)](#user-content-install-macos-sierra--version-12-6-06-) 7. [References](#user-content-references) 1. [Installer application types](#user-content-installer-application-types) 2. [Required external commands](#user-content-required-external-commands) @@ -149,9 +149,11 @@ The command **createinstalliso** requires the following two command line argumen * `--isodirectory` (or `-i`) must be followed by a path to a directory where the installer ISO image file will be created. * `--applicationpath` (or `-a`) must be followed by a path to a copy of the OS installer application to create the bootable ISO image from. -The name of the ISO image to be created is made up of the name of the installer and has the extension `iso` (e.g. `Install macOS Catalina.iso`). If such a file already exists in the specified destination directory, you will be prompted for confirmation before this file is overwritten. If you do not care about overwriting an existing file and do not want to be prompted, you can add the command line option: +The name of the ISO image to be created is made up of the name of the installer and has the extension `iso` (e.g. `Install macOS Catalina.iso`). If such a file already exists in the specified destination directory, you will be prompted for confirmation before this file is overwritten. If you do not care about overwriting an existing file and do not want to be prompted, you can add the command line option `--nointeraction`. -* `--nointeraction` (or `-n`) which causes an existing ISO image file to **always** be overwritten. +If you want to suppress user interactions when running the command and always allow the required actions, you can add the option: + +* `--nointeraction` (or `-n`) which causes an existing ISO image file to be overwritten and/or patch a defective macOS Sierra installer application without prompting for confirmation. ### Example for the macOS Catalina installer @@ -184,13 +186,11 @@ To manually set the system date to a value in the past, perform the following st * Type `date 100800002019` into Terminal, then press Return. This sets the system date to October 8, 2019, 0:00 am. * Quit Terminal and resume the macOS installation. -### Error message: ... contains a broken createinstallmedia command. - -If **createinstalliso** terminates with the error message `... contains a broken 'createinstallmedia' command.` you are using the *new* **macOS Sierra** installer. +### Install macOS Sierra (Version 12.6.06) -While this *new* installer solves the problem with an expired certificate, it unfortunately introduced a bug that makes the included `createinstallmedia` command unusable because it **always** complains about the mount point. Since `createinstallmedia` is used internally by **createinstalliso**, it is *not* possible to use this *new* version of the macOS Sierra installer. +When Apple released this particular macOS Sierra installer application, a bug was introduced that causes the included `createinstallmedia` command to fail with an error message about an invalid mount point. Thanks to [Eric Knibbe](https://github.com/EricFromCanada) this bug can easily be fixed by changing the value for `CFBundleShortVersionString` from `12.6.06` to `12.6.03` in the file `Info.plist` (see: [#4](https://github.com/BITespresso/createinstalliso/issues/4)). -Therefore, the only solution is to obtain an *old* macOS Sierra installer and follow the procedure described in the section "[Alert during macOS installation from ISO image](#user-content-alert-during-macos-installation-from-iso-image)". +If **createinstalliso** detects this defective version of the macOS Sierra installer application, it will offer you to temporarily patch the `Info.plist` file by applying the above change. The original `Info.plist` will be automatically restored when **createinstalliso** terminates. This requires, of course, that the installer is on a writable medium. ## References @@ -269,8 +269,9 @@ The table below lists all possible exit status and corresponding messages: | 225 | Failed to create disk image. | | 224 | Failed to convert disk image into bootable install media. | | 223 | Failed to create ISO image. | -| 222 | \[FILE\] contains a broken createinstallmedia command. | +| 222 | \[NOT USED SINCE 1.1\] | | 221 | Couldn't get installer application minimum macOS version. | +| 220 | Failed to patch the file \[FILE\], is write-protected? | ## License diff --git a/createinstalliso b/createinstalliso index 773a44e..9c42017 100755 --- a/createinstalliso +++ b/createinstalliso @@ -29,6 +29,9 @@ fi declare -r const_script_name="createinstalliso" declare -r const_minimum_required_macos_version_string="10.6" declare -r -i const_root_uid=0 +declare -r const_defective_macos_sierra_installer_application_short_version_string="12.6.06" +declare -r const_patched_macos_sierra_installer_application_short_version_string="12.6.03" +declare -r const_macos_sierra_installer_application_info_plist_directory="Install macOS Sierra.app/Contents" # List of unsupported macOS versions. # @@ -117,6 +120,7 @@ declare -r -a const_known_macos_names_and_version_strings_regex=( unset SYSTEM_VERSION_COMPAT unset global_tmp_directory +unset global_patched_macos_sierra_installer_application_path unset global_install_esd_mountpoint unset global_install_disk_mountpoint unset global_install_disk_device_node @@ -317,14 +321,6 @@ main() { global_tmp_directory="$(mktemp -d -t "${const_script_name}")" || exit_with_status 240 "Failed to create temporary directory." random_string="${global_tmp_directory##*.}" - # ------------------------------------------------------------------ - # Check for invalid macOS Sierra installer application - # ------------------------------------------------------------------ - - if [[ "${installer_application_display_name}" == "Install macOS Sierra" ]] && is_invalid_macos_sierra_installer_application "${option_applicationpath}"; then - exit_with_status 222 "${option_applicationpath} contains a broken 'createinstallmedia' command." - fi - # ------------------------------------------------------------------ # Check installer application requirements # ------------------------------------------------------------------ @@ -350,6 +346,20 @@ main() { local type_of_existing_destination + if [[ "${installer_application_display_name}" == "Install macOS Sierra" ]] && is_defective_macos_sierra_installer_application "${option_applicationpath}"; then + if [[ ! "${option_nointeraction}" == "true" ]]; then + echo "${installer_application_path} is a defective macOS Sierra installer application." + echo "To continue we need to temporarily patch the file ${installer_application_path}/Contents/Info.plist." + + if ! get_user_consent "If you wish to continue type (Y) then press return: "; then + echo "Operation canceled." + exit_with_status 0 + fi + fi + + patch_info_plist_in_macos_sierra_installer_application "${installer_application_path}" || exit_with_status 220 "Failed to patch the file ${installer_application_path}/Contents/Info.plist, is write-protected?" + fi + if is_existing "${iso_destination_image}"; then if is_file "${iso_destination_image}"; then type_of_existing_destination="file" @@ -360,6 +370,7 @@ main() { if [[ ! "${option_nointeraction}" == "true" ]]; then echo "Ready to start." echo "To continue we need to delete the ${type_of_existing_destination} ${iso_destination_image}." + if ! get_user_consent "If you wish to continue type (Y) then press return: "; then echo "Operation canceled." exit_with_status 0 @@ -747,6 +758,21 @@ is_directory() { [[ -d "${file_or_directory}" ]] } +## +## @brief Determines whether the specified file exists and the user +## has write access. +## +## @param $1 The file to be checked. +## +## @retval `true` The file exists and the user has write access. +## @retval `false` Otherwise. +## +is_writable() { + local -r file_or_directory="$1" + + [[ -f "${file_or_directory}" ]] +} + ## ## @brief Determines whether the specified string is not empty. ## @@ -898,6 +924,25 @@ get_plist_value() { execute_plist_buddy_command "Print '${entry}'" "${plist_file}" } +## +## @brief Sets the value of an entry in a property list file. +## +## @param $1 A property list entry name. +## @param $2 The new value for the specified property list entry. +## @param $3 A property list file. +## +set_plist_value() { + local -r entry="$1" + local -r value="$2" + local -r plist_file="$3" + + is_not_empty "${entry}" || return 1 + is_not_empty "${value}" || return 1 + is_not_empty "${plist_file}" || return 1 + + execute_plist_buddy_command "Set '${entry}' '${value}'" "${plist_file}" >/dev/null +} + ## ## @brief Executes a PlistBuddy command on a property list file. ## @@ -1011,6 +1056,10 @@ signal_handler() { ## cleanup() { if is_not_empty "${global_tmp_directory-}" && is_existing "${global_tmp_directory}"; then + if is_not_empty "${global_patched_macos_sierra_installer_application_path-}"; then + restore_info_plist_in_macos_sierra_installer_application "${global_patched_macos_sierra_installer_application_path}" && unset global_patched_macos_sierra_installer_application_path + fi + detach_for_cleanup "global_install_esd_mountpoint" detach_for_cleanup "global_install_disk_mountpoint" detach_for_cleanup "global_install_disk_device_node" @@ -1115,7 +1164,7 @@ print_error_usage() { print_error "Arguments" print_error "--isodirectory, A path to a directory where the installer ISO image file will be created." print_error "--applicationpath, A path to copy of the OS installer application to create the bootable ISO image from." - print_error "--nointeraction, Delete an existing ISO image file without prompting for confirmation." + print_error "--nointeraction, Delete an existing ISO image file and/or patch a defective macOS Sierra installer application without prompting for confirmation." print_error print_error "Example: ${const_script_name} --isodirectory ~/Desktop/ --applicationpath \"/Applications/$(create_example_installer_application_name)\"" @@ -1540,22 +1589,22 @@ is_valid_installer_application_path() { } ## -## @brief Determines whether the installer application is a macOS -## Sierra installer application version which contains a -## broken 'createinstallmedia' command. +## @brief Determines whether an installer application is the +## defective macOS Sierra installer application. ## ## @param $1 An installer application path to be checked. ## -## @retval `true` Invalid macOS Sierra installer application. +## @retval `true` The installer application is the defective macOS +## Sierra installer application. ## @retval `false` Otherwise. ## -is_invalid_macos_sierra_installer_application() { +is_defective_macos_sierra_installer_application() { local -r installer_application_path="$1" local installer_application_short_version_string installer_application_short_version_string="$(get_installer_application_short_version_string "${installer_application_path}")" || return 0 - [[ "${installer_application_short_version_string}" == "12.6.06" ]] + [[ "${installer_application_short_version_string}" == "${const_defective_macos_sierra_installer_application_short_version_string}" ]] } ## @@ -1805,6 +1854,61 @@ get_installer_application_short_version_string() get_plist_value ":CFBundleShortVersionString" "${installer_application_path}/Contents/Info.plist" } +## +## @brief Sets the 'short version string' of an installer +## application. +## +## @param $1 The path to an installer application. +## @param $2 The new value for the 'short version string'. +## +set_installer_application_short_version_string() +{ + local -r installer_application_path="$1" + local -r value="$2" + + is_not_empty "${installer_application_path}" || return 1 + is_not_empty "${value}" || return 1 + + set_plist_value ":CFBundleShortVersionString" "${value}" "${installer_application_path}/Contents/Info.plist" +} + +## +## @brief Patches the Info.plist file in the defective macOS Sierra +## installer application and sets the installer application +## path in a global variable for cleanup. +## +## @param $1 The path to the defective macOS Sierra installer +## application. +## +patch_info_plist_in_macos_sierra_installer_application() +{ + local -r installer_application_path="$1" + + is_not_empty "${installer_application_path}" || return 1 + + mkdir -p "${global_tmp_directory}/${const_macos_sierra_installer_application_info_plist_directory}" || return 1 + cp -p "${installer_application_path}/Contents/Info.plist" "${global_tmp_directory}/${const_macos_sierra_installer_application_info_plist_directory}/" || return 1 + set_installer_application_short_version_string "${installer_application_path}" "${const_patched_macos_sierra_installer_application_short_version_string}" || return 1 + global_patched_macos_sierra_installer_application_path="${installer_application_path}" +} + +## +## @brief Restores the Info.plist file in the defective macOS +## Sierra installer application to the state before it was +## patched. +## +## @param $1 The path to the defective macOS Sierra installer +## application. +## +restore_info_plist_in_macos_sierra_installer_application() +{ + local -r installer_application_path="$1" + + is_not_empty "${installer_application_path}" || return 1 + + cp -p "${global_tmp_directory}/${const_macos_sierra_installer_application_info_plist_directory}/Info.plist" "${installer_application_path}/Contents/Info.plist" +} + ## ## @brief Gets the 'identifier' of an installer application. ## From 01fbcd56842f7518ee74f028c4e17275bca0bdf1 Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Thu, 9 May 2024 16:30:27 +0200 Subject: [PATCH 12/28] Use '[[ a != b ]]' instead of '[[ ! a == b ]]' --- createinstalliso | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/createinstalliso b/createinstalliso index 9c42017..5cd3557 100755 --- a/createinstalliso +++ b/createinstalliso @@ -347,7 +347,7 @@ main() { local type_of_existing_destination if [[ "${installer_application_display_name}" == "Install macOS Sierra" ]] && is_defective_macos_sierra_installer_application "${option_applicationpath}"; then - if [[ ! "${option_nointeraction}" == "true" ]]; then + if [[ "${option_nointeraction}" != "true" ]]; then echo "${installer_application_path} is a defective macOS Sierra installer application." echo "To continue we need to temporarily patch the file ${installer_application_path}/Contents/Info.plist." @@ -367,7 +367,7 @@ main() { type_of_existing_destination="directory" fi - if [[ ! "${option_nointeraction}" == "true" ]]; then + if [[ "${option_nointeraction}" != "true" ]]; then echo "Ready to start." echo "To continue we need to delete the ${type_of_existing_destination} ${iso_destination_image}." From 4087a014ce5a2fdaa431e5a30c83be161d665018 Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Thu, 9 May 2024 16:55:39 +0200 Subject: [PATCH 13/28] Remove unnecessary option '-P' in 'cp' command --- createinstalliso | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/createinstalliso b/createinstalliso index 5cd3557..d255aaa 100755 --- a/createinstalliso +++ b/createinstalliso @@ -509,15 +509,15 @@ main() { echo "Copying installer files to disk image..." rm -f "${global_install_disk_mountpoint}/System/Installation/Packages" >/dev/null || exit_with_status 231 "Failed to delete Packages symlink." - cp -R -P -p "${global_install_esd_mountpoint}/Packages" "${global_install_disk_mountpoint}/System/Installation/" >/dev/null || exit_with_status 230 "Failed to copy Packages directory." + cp -R -p "${global_install_esd_mountpoint}/Packages" "${global_install_disk_mountpoint}/System/Installation/" >/dev/null || exit_with_status 230 "Failed to copy Packages directory." echo "Making disk image bootable..." core_services_directory="${global_install_disk_mountpoint}/System/Library/CoreServices" bless --folder "${core_services_directory}" --file "${core_services_directory}/boot.efi" --label "${installer_application_display_name}" >/dev/null || exit_with_status 229 "The bless of the installer disk image failed." echo "Copying installer image..." - cp -P -p "${base_system_image}" "${global_install_disk_mountpoint}/" >/dev/null || exit_with_status 228 "Failed to copy BaseSystem.dmg." - cp -P -p "${base_system_image%.dmg}.chunklist" "${global_install_disk_mountpoint}/" >/dev/null || exit_with_status 227 "Failed to copy BaseSystem.chunklist." + cp -p "${base_system_image}" "${global_install_disk_mountpoint}/" >/dev/null || exit_with_status 228 "Failed to copy BaseSystem.dmg." + cp -p "${base_system_image%.dmg}.chunklist" "${global_install_disk_mountpoint}/" >/dev/null || exit_with_status 227 "Failed to copy BaseSystem.chunklist." hdiutil detach "${global_install_esd_mountpoint}" -force >/dev/null || exit_with_status 226 "Couldn't unmount disk image." unset global_install_esd_mountpoint From 2ba525cfac7d78074fb8aa68ed005afb7e50b1fd Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Sat, 11 May 2024 13:11:42 +0200 Subject: [PATCH 14/28] Change a comment for better explanation --- createinstalliso | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/createinstalliso b/createinstalliso index d255aaa..361dfad 100755 --- a/createinstalliso +++ b/createinstalliso @@ -115,8 +115,9 @@ declare -r -a const_known_macos_names_and_version_strings_regex=( '^14\.[[:digit:]]+(\.[[:digit:]+])?$ # macOS Sonoma' # 14 ) -# Make sure that macOS Big Sur reports 11 as the macOS version instead -# of 10.16. +# Ensures that 'sw_vers' uses Apple's new naming scheme for macOS +# versions. E.g. macOS Big Sur reports version 11.x.y instead of +# 10.16.x. unset SYSTEM_VERSION_COMPAT unset global_tmp_directory From a9b983ea91656d66b57d52a65f433b8d358f6494 Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Sat, 11 May 2024 13:20:35 +0200 Subject: [PATCH 15/28] Add check for empty or unset 'global_tmp_directory' --- createinstalliso | 2 ++ 1 file changed, 2 insertions(+) diff --git a/createinstalliso b/createinstalliso index 361dfad..338a851 100755 --- a/createinstalliso +++ b/createinstalliso @@ -1886,6 +1886,7 @@ patch_info_plist_in_macos_sierra_installer_application() local -r installer_application_path="$1" is_not_empty "${installer_application_path}" || return 1 + is_not_empty "${global_tmp_directory-}" || return 1 mkdir -p "${global_tmp_directory}/${const_macos_sierra_installer_application_info_plist_directory}" || return 1 cp -p "${installer_application_path}/Contents/Info.plist" "${global_tmp_directory}/${const_macos_sierra_installer_application_info_plist_directory}/" || return 1 @@ -1906,6 +1907,7 @@ restore_info_plist_in_macos_sierra_installer_application() local -r installer_application_path="$1" is_not_empty "${installer_application_path}" || return 1 + is_not_empty "${global_tmp_directory-}" || return 1 cp -p "${global_tmp_directory}/${const_macos_sierra_installer_application_info_plist_directory}/Info.plist" "${installer_application_path}/Contents/Info.plist" } From f2fddf39528ebca245b80838038bfcdb0acd7d49 Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Sat, 11 May 2024 13:25:35 +0200 Subject: [PATCH 16/28] Shorten some links in README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ee4f594..a75f665 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ Creates a bootable ISO image from a macOS installer application. This image can 4. [Installation](#user-content-installation) 5. [Usage](#user-content-usage) 1. [Command line arguments](#user-content-command-line-arguments) - 2. [Example for the macOS Catalina installer](#user-content-example-for-the-macos-catalina-installer) + 2. [Example](#user-content-example) 6. [Troubleshooting](#user-content-troubleshooting) - 1. [Alert during macOS installation from ISO image](#user-content-alert-during-macos-installation-from-iso-image) + 1. [Alert during macOS installation](#user-content-alert-during-macos-installation) 2. [Install macOS Sierra (Version 12.6.06)](#user-content-install-macos-sierra--version-12-6-06-) 7. [References](#user-content-references) 1. [Installer application types](#user-content-installer-application-types) @@ -155,7 +155,7 @@ If you want to suppress user interactions when running the command and always al * `--nointeraction` (or `-n`) which causes an existing ISO image file to be overwritten and/or patch a defective macOS Sierra installer application without prompting for confirmation. -### Example for the macOS Catalina installer +### Example The following example show the steps required to create an ISO image from a macOS Catalina installer: @@ -173,7 +173,7 @@ The following example show the steps required to create an ISO image from a macO ## Troubleshooting -### Alert during macOS installation from ISO image +### Alert during macOS installation If you try to install macOS using the ISO image you created, you may see an alert that says the application can't be verified and may have been corrupted or tampered with during downloading. Or it says that the package was signed with a certificate that has expired and may not be authentic (see: [If an installer says it can't be verified or was signed with a certificate that has expired](https://support.apple.com/en-us/100622)). From d76b37d77675473c42fca06ae225ef73347117b0 Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Sat, 11 May 2024 13:30:29 +0200 Subject: [PATCH 17/28] Change copyright date --- createinstalliso | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/createinstalliso b/createinstalliso index 338a851..847df69 100755 --- a/createinstalliso +++ b/createinstalliso @@ -2,7 +2,7 @@ # createinstalliso - Creates a bootable ISO image from a macOS # installer application. -# Copyright (C) 2017 Michael Berger +# Copyright (C) 2021-2024 Michael Berger # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by From 4504be0907af23089b5da27688bd5ea8c4e11ac6 Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Wed, 26 Jun 2024 21:00:11 +0200 Subject: [PATCH 18/28] Fix typo in function description --- createinstalliso | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/createinstalliso b/createinstalliso index 847df69..5ac3810 100755 --- a/createinstalliso +++ b/createinstalliso @@ -963,7 +963,7 @@ execute_plist_buddy_command() { } ## -## @brief Removes all leading and tailing whitespaces from a +## @brief Removes all leading and trailing whitespaces from a ## string. ## ## @param $1 A string. From 65ae72de448e97f0d4f29d889b5a1c6b688fa828 Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Mon, 1 Jul 2024 12:46:08 +0200 Subject: [PATCH 19/28] Improve function description --- createinstalliso | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/createinstalliso b/createinstalliso index 5ac3810..0d0ebf2 100755 --- a/createinstalliso +++ b/createinstalliso @@ -814,9 +814,10 @@ matches_array_item_regex() { } ## -## @brief Gets the absolute logical path to a file or directory. +## @brief Gets the absolute logical path to an existing file or +## directory. ## -## @param $1 An absolute or relative path to a file or +## @param $1 An absolute or relative path to an existing file or ## directory. ## ## @return The absolute logical (i.e. not the physical) path to the From 2b5e94a1e97bcf22a344f17afe177d60214627b0 Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Mon, 1 Jul 2024 12:47:32 +0200 Subject: [PATCH 20/28] Improve code formatting --- createinstalliso | 1 + 1 file changed, 1 insertion(+) diff --git a/createinstalliso b/createinstalliso index 0d0ebf2..5465144 100755 --- a/createinstalliso +++ b/createinstalliso @@ -863,6 +863,7 @@ get_item_starting_with_string() { return 0 fi done + return 1 } From 2ca79c69bcffbe879b71c5240f33f1f4c0673c02 Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Thu, 31 Oct 2024 10:30:03 +0100 Subject: [PATCH 21/28] Use a copy of the installer application if required When using an Apple silicon Mac to create an ISO image for macOS 10.12 to 10.15, the required 'createinstallmedia' fails due to missing code signatures for 'arm64'. As a workaround, a copy of the installer application is created and the existing code signatures are replaced by an ad-doc signature in this copy of the installer application. The fix for the broken macOS Sierra installer is now also applied to a copy of the installer application instead of temporarily modifying the originally specified installer application. --- README.md | 43 ++++- createinstalliso | 461 ++++++++++++++++++++++++++++++++++------------- 2 files changed, 372 insertions(+), 132 deletions(-) diff --git a/README.md b/README.md index a75f665..94102af 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,6 @@ Creates a bootable ISO image from a macOS installer application. This image can 2. [Example](#user-content-example) 6. [Troubleshooting](#user-content-troubleshooting) 1. [Alert during macOS installation](#user-content-alert-during-macos-installation) - 2. [Install macOS Sierra (Version 12.6.06)](#user-content-install-macos-sierra--version-12-6-06-) 7. [References](#user-content-references) 1. [Installer application types](#user-content-installer-application-types) 2. [Required external commands](#user-content-required-external-commands) @@ -153,7 +152,7 @@ The name of the ISO image to be created is made up of the name of the installer If you want to suppress user interactions when running the command and always allow the required actions, you can add the option: -* `--nointeraction` (or `-n`) which causes an existing ISO image file to be overwritten and/or patch a defective macOS Sierra installer application without prompting for confirmation. +* `--nointeraction` (or `-n`) causes an existing ISO image file to **always** be overwritten. ### Example @@ -186,12 +185,6 @@ To manually set the system date to a value in the past, perform the following st * Type `date 100800002019` into Terminal, then press Return. This sets the system date to October 8, 2019, 0:00 am. * Quit Terminal and resume the macOS installation. -### Install macOS Sierra (Version 12.6.06) - -When Apple released this particular macOS Sierra installer application, a bug was introduced that causes the included `createinstallmedia` command to fail with an error message about an invalid mount point. Thanks to [Eric Knibbe](https://github.com/EricFromCanada) this bug can easily be fixed by changing the value for `CFBundleShortVersionString` from `12.6.06` to `12.6.03` in the file `Info.plist` (see: [#4](https://github.com/BITespresso/createinstalliso/issues/4)). - -If **createinstalliso** detects this defective version of the macOS Sierra installer application, it will offer you to temporarily patch the `Info.plist` file by applying the above change. The original `Info.plist` will be automatically restored when **createinstalliso** terminates. This requires, of course, that the installer is on a writable medium. - ## References ### Installer application types @@ -218,7 +211,33 @@ The installer application types are: ### Required external commands -**createinstalliso** uses a number of external commands, which must be available on your system: `awk`, `bless`, `cp`, `cut`, `df`, `du`, `hdiutil`, `mktemp`, `ps`, `pwd`, `rm`, `script`, `seq`, `stat`, `sw_vers`, `tput`, `uname` and `/usr/libexec/PlistBuddy`. +**createinstalliso** uses a number of external commands, which must be available on your system: + +- `awk` +- `bless` +- `codesign` +- `cp` +- `cut` +- `df` +- `ditto` +- `du` +- `file` +- `grep` +- `hdiutil` +- `mktemp` +- `ps` +- `pwd` +- `rm` +- `rsync` +- `script` +- `seq` +- `stat` +- `sw_vers` +- `sysctl` +- `tput` +- `uname` +- `xattr` +- `/usr/libexec/PlistBuddy`. Unless you have deliberately modified your system, all of the above commands are available on the macOS versions listed in the section "[Compatibility](#user-content-compatibility)". @@ -271,7 +290,11 @@ The table below lists all possible exit status and corresponding messages: | 223 | Failed to create ISO image. | | 222 | \[NOT USED SINCE 1.1\] | | 221 | Couldn't get installer application minimum macOS version. | -| 220 | Failed to patch the file \[FILE\], is write-protected? | +| 220 | Failed to copy installer application. | +| 219 | Failed to remove extended attribute com.apple.quarantine. | +| 218 | Failed to patch macOS Sierra installer application. | +| 217 | Failed to replace code signatures. | +| 216 | Failed to replace modified files with original ones. | ## License diff --git a/createinstalliso b/createinstalliso index 5465144..2ba35b2 100755 --- a/createinstalliso +++ b/createinstalliso @@ -29,9 +29,6 @@ fi declare -r const_script_name="createinstalliso" declare -r const_minimum_required_macos_version_string="10.6" declare -r -i const_root_uid=0 -declare -r const_defective_macos_sierra_installer_application_short_version_string="12.6.06" -declare -r const_patched_macos_sierra_installer_application_short_version_string="12.6.03" -declare -r const_macos_sierra_installer_application_info_plist_directory="Install macOS Sierra.app/Contents" # List of unsupported macOS versions. # @@ -69,10 +66,10 @@ declare -r -a const_supported_macos_version_strings_regex=( # List of all supported installer applications and their disk space # offset requirements for the installer application types '3' and '4' -# (see 'get_offset_for_global_tmp_directory' and -# 'get_offset_for_iso_directory_path'). The disk space offset -# requirements must each be separated by the '#' character each. The -# disk space offset requirements are specified in millions of bytes. +# (see 'get_offset_for_tmp_directory' and +# 'get_offset_for_iso_directory_path'). The columns are separated by the +# '#' character. The disk space offset requirements are specified in +# millions of bytes. declare -r -a const_supported_installer_application_display_names_and_offsets=( 'Install Mac OS X Lion # # ' # 10.7 'Install OS X Mountain Lion # # ' # 10.8 @@ -90,8 +87,7 @@ declare -r -a const_supported_installer_application_display_names_and_offsets=( ) # List of all known macOS version names and their corresponding version -# string regex. The regex expression and the macOS version name must -# be separated by the '#' character. +# string regex. The columns are separated by the '#' character. declare -r -a const_known_macos_names_and_version_strings_regex=( '^10\.0(\.[[:digit:]+])?$ # Mac OS X Cheetah' # 10.0 '^10\.1(\.[[:digit:]+])?$ # Mac OS X Puma' # 10.1 @@ -115,13 +111,18 @@ declare -r -a const_known_macos_names_and_version_strings_regex=( '^14\.[[:digit:]]+(\.[[:digit:]+])?$ # macOS Sonoma' # 14 ) +declare global_patch_macos_sierra_installer_application="false" +declare global_replace_code_signatures_in_installer_application="false" + # Ensures that 'sw_vers' uses Apple's new naming scheme for macOS # versions. E.g. macOS Big Sur reports version 11.x.y instead of # 10.16.x. unset SYSTEM_VERSION_COMPAT +# Ensures that 'ditto' does not automatically add some options. +unset DITTONORSRC + unset global_tmp_directory -unset global_patched_macos_sierra_installer_application_path unset global_install_esd_mountpoint unset global_install_disk_mountpoint unset global_install_disk_device_node @@ -306,6 +307,8 @@ main() { # ------------------------------------------------------------------ local installer_application_path + local original_installer_application_path + local installer_application_basename local iso_directory_path local installer_application_display_name local -i installer_application_type @@ -313,6 +316,8 @@ main() { local random_string installer_application_path="$(get_absolute_logical_path "${option_applicationpath}")" || exit_with_status 244 "Couldn't get absolute path for ${option_applicationpath}." + original_installer_application_path="${installer_application_path}" + installer_application_basename="$(get_basename "${installer_application_path}")" iso_directory_path="$(get_absolute_logical_path "${option_isodirectory}")" || exit_with_status 243 "Couldn't get absolute path for ${option_isodirectory}." installer_application_display_name="$(get_installer_application_display_name "${installer_application_path}")" || exit_with_status 242 "Couldn't get installer application display name." @@ -341,26 +346,25 @@ main() { exit_with_status 238 "You need ${macos_name_and_version} or later to create an ISO image from this installer application." fi + if is_defective_macos_sierra_installer_application "${installer_application_path}" "${installer_application_display_name}"; then + global_patch_macos_sierra_installer_application="true" + fi + + if is_apple_silicon_mac && is_createinstallmedia_required "${installer_application_type}" && ! is_mach_o_arm64_executable "${installer_application_path}/Contents/Resources/createinstallmedia"; then + # This flag may only be set to 'true' if the 'codesign' command + # supports the '--sign -' and '--force' options. Here we can be + # sure that we are dealing with an Apple silicon Mac and are + # therefore using macOS Big Sur or higher, all of which support + # these options. + global_replace_code_signatures_in_installer_application="true" + fi + # ------------------------------------------------------------------ # Perform required user interactions # ------------------------------------------------------------------ local type_of_existing_destination - if [[ "${installer_application_display_name}" == "Install macOS Sierra" ]] && is_defective_macos_sierra_installer_application "${option_applicationpath}"; then - if [[ "${option_nointeraction}" != "true" ]]; then - echo "${installer_application_path} is a defective macOS Sierra installer application." - echo "To continue we need to temporarily patch the file ${installer_application_path}/Contents/Info.plist." - - if ! get_user_consent "If you wish to continue type (Y) then press return: "; then - echo "Operation canceled." - exit_with_status 0 - fi - fi - - patch_info_plist_in_macos_sierra_installer_application "${installer_application_path}" || exit_with_status 220 "Failed to patch the file ${installer_application_path}/Contents/Info.plist, is write-protected?" - fi - if is_existing "${iso_destination_image}"; then if is_file "${iso_destination_image}"; then type_of_existing_destination="file" @@ -385,17 +389,25 @@ main() { # Check available disk space # ------------------------------------------------------------------ - local -i available_in_global_tmp_directory - local -i available_in_iso_directory_path - local -i required_in_global_tmp_directory - local -i required_in_iso_directory_path - local -i offset_for_global_tmp_directory - local -i offset_for_iso_directory_path - local -i missing_in_global_tmp_directory - local -i missing_in_iso_directory_path + local -i available_in_tmp_directory + local -i available_in_iso_directory + + local -i required_for_modified_installer_application + local -i required_for_iso_image + + local -i required_in_tmp_directory + local -i required_in_iso_directory - available_in_global_tmp_directory="$(get_available_disk_space "${global_tmp_directory}")" || return 1 - available_in_iso_directory_path="$(get_available_disk_space "${iso_directory_path}")" || return 1 + local -i offset_for_tmp_directory + local -i offset_for_iso_image + + local -i missing_in_tmp_directory + local -i missing_in_iso_directory + + (( available_in_tmp_directory = $(get_available_disk_space "${global_tmp_directory}") )) + (( available_in_iso_directory = $(get_available_disk_space "${iso_directory_path}") )) + + (( required_for_modified_installer_application = $(get_disk_space_used_by_file_or_directory "${installer_application_path}") )) case "${installer_application_type}" in @@ -403,9 +415,10 @@ main() { # - Mac OS X Lion 10.7 # - OS X Mountain Lion 10.8 1 ) - (( required_in_global_tmp_directory = 0 )) + (( required_in_tmp_directory = 0 )) - (( required_in_iso_directory_path = $(get_disk_image_total_non_empty_bytes "${installer_application_path}/Contents/SharedSupport/InstallESD.dmg") )) + (( required_for_iso_image = $(get_disk_image_total_non_empty_bytes "${installer_application_path}/Contents/SharedSupport/InstallESD.dmg") )) + (( required_in_iso_directory = required_for_iso_image )) ;; # Installer application type '2' for: @@ -413,11 +426,12 @@ main() { # - OS X Yosemite 10.10 # - OS X El Capitan 10.11 2 ) - (( required_in_global_tmp_directory = $(get_disk_image_total_non_empty_bytes "${installer_application_path}/Contents/SharedSupport/InstallESD.dmg") )) - (( required_in_global_tmp_directory = required_in_global_tmp_directory + (required_in_global_tmp_directory * 16 / 100) + 360000000 )) + (( required_in_tmp_directory = $(get_disk_image_total_non_empty_bytes "${installer_application_path}/Contents/SharedSupport/InstallESD.dmg") * 16 / 100 )) + (( required_in_tmp_directory += 360000000 )) - (( required_in_iso_directory_path = $(get_disk_space_used_by_file_or_directory "${installer_application_path}") )) - (( required_in_iso_directory_path = required_in_iso_directory_path + 1460000000 )) + (( required_for_iso_image = $(get_disk_space_used_by_file_or_directory "${installer_application_path}") )) + (( required_for_iso_image += 1460000000 )) + (( required_in_iso_directory = required_for_iso_image )) ;; # Installer application type '3' for: @@ -434,37 +448,72 @@ main() { # - macOS Ventura 13 # - macOS Sonoma 14 3 | 4 ) - (( required_in_global_tmp_directory = $(get_disk_space_used_by_file_or_directory "${installer_application_path}") )) - offset_for_global_tmp_directory="$(get_offset_for_global_tmp_directory "${installer_application_display_name}")" || offset_for_global_tmp_directory=0 - (( offset_for_global_tmp_directory = offset_for_global_tmp_directory * 1000000 )) - (( required_in_global_tmp_directory = required_in_global_tmp_directory + offset_for_global_tmp_directory )) - - (( required_in_iso_directory_path = $(get_disk_space_used_by_file_or_directory "${installer_application_path}") )) - offset_for_iso_directory_path="$(get_offset_for_iso_directory_path "${installer_application_display_name}")" || offset_for_iso_directory_path=0 - (( offset_for_iso_directory_path = offset_for_iso_directory_path * 1000000 )) - (( required_in_iso_directory_path = required_in_iso_directory_path + offset_for_iso_directory_path )) + (( offset_for_tmp_directory = $(get_offset_for_tmp_directory "${installer_application_display_name}") )) || offset_for_tmp_directory=0 + (( required_in_tmp_directory = $(get_disk_space_used_by_file_or_directory "${installer_application_path}") )) + (( required_in_tmp_directory += offset_for_tmp_directory * 1000000 )) + + (( offset_for_iso_image = $(get_offset_for_iso_directory_path "${installer_application_display_name}") )) || offset_for_iso_image=0 + (( required_for_iso_image = $(get_disk_space_used_by_file_or_directory "${installer_application_path}") )) + (( required_for_iso_image += offset_for_iso_image * 1000000 )) + (( required_in_iso_directory = required_for_iso_image )) ;; esac if are_one_same_device "${global_tmp_directory}" "${iso_directory_path}"; then - (( missing_in_global_tmp_directory = (required_in_global_tmp_directory + required_in_iso_directory_path) - available_in_global_tmp_directory )) + if is_modification_of_installer_application_required; then + (( required_in_tmp_directory += $(max "${required_for_modified_installer_application}" "${required_in_iso_directory}") )) + else + (( required_in_tmp_directory += required_in_iso_directory )) + fi - if (( missing_in_global_tmp_directory > 0 )); then - exit_with_missing_disk_space "$(get_volume_name "${global_tmp_directory}")" "temporary files and installer ISO image" "${missing_in_global_tmp_directory}" + (( missing_in_tmp_directory = required_in_tmp_directory - available_in_tmp_directory )) + + if (( missing_in_tmp_directory > 0 )); then + exit_with_missing_disk_space "$(get_volume_name "${global_tmp_directory}")" "temporary files and installer ISO image" "${missing_in_tmp_directory}" fi else - (( missing_in_global_tmp_directory = required_in_global_tmp_directory - available_in_global_tmp_directory )) + if is_modification_of_installer_application_required; then + (( required_in_tmp_directory += required_for_modified_installer_application )) + fi + + (( missing_in_tmp_directory = required_in_tmp_directory - available_in_tmp_directory )) + + if (( missing_in_tmp_directory > 0 )); then + exit_with_missing_disk_space "$(get_volume_name "${global_tmp_directory}")" "temporary files" "${missing_in_tmp_directory}" + fi + + (( missing_in_iso_directory = required_in_iso_directory - available_in_iso_directory )) - if (( missing_in_global_tmp_directory > 0 )); then - exit_with_missing_disk_space "$(get_volume_name "${global_tmp_directory}")" "temporary files" "${missing_in_global_tmp_directory}" + if (( missing_in_iso_directory > 0 )); then + exit_with_missing_disk_space "$(get_volume_name "${iso_directory_path}")" "installer ISO image" "${missing_in_iso_directory}" fi + fi + + # ------------------------------------------------------------------ + # Modify installer application (if required) + # ------------------------------------------------------------------ + + local modified_installer_application_path - (( missing_in_iso_directory_path = required_in_iso_directory_path - available_in_iso_directory_path )) + if is_modification_of_installer_application_required; then + modified_installer_application_path="${global_tmp_directory}/InstallerApplication/${installer_application_basename}" - if (( missing_in_iso_directory_path > 0 )); then - exit_with_missing_disk_space "$(get_volume_name "${iso_directory_path}")" "installer ISO image" "${missing_in_iso_directory_path}" + echo "Copying installer application..." + ditto "${original_installer_application_path}" "${modified_installer_application_path}" >/dev/null || exit_with_status 220 "Failed to copy installer application." + xattr -drs "com.apple.quarantine" "${modified_installer_application_path}" >/dev/null || exit_with_status 219 "Failed to remove extended attribute com.apple.quarantine." + + if [[ "${global_patch_macos_sierra_installer_application}" == "true" ]]; then + echo "Patching macOS Sierra installer application..." + set_installer_application_short_version_string "${modified_installer_application_path}" "12.6.03" || exit_with_status 218 "Failed to patch macOS Sierra installer application." + fi + + if [[ "${global_replace_code_signatures_in_installer_application}" == "true" ]]; then + echo "Replacing existing code signatures..." + replace_code_signatures "${modified_installer_application_path}" || exit_with_status 217 "Failed to replace code signatures." fi + + installer_application_path="${modified_installer_application_path}" fi # ------------------------------------------------------------------ @@ -473,7 +522,6 @@ main() { local iso_source_image local install_esd_image - local install_esd_shadow local base_system_image local install_disk_image local install_disk_volname @@ -554,6 +602,17 @@ main() { hdiutil detach "${global_install_disk_device_node}" -force >/dev/null || exit_with_status 226 "Couldn't unmount disk image." unset global_install_disk_device_node + if is_modification_of_installer_application_required; then + rm -rf "${modified_installer_application_path}" >/dev/null || true + global_install_disk_mountpoint="${global_tmp_directory}/InstallDisk" + hdiutil attach "${install_disk_image}" -readwrite -mountpoint "${global_install_disk_mountpoint}" -nobrowse -noverify >/dev/null || exit_with_status 232 "The disk image did not mount." + + rsync -a "${original_installer_application_path}" "${global_install_disk_mountpoint}" || exit_with_status 216 "Failed to replace modified files with original ones." + + hdiutil detach "${global_install_disk_mountpoint}" -force >/dev/null || exit_with_status 226 "Couldn't unmount disk image." + unset global_install_disk_mountpoint + fi + iso_source_image="${install_disk_image}" ;; @@ -598,6 +657,23 @@ echo_n() { IFS=' ' printf "%s" "$*" } +## +## @brief Gets the basename of a file or directory. +## +## @param $1 A file or directory. +## +## @return The basename of the file or directory. +## +get_basename() { + local -r file_or_directory="$1" + local basename + + basename="${file_or_directory%/}" + basename="${basename##*/}" + + echo "${basename}" +} + ## ## @brief Gets the name of the current operating system. ## @@ -611,6 +687,61 @@ get_os_name() { echo "${os_name}" } +## +## @brief Gets a space-delimited list of the architectures of all +## Mach-O executables in the specified file. +## +## @param $1 A file. +## +## @return The space-delimited list of Mach-O executable +## architectures. +## +get_mach_o_executable_architectures() { + local -r file="$1" + local file_output_with_colon_separator + local file_output_with_space_separator + local -i output_length + local -i i + local file_path_in_output + local file_output_without_file_path + local file_output_line_without_file_path + local -a executable_architectures=() + + is_not_empty "${file}" || return 1 + is_file "${file}" || return 1 + + file_output_with_colon_separator="$(file --separator ":" "${file}")" + file_output_with_space_separator="$(file --separator " " "${file}")" + output_length="${#file_output_with_colon_separator}" + + # Search for the first character that differs in both strings + for (( i = 0; i < output_length; i++ )); do + if [[ "${file_output_with_colon_separator:${i}:1}" != "${file_output_with_space_separator:${i}:1}" ]]; then + break + fi + done + + file_path_in_output="${file_output_with_colon_separator:0:${i}}" + + # Remove all occurrences of the file path in the output of the 'file' + # command to avoid unexpected characters in the output. + file_output_without_file_path="${file_output_with_colon_separator//"${file_path_in_output}"/}" + + if is_mach_o_universal_binary "${file_output_without_file_path}"; then + while IFS= read -r file_output_line_without_file_path; do + if is_mach_o_executable "${file_output_line_without_file_path}"; then + executable_architectures+=("${file_output_line_without_file_path##* }") + fi + done <<<"${file_output_without_file_path#*$'\n'}" + else + if is_mach_o_executable "${file_output_without_file_path}"; then + executable_architectures+=("${file_output_without_file_path##* }") + fi + fi + + IFS=' ' echo "${executable_architectures[@]}" +} + ## ## @brief Gets the available disk space. ## @@ -690,6 +821,44 @@ get_root_volume_name() { get_plist_value ":VolumeName" "${plist_file}" } +## +## @brief Determines whether this machine is an Apple silicon Mac. +## +## @retval `true` The machine is an Apple silicon Mac. +## @retval `false` Otherwise. +## +is_apple_silicon_mac() { + [[ "$(sysctl -n hw.optional.arm64 2>/dev/null)" == "1" ]] +} + +## +## @brief Determines whether the output of the 'file' command (with +## the file path portions removed) contains the file type +## 'Mach-O universal binary'. +## +## @param $1 A single output line from the 'file' command with +## the file path portion removed. +## +is_mach_o_universal_binary() { + local -r file_output_without_file_path="$1" + + echo "${file_output_without_file_path%%$'\n'*}" | grep -E "^:[[:space:]]+Mach-O universal binary with [0-9]+ architectures" >/dev/null +} + +## +## @brief Determines whether an output line of the 'file' command +## (with the file path portion removed) contains the file +## type 'Mach-O executable'. +## +## @param $1 A single output line from the 'file' command with +## the file path portion removed. +## +is_mach_o_executable() { + local -r file_output_line_without_file_path="$1" + + echo "${file_output_line_without_file_path}" | grep -E "Mach-O( 64-bit | )executable [^ ]+$" >/dev/null +} + ## ## @brief Determines whether an operating system is macOS. ## @@ -759,21 +928,6 @@ is_directory() { [[ -d "${file_or_directory}" ]] } -## -## @brief Determines whether the specified file exists and the user -## has write access. -## -## @param $1 The file to be checked. -## -## @retval `true` The file exists and the user has write access. -## @retval `false` Otherwise. -## -is_writable() { - local -r file_or_directory="$1" - - [[ -f "${file_or_directory}" ]] -} - ## ## @brief Determines whether the specified string is not empty. ## @@ -813,6 +967,23 @@ matches_array_item_regex() { return 1 } +## +## @brief Returns the maximum of two integer numbers. +## +## @param $1 An integer. +## @param $2 Another integer. +## +## @return The maximum of the two integer numbers. +## +max() { + local -r -i m="$1" + local -r -i n="$2" + + echo "$(( m > n ? m : n ))" + + return 0 +} + ## ## @brief Gets the absolute logical path to an existing file or ## directory. @@ -1059,10 +1230,6 @@ signal_handler() { ## cleanup() { if is_not_empty "${global_tmp_directory-}" && is_existing "${global_tmp_directory}"; then - if is_not_empty "${global_patched_macos_sierra_installer_application_path-}"; then - restore_info_plist_in_macos_sierra_installer_application "${global_patched_macos_sierra_installer_application_path}" && unset global_patched_macos_sierra_installer_application_path - fi - detach_for_cleanup "global_install_esd_mountpoint" detach_for_cleanup "global_install_disk_mountpoint" detach_for_cleanup "global_install_disk_device_node" @@ -1167,7 +1334,7 @@ print_error_usage() { print_error "Arguments" print_error "--isodirectory, A path to a directory where the installer ISO image file will be created." print_error "--applicationpath, A path to copy of the OS installer application to create the bootable ISO image from." - print_error "--nointeraction, Delete an existing ISO image file and/or patch a defective macOS Sierra installer application without prompting for confirmation." + print_error "--nointeraction, Delete an existing ISO image file without prompting for confirmation." print_error print_error "Example: ${const_script_name} --isodirectory ~/Desktop/ --applicationpath \"/Applications/$(create_example_installer_application_name)\"" @@ -1596,6 +1763,8 @@ is_valid_installer_application_path() { ## defective macOS Sierra installer application. ## ## @param $1 An installer application path to be checked. +## @param $2 An installer application display name to be +## checked (e.g. 'Install macOS Sierra'). ## ## @retval `true` The installer application is the defective macOS ## Sierra installer application. @@ -1603,11 +1772,70 @@ is_valid_installer_application_path() { ## is_defective_macos_sierra_installer_application() { local -r installer_application_path="$1" + local -r installer_application_display_name="$2" local installer_application_short_version_string - installer_application_short_version_string="$(get_installer_application_short_version_string "${installer_application_path}")" || return 0 + [[ "${installer_application_display_name}" == "Install macOS Sierra" ]] || return 1 + installer_application_short_version_string="$(get_installer_application_short_version_string "${installer_application_path}")" || return 1 + + [[ "${installer_application_short_version_string}" == "12.6.06" ]] +} + +## +## @brief Determines whether a file contains a Mach-O executable +## for the 'arm64' architecture. +## +## @param $1 A file. +## +## @retval `true` The file contains a Mach-O executable for the +## 'arm64' architecture. +## @retval `false` Otherwise. +## +is_mach_o_arm64_executable() { + local -r file="$1" + local -r arm64_regex='[[:<:]]arm64[[:>:]]' + + # Check whether the list of architectures contains 'arm64' at word + # boundaries. + [[ "$(get_mach_o_executable_architectures "${file}")" =~ $arm64_regex ]] +} + +## +## @brief Determines whether an installer application type requires +## 'createinstallmedia' for the creation of the bootable ISO +## image. +## +## @param $1 An installer application type (e.g. '3'). +## +## @retval `true` The installer application type requires +## 'createinstallmedia'. +## @retval `false` Otherwise. +## +is_createinstallmedia_required() { + local -r installer_application_type="$1" + + case "${installer_application_type}" in + 0 | 1 | 2 ) + return 1 + ;; + + 3 | 4 | * ) + return 0 + ;; + + esac +} - [[ "${installer_application_short_version_string}" == "${const_defective_macos_sierra_installer_application_short_version_string}" ]] +## +## @brief Determines whether a modification of the installer +## application is required. +## +## @retval `true` A modification of the installer application is +## required. +## @retval `false` Otherwise. +## +is_modification_of_installer_application_required() { + [[ ("${global_patch_macos_sierra_installer_application}" == "true") || ("${global_replace_code_signatures_in_installer_application}" == "true") ]] } ## @@ -1794,13 +2022,13 @@ get_disk_image_total_non_empty_bytes() { ## ## @brief Gets the offset for the required disk space on the volume -## where the global temporary directory is located. +## where the temporary directory is located. ## ## @param $1 The installer application display name. ## ## @return The offset in bytes. ## -get_offset_for_global_tmp_directory() { +get_offset_for_tmp_directory() { local -r installer_application_display_name="$1" local item @@ -1875,45 +2103,6 @@ set_installer_application_short_version_string() set_plist_value ":CFBundleShortVersionString" "${value}" "${installer_application_path}/Contents/Info.plist" } -## -## @brief Patches the Info.plist file in the defective macOS Sierra -## installer application and sets the installer application -## path in a global variable for cleanup. -## -## @param $1 The path to the defective macOS Sierra installer -## application. -## -patch_info_plist_in_macos_sierra_installer_application() -{ - local -r installer_application_path="$1" - - is_not_empty "${installer_application_path}" || return 1 - is_not_empty "${global_tmp_directory-}" || return 1 - - mkdir -p "${global_tmp_directory}/${const_macos_sierra_installer_application_info_plist_directory}" || return 1 - cp -p "${installer_application_path}/Contents/Info.plist" "${global_tmp_directory}/${const_macos_sierra_installer_application_info_plist_directory}/" || return 1 - set_installer_application_short_version_string "${installer_application_path}" "${const_patched_macos_sierra_installer_application_short_version_string}" || return 1 - global_patched_macos_sierra_installer_application_path="${installer_application_path}" -} - -## -## @brief Restores the Info.plist file in the defective macOS -## Sierra installer application to the state before it was -## patched. -## -## @param $1 The path to the defective macOS Sierra installer -## application. -## -restore_info_plist_in_macos_sierra_installer_application() -{ - local -r installer_application_path="$1" - - is_not_empty "${installer_application_path}" || return 1 - is_not_empty "${global_tmp_directory-}" || return 1 - - cp -p "${global_tmp_directory}/${const_macos_sierra_installer_application_info_plist_directory}/Info.plist" "${installer_application_path}/Contents/Info.plist" -} - ## ## @brief Gets the 'identifier' of an installer application. ## @@ -2023,6 +2212,10 @@ create_install_media() { is_not_empty "${installer_application_path}" || return 1 is_not_empty "${install_disk_volname}" || return 1 + if [[ "${global_replace_code_signatures_in_installer_application}" == "true" ]]; then + echo "Verifying installer application..." + fi + if has_applicationpath_option "${installer_application_path}"; then applicationpath_option_name="--applicationpath" applicationpath_option_parameter="${installer_application_path}" @@ -2184,6 +2377,30 @@ script_createinstallmedia_canceled_by_sigquit() { [[ "${exit_status}" -eq 3 ]] } +## +## @brief Replaces an existing code signature of all regular files +## in the installer application path with an adhoc +## signature. +## +## @param $1 The path to an installer application. +## +replace_code_signatures() { + local -r installer_application_path="$1" + local file + local -i exit_status + + find "${installer_application_path}" -type f -print0 | while IFS= read -r -d "" file; do + # Check if file has a code signature + codesign -d "${file}" >/dev/null 2>&1 + exit_status="$?" + + # Replace existing code signature with adhoc signature + if [[ "${exit_status}" -eq 0 ]]; then + codesign --sign - --force "${file}" >/dev/null 2>&1 || return 1 + fi + done +} + ## ## @brief Creates an ISO image file from a source image file. ## From c003eae615be7fa3ea825176122a609db5f5560d Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Thu, 31 Oct 2024 14:39:35 +0100 Subject: [PATCH 22/28] Fix unit for offset in bytes --- createinstalliso | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/createinstalliso b/createinstalliso index 2ba35b2..81e395f 100755 --- a/createinstalliso +++ b/createinstalliso @@ -69,7 +69,7 @@ declare -r -a const_supported_macos_version_strings_regex=( # (see 'get_offset_for_tmp_directory' and # 'get_offset_for_iso_directory_path'). The columns are separated by the # '#' character. The disk space offset requirements are specified in -# millions of bytes. +# megabytes. declare -r -a const_supported_installer_application_display_names_and_offsets=( 'Install Mac OS X Lion # # ' # 10.7 'Install OS X Mountain Lion # # ' # 10.8 @@ -2026,7 +2026,7 @@ get_disk_image_total_non_empty_bytes() { ## ## @param $1 The installer application display name. ## -## @return The offset in bytes. +## @return The offset in megabytes. ## get_offset_for_tmp_directory() { local -r installer_application_display_name="$1" @@ -2042,7 +2042,7 @@ get_offset_for_tmp_directory() { ## ## @param $1 The installer application display name. ## -## @return The offset in bytes. +## @return The offset in megabytes. ## get_offset_for_iso_directory_path() { local -r installer_application_display_name="$1" From 3facf209572159b30306e3cacb2632f4638335c4 Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Thu, 31 Oct 2024 21:53:38 +0100 Subject: [PATCH 23/28] Fix incorrect regular expressions --- createinstalliso | 80 ++++++++++++++++++++++++------------------------ 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/createinstalliso b/createinstalliso index 81e395f..ca2c8f1 100755 --- a/createinstalliso +++ b/createinstalliso @@ -38,30 +38,30 @@ declare -r -i const_root_uid=0 # handle large integer values correctly. Therefore, this script requires # Mac OS X 10.6 Snow Leopard or later. declare -r -a const_unsupported_macos_version_strings_regex=( - '^10\.0(\.[[:digit:]+])?$' # Mac OS X Cheetah 10.0 - '^10\.1(\.[[:digit:]+])?$' # Mac OS X Puma 10.1 - '^10\.2(\.[[:digit:]+])?$' # Mac OS X Jaguar 10.2 - '^10\.3(\.[[:digit:]+])?$' # Mac OS X Panther 10.3 - '^10\.4(\.[[:digit:]+])?$' # Mac OS X Tiger 10.4 - '^10\.5(\.[[:digit:]+])?$' # Mac OS X Leopard 10.5 + '^10\.0(\.[[:digit:]]+)?$' # Mac OS X Cheetah 10.0 + '^10\.1(\.[[:digit:]]+)?$' # Mac OS X Puma 10.1 + '^10\.2(\.[[:digit:]]+)?$' # Mac OS X Jaguar 10.2 + '^10\.3(\.[[:digit:]]+)?$' # Mac OS X Panther 10.3 + '^10\.4(\.[[:digit:]]+)?$' # Mac OS X Tiger 10.4 + '^10\.5(\.[[:digit:]]+)?$' # Mac OS X Leopard 10.5 ) # List of all supported macOS versions. declare -r -a const_supported_macos_version_strings_regex=( - '^10\.6(\.[[:digit:]+])?$' # Mac OS X Snow Leopard 10.6 - '^10\.7(\.[[:digit:]+])?$' # Mac OS X Lion 10.7 - '^10\.8(\.[[:digit:]+])?$' # OS X Mountain Lion 10.8 - '^10\.9(\.[[:digit:]+])?$' # OS X Mavericks 10.9 - '^10\.10(\.[[:digit:]+])?$' # OS X Yosemite 10.10 - '^10\.11(\.[[:digit:]+])?$' # OS X El Capitan 10.11 - '^10\.12(\.[[:digit:]+])?$' # macOS Sierra 10.12 - '^10\.13(\.[[:digit:]+])?$' # macOS High Sierra 10.13 - '^10\.14(\.[[:digit:]+])?$' # macOS Mojave 10.14 - '^10\.15(\.[[:digit:]+])?$' # macOS Catalina 10.15 - '^11\.[[:digit:]]+(\.[[:digit:]+])?$' # macOS Big Sur 11 - '^12\.[[:digit:]]+(\.[[:digit:]+])?$' # macOS Monterey 12 - '^13\.[[:digit:]]+(\.[[:digit:]+])?$' # macOS Ventura 13 - '^14\.[[:digit:]]+(\.[[:digit:]+])?$' # macOS Sonoma 14 + '^10\.6(\.[[:digit:]]+)?$' # Mac OS X Snow Leopard 10.6 + '^10\.7(\.[[:digit:]]+)?$' # Mac OS X Lion 10.7 + '^10\.8(\.[[:digit:]]+)?$' # OS X Mountain Lion 10.8 + '^10\.9(\.[[:digit:]]+)?$' # OS X Mavericks 10.9 + '^10\.10(\.[[:digit:]]+)?$' # OS X Yosemite 10.10 + '^10\.11(\.[[:digit:]]+)?$' # OS X El Capitan 10.11 + '^10\.12(\.[[:digit:]]+)?$' # macOS Sierra 10.12 + '^10\.13(\.[[:digit:]]+)?$' # macOS High Sierra 10.13 + '^10\.14(\.[[:digit:]]+)?$' # macOS Mojave 10.14 + '^10\.15(\.[[:digit:]]+)?$' # macOS Catalina 10.15 + '^11\.[[:digit:]]+(\.[[:digit:]]+)?$' # macOS Big Sur 11 + '^12\.[[:digit:]]+(\.[[:digit:]]+)?$' # macOS Monterey 12 + '^13\.[[:digit:]]+(\.[[:digit:]]+)?$' # macOS Ventura 13 + '^14\.[[:digit:]]+(\.[[:digit:]]+)?$' # macOS Sonoma 14 ) # List of all supported installer applications and their disk space @@ -89,26 +89,26 @@ declare -r -a const_supported_installer_application_display_names_and_offsets=( # List of all known macOS version names and their corresponding version # string regex. The columns are separated by the '#' character. declare -r -a const_known_macos_names_and_version_strings_regex=( - '^10\.0(\.[[:digit:]+])?$ # Mac OS X Cheetah' # 10.0 - '^10\.1(\.[[:digit:]+])?$ # Mac OS X Puma' # 10.1 - '^10\.2(\.[[:digit:]+])?$ # Mac OS X Jaguar' # 10.2 - '^10\.3(\.[[:digit:]+])?$ # Mac OS X Panther' # 10.3 - '^10\.4(\.[[:digit:]+])?$ # Mac OS X Tiger' # 10.4 - '^10\.5(\.[[:digit:]+])?$ # Mac OS X Leopard' # 10.5 - '^10\.6(\.[[:digit:]+])?$ # Mac OS X Snow Leopard' # 10.6 - '^10\.7(\.[[:digit:]+])?$ # Mac OS X Lion' # 10.7 - '^10\.8(\.[[:digit:]+])?$ # OS X Mountain Lion' # 10.8 - '^10\.9(\.[[:digit:]+])?$ # OS X Mavericks' # 10.9 - '^10\.10(\.[[:digit:]+])?$ # OS X Yosemite' # 10.10 - '^10\.11(\.[[:digit:]+])?$ # OS X El Capitan' # 10.11 - '^10\.12(\.[[:digit:]+])?$ # macOS Sierra' # 10.12 - '^10\.13(\.[[:digit:]+])?$ # macOS High Sierra' # 10.13 - '^10\.14(\.[[:digit:]+])?$ # macOS Mojave' # 10.14 - '^10\.15(\.[[:digit:]+])?$ # macOS Catalina' # 10.15 - '^11\.[[:digit:]]+(\.[[:digit:]+])?$ # macOS Big Sur' # 11 - '^12\.[[:digit:]]+(\.[[:digit:]+])?$ # macOS Monterey' # 12 - '^13\.[[:digit:]]+(\.[[:digit:]+])?$ # macOS Ventura' # 13 - '^14\.[[:digit:]]+(\.[[:digit:]+])?$ # macOS Sonoma' # 14 + '^10\.0(\.[[:digit:]]+)?$ # Mac OS X Cheetah' # 10.0 + '^10\.1(\.[[:digit:]]+)?$ # Mac OS X Puma' # 10.1 + '^10\.2(\.[[:digit:]]+)?$ # Mac OS X Jaguar' # 10.2 + '^10\.3(\.[[:digit:]]+)?$ # Mac OS X Panther' # 10.3 + '^10\.4(\.[[:digit:]]+)?$ # Mac OS X Tiger' # 10.4 + '^10\.5(\.[[:digit:]]+)?$ # Mac OS X Leopard' # 10.5 + '^10\.6(\.[[:digit:]]+)?$ # Mac OS X Snow Leopard' # 10.6 + '^10\.7(\.[[:digit:]]+)?$ # Mac OS X Lion' # 10.7 + '^10\.8(\.[[:digit:]]+)?$ # OS X Mountain Lion' # 10.8 + '^10\.9(\.[[:digit:]]+)?$ # OS X Mavericks' # 10.9 + '^10\.10(\.[[:digit:]]+)?$ # OS X Yosemite' # 10.10 + '^10\.11(\.[[:digit:]]+)?$ # OS X El Capitan' # 10.11 + '^10\.12(\.[[:digit:]]+)?$ # macOS Sierra' # 10.12 + '^10\.13(\.[[:digit:]]+)?$ # macOS High Sierra' # 10.13 + '^10\.14(\.[[:digit:]]+)?$ # macOS Mojave' # 10.14 + '^10\.15(\.[[:digit:]]+)?$ # macOS Catalina' # 10.15 + '^11\.[[:digit:]]+(\.[[:digit:]]+)?$ # macOS Big Sur' # 11 + '^12\.[[:digit:]]+(\.[[:digit:]]+)?$ # macOS Monterey' # 12 + '^13\.[[:digit:]]+(\.[[:digit:]]+)?$ # macOS Ventura' # 13 + '^14\.[[:digit:]]+(\.[[:digit:]]+)?$ # macOS Sonoma' # 14 ) declare global_patch_macos_sierra_installer_application="false" From 0e17a3c31bbdff8d14c5996642eace743cfaec85 Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Thu, 31 Oct 2024 22:19:08 +0100 Subject: [PATCH 24/28] Fix code formatting for functions --- createinstalliso | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/createinstalliso b/createinstalliso index ca2c8f1..02c5df7 100755 --- a/createinstalliso +++ b/createinstalliso @@ -1524,7 +1524,7 @@ exit_missing_option_parameter_for_option() { ## @param $1 The long option as provided on the command line ## (including leading '--'). ## -exit_no_option_parameter_allowed_for_long_option(){ +exit_no_option_parameter_allowed_for_long_option() { local -r long_option="$1" print_error "${const_script_name}: option \`${long_option}' doesn't allow an argument" @@ -1904,8 +1904,7 @@ get_installer_application_display_name() { ## ## @return The type of the installer application. ## -get_installer_application_type() -{ +get_installer_application_type() { local -r installer_application_path="$1" local -i installer_application_type=0 @@ -2076,8 +2075,7 @@ get_installer_application_minimum_system_version() { ## ## @return The installer application 'short version string'. ## -get_installer_application_short_version_string() -{ +get_installer_application_short_version_string() { local -r installer_application_path="$1" is_not_empty "${installer_application_path}" || return 1 @@ -2092,8 +2090,7 @@ get_installer_application_short_version_string() ## @param $1 The path to an installer application. ## @param $2 The new value for the 'short version string'. ## -set_installer_application_short_version_string() -{ +set_installer_application_short_version_string() { local -r installer_application_path="$1" local -r value="$2" @@ -2110,8 +2107,7 @@ set_installer_application_short_version_string() ## ## @return The installer application 'identifier'. ## -get_installer_application_identifier() -{ +get_installer_application_identifier() { local -r installer_application_path="$1" is_not_empty "${installer_application_path}" || return 1 @@ -2127,8 +2123,7 @@ get_installer_application_identifier() ## ## @return The URL of the system image. ## -get_system_image_url() -{ +get_system_image_url() { local -r installer_application_path="$1" is_not_empty "${installer_application_path}" || return 1 From 842ea40d232bb4019cfac00f2da802e8a78f6094 Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Fri, 1 Nov 2024 11:39:52 +0100 Subject: [PATCH 25/28] Reformat tables in README.md --- README.md | 116 +++++++++++++++++++++++++++--------------------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 94102af..581427a 100644 --- a/README.md +++ b/README.md @@ -51,27 +51,27 @@ The table below shows: * Which macOS installers can be used to create an ISO image with this particular macOS version. | macOS | Version | Can run **createinstalliso** | Can use installer for | -| --------------------- | ------- |:----------------------------:| --------------------- | -| Mac OS X Cheetah | 10.0 | No | N/A | -| Mac OS X Puma | 10.1 | No | N/A | -| Mac OS X Jaguar | 10.2 | No | N/A | -| Mac OS X Panther | 10.3 | No | N/A | -| Mac OS X Tiger | 10.4 | No | N/A | -| Mac OS X Leopard | 10.5 | No | N/A | -| Mac OS X Snow Leopard | 10.6 | Yes | 10.7 - 10.11 | -| Mac OS X Lion | 10.7 | Yes | 10.7 - 10.11 | -| OS X Mountain Lion | 10.8 | Yes | 10.7 - 10.15 | -| OS X Mavericks | 10.9 | Yes | 10.7 - 12 | -| OS X Yosemite | 10.10 | Yes | 10.7 - 12 | -| OS X El Capitan | 10.11 | Yes | 10.7 - 13 | -| macOS Sierra | 10.12 | Yes | 10.7 - 13 | -| macOS High Sierra | 10.13 | Yes | 10.7 - 14 | -| macOS Mojave | 10.14 | Yes | 10.7 - 14 | -| macOS Catalina | 10.15 | Yes | 10.7 - 14 | -| macOS Big Sur | 11 | Yes | 10.7 - 14 | -| macOS Monterey | 12 | Yes | 10.7 - 14 | -| macOS Ventura | 13 | Yes | 10.7 - 14 | -| macOS Sonoma | 14 | Yes | 10.7 - 14 | +|-----------------------|---------|:----------------------------:|-----------------------| +| Mac OS X Cheetah | 10.0 | No | N/A | +| Mac OS X Puma | 10.1 | No | N/A | +| Mac OS X Jaguar | 10.2 | No | N/A | +| Mac OS X Panther | 10.3 | No | N/A | +| Mac OS X Tiger | 10.4 | No | N/A | +| Mac OS X Leopard | 10.5 | No | N/A | +| Mac OS X Snow Leopard | 10.6 | Yes | 10.7 - 10.11 | +| Mac OS X Lion | 10.7 | Yes | 10.7 - 10.11 | +| OS X Mountain Lion | 10.8 | Yes | 10.7 - 10.15 | +| OS X Mavericks | 10.9 | Yes | 10.7 - 12 | +| OS X Yosemite | 10.10 | Yes | 10.7 - 12 | +| OS X El Capitan | 10.11 | Yes | 10.7 - 13 | +| macOS Sierra | 10.12 | Yes | 10.7 - 13 | +| macOS High Sierra | 10.13 | Yes | 10.7 - 14 | +| macOS Mojave | 10.14 | Yes | 10.7 - 14 | +| macOS Catalina | 10.15 | Yes | 10.7 - 14 | +| macOS Big Sur | 11 | Yes | 10.7 - 14 | +| macOS Monterey | 12 | Yes | 10.7 - 14 | +| macOS Ventura | 13 | Yes | 10.7 - 14 | +| macOS Sonoma | 14 | Yes | 10.7 - 14 | ### Installer version @@ -80,27 +80,27 @@ The table below shows: * Which macOS version is required to use **createinstalliso** with a particular macOS installer. | Installer for | Version | Required macOS version | -| --------------------- | ------- |:----------------------:| -| Mac OS X Cheetah | 10.0 | N/A | -| Mac OS X Puma | 10.1 | N/A | -| Mac OS X Jaguar | 10.2 | N/A | -| Mac OS X Panther | 10.3 | N/A | -| Mac OS X Tiger | 10.4 | N/A | -| Mac OS X Leopard | 10.5 | N/A | -| Mac OS X Snow Leopard | 10.6 | N/A | -| Mac OS X Lion | 10.7 | 10.6 or later | -| OS X Mountain Lion | 10.8 | 10.6 or later | -| OS X Mavericks | 10.9 | 10.6 or later | -| OS X Yosemite | 10.10 | 10.6 or later | -| OS X El Capitan | 10.11 | 10.6 or later | -| macOS Sierra | 10.12 | 10.8 or later | -| macOS High Sierra | 10.13 | 10.8 or later | -| macOS Mojave | 10.14 | 10.8 or later | -| macOS Catalina | 10.15 | 10.8 or later | -| macOS Big Sur | 11 | 10.9 or later | -| macOS Monterey | 12 | 10.9 or later | -| macOS Ventura | 13 | 10.11 or later | -| macOS Sonoma | 14 | 10.13 or later | +|-----------------------|---------|:----------------------:| +| Mac OS X Cheetah | 10.0 | N/A | +| Mac OS X Puma | 10.1 | N/A | +| Mac OS X Jaguar | 10.2 | N/A | +| Mac OS X Panther | 10.3 | N/A | +| Mac OS X Tiger | 10.4 | N/A | +| Mac OS X Leopard | 10.5 | N/A | +| Mac OS X Snow Leopard | 10.6 | N/A | +| Mac OS X Lion | 10.7 | 10.6 or later | +| OS X Mountain Lion | 10.8 | 10.6 or later | +| OS X Mavericks | 10.9 | 10.6 or later | +| OS X Yosemite | 10.10 | 10.6 or later | +| OS X El Capitan | 10.11 | 10.6 or later | +| macOS Sierra | 10.12 | 10.8 or later | +| macOS High Sierra | 10.13 | 10.8 or later | +| macOS Mojave | 10.14 | 10.8 or later | +| macOS Catalina | 10.15 | 10.8 or later | +| macOS Big Sur | 11 | 10.9 or later | +| macOS Monterey | 12 | 10.9 or later | +| macOS Ventura | 13 | 10.11 or later | +| macOS Sonoma | 14 | 10.13 or later | ## Installation @@ -193,21 +193,21 @@ Apple has used different internal structures for its macOS installers over time. The installer application types are: -| Name | Version | Type | -| --------------------- | ------- |:----:| -| Mac OS X Lion | 10.7 | 1 | -| OS X Mountain Lion | 10.8 | 1 | -| OS X Mavericks | 10.9 | 2 | -| OS X Yosemite | 10.10 | 2 | -| OS X El Capitan | 10.11 | 2 | -| macOS Sierra | 10.12 | 3 | -| macOS High Sierra | 10.13 | 3 | -| macOS Mojave | 10.14 | 3 | -| macOS Catalina | 10.15 | 3 | -| macOS Big Sur | 11 | 4 | -| macOS Monterey | 12 | 4 | -| macOS Ventura | 13 | 4 | -| macOS Sonoma | 14 | 4 | +| Name | Version | Type | +|--------------------|---------|:----:| +| Mac OS X Lion | 10.7 | 1 | +| OS X Mountain Lion | 10.8 | 1 | +| OS X Mavericks | 10.9 | 2 | +| OS X Yosemite | 10.10 | 2 | +| OS X El Capitan | 10.11 | 2 | +| macOS Sierra | 10.12 | 3 | +| macOS High Sierra | 10.13 | 3 | +| macOS Mojave | 10.14 | 3 | +| macOS Catalina | 10.15 | 3 | +| macOS Big Sur | 11 | 4 | +| macOS Monterey | 12 | 4 | +| macOS Ventura | 13 | 4 | +| macOS Sonoma | 14 | 4 | ### Required external commands @@ -248,7 +248,7 @@ A failure of **createinstalliso** is indicated by a non-zero exit status. Wherev The table below lists all possible exit status and corresponding messages: | Status | Message | -| ------ | ------------------------------------------------------------------------------------- | +|--------|---------------------------------------------------------------------------------------| | 255 | You must specify both the ISO directory and install application path. | | 255 | createinstalliso: unrecognized option \`\[OPTION\]' | | 255 | createinstalliso: invalid option -- \[OPTION\] | From de50248ca83488ba3dc3c3c15c70bf819b0b57a6 Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Fri, 1 Nov 2024 11:50:36 +0100 Subject: [PATCH 26/28] Use unordered list for table of contents in README.md --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 581427a..2b28e28 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,20 @@ Creates a bootable ISO image from a macOS installer application. This image can ## Table of Contents -1. [Introduction](#user-content-introduction) -2. [Requirements](#user-content-requirements) -3. [Compatibility](#user-content-compatibility) -4. [Installation](#user-content-installation) -5. [Usage](#user-content-usage) - 1. [Command line arguments](#user-content-command-line-arguments) - 2. [Example](#user-content-example) -6. [Troubleshooting](#user-content-troubleshooting) - 1. [Alert during macOS installation](#user-content-alert-during-macos-installation) -7. [References](#user-content-references) - 1. [Installer application types](#user-content-installer-application-types) - 2. [Required external commands](#user-content-required-external-commands) - 3. [Exit status and messages](#user-content-exit-status-and-messages) -8. [License](#user-content-license) +- [Introduction](#user-content-introduction) +- [Requirements](#user-content-requirements) +- [Compatibility](#user-content-compatibility) +- [Installation](#user-content-installation) +- [Usage](#user-content-usage) + - [Command line arguments](#user-content-command-line-arguments) + - [Example](#user-content-example) +- [Troubleshooting](#user-content-troubleshooting) + - [Alert during macOS installation](#user-content-alert-during-macos-installation) +- [References](#user-content-references) + - [Installer application types](#user-content-installer-application-types) + -[Required external commands](#user-content-required-external-commands) + - [Exit status and messages](#user-content-exit-status-and-messages) +- [License](#user-content-license) ## Introduction From a43213489bfb8604148231621184d62f62d812c7 Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Sat, 2 Nov 2024 21:41:19 +0100 Subject: [PATCH 27/28] Add support for macOS Sequoia --- README.md | 17 ++++++++++------- createinstalliso | 10 +++++++++- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2b28e28..ab326a1 100644 --- a/README.md +++ b/README.md @@ -65,13 +65,14 @@ The table below shows: | OS X Yosemite | 10.10 | Yes | 10.7 - 12 | | OS X El Capitan | 10.11 | Yes | 10.7 - 13 | | macOS Sierra | 10.12 | Yes | 10.7 - 13 | -| macOS High Sierra | 10.13 | Yes | 10.7 - 14 | -| macOS Mojave | 10.14 | Yes | 10.7 - 14 | -| macOS Catalina | 10.15 | Yes | 10.7 - 14 | -| macOS Big Sur | 11 | Yes | 10.7 - 14 | -| macOS Monterey | 12 | Yes | 10.7 - 14 | -| macOS Ventura | 13 | Yes | 10.7 - 14 | -| macOS Sonoma | 14 | Yes | 10.7 - 14 | +| macOS High Sierra | 10.13 | Yes | 10.7 - 15 | +| macOS Mojave | 10.14 | Yes | 10.7 - 15 | +| macOS Catalina | 10.15 | Yes | 10.7 - 15 | +| macOS Big Sur | 11 | Yes | 10.7 - 15 | +| macOS Monterey | 12 | Yes | 10.7 - 15 | +| macOS Ventura | 13 | Yes | 10.7 - 15 | +| macOS Sonoma | 14 | Yes | 10.7 - 15 | +| macOS Sequoia | 15 | Yes | 10.7 - 15 | ### Installer version @@ -101,6 +102,7 @@ The table below shows: | macOS Monterey | 12 | 10.9 or later | | macOS Ventura | 13 | 10.11 or later | | macOS Sonoma | 14 | 10.13 or later | +| macOS Sequoia | 15 | 10.13 or later | ## Installation @@ -208,6 +210,7 @@ The installer application types are: | macOS Monterey | 12 | 4 | | macOS Ventura | 13 | 4 | | macOS Sonoma | 14 | 4 | +| macOS Sequoia | 15 | 4 | ### Required external commands diff --git a/createinstalliso b/createinstalliso index 02c5df7..25063bc 100755 --- a/createinstalliso +++ b/createinstalliso @@ -62,6 +62,7 @@ declare -r -a const_supported_macos_version_strings_regex=( '^12\.[[:digit:]]+(\.[[:digit:]]+)?$' # macOS Monterey 12 '^13\.[[:digit:]]+(\.[[:digit:]]+)?$' # macOS Ventura 13 '^14\.[[:digit:]]+(\.[[:digit:]]+)?$' # macOS Sonoma 14 + '^15\.[[:digit:]]+(\.[[:digit:]]+)?$' # macOS Sequoia 15 ) # List of all supported installer applications and their disk space @@ -84,6 +85,7 @@ declare -r -a const_supported_installer_application_display_names_and_offsets=( 'Install macOS Monterey # 1900 # 1300' # 12 'Install macOS Ventura # 1450 # 1300' # 13 'Install macOS Sonoma # 2100 # 1350' # 14 + 'Install macOS Sequoia # 2250 # 1350' # 15 ) # List of all known macOS version names and their corresponding version @@ -109,6 +111,7 @@ declare -r -a const_known_macos_names_and_version_strings_regex=( '^12\.[[:digit:]]+(\.[[:digit:]]+)?$ # macOS Monterey' # 12 '^13\.[[:digit:]]+(\.[[:digit:]]+)?$ # macOS Ventura' # 13 '^14\.[[:digit:]]+(\.[[:digit:]]+)?$ # macOS Sonoma' # 14 + '^15\.[[:digit:]]+(\.[[:digit:]]+)?$ # macOS Sequoia' # 15 ) declare global_patch_macos_sierra_installer_application="false" @@ -447,6 +450,7 @@ main() { # - macOS Monterey 12 # - macOS Ventura 13 # - macOS Sonoma 14 + # - macOS Sequoia 15 3 | 4 ) (( offset_for_tmp_directory = $(get_offset_for_tmp_directory "${installer_application_display_name}") )) || offset_for_tmp_directory=0 (( required_in_tmp_directory = $(get_disk_space_used_by_file_or_directory "${installer_application_path}") )) @@ -590,6 +594,7 @@ main() { # - macOS Monterey 12 # - macOS Ventura 13 # - macOS Sonoma 14 + # - macOS Sequoia 15 3 | 4 ) echo "Creating empty disk image..." install_disk_image="${global_tmp_directory}/InstallDisk.sparseimage" @@ -1733,6 +1738,7 @@ is_valid_installer_application_path() { # - macOS Monterey 12 # - macOS Ventura 13 # - macOS Sonoma 14 + # - macOS Sequoia 15 4 ) # Apple's command 'createinstallmedia' is required because # it will be used to create the installer disk image. @@ -1899,6 +1905,7 @@ get_installer_application_display_name() { ## - macOS Monterey 12 ## - macOS Ventura 13 ## - macOS Sonoma 14 +## - macOS Sequoia 15 ## ## @param $1 The path to an installer application. ## @@ -1983,7 +1990,8 @@ get_minimum_macos_version_string() { # Installer application for: # - macOS Sonoma 14 - "Install macOS Sonoma" ) + # - macOS Sequoia 15 + "Install macOS Sonoma" | "Install macOS Sequoia" ) minimum_macos_version_string="10.13" ;; From 59f354386713161ab6d4992fac52ccbc082854ee Mon Sep 17 00:00:00 2001 From: Michael Berger Date: Sun, 3 Nov 2024 20:58:53 +0100 Subject: [PATCH 28/28] Add script version to source code --- createinstalliso | 1 + 1 file changed, 1 insertion(+) diff --git a/createinstalliso b/createinstalliso index 25063bc..a7417fc 100755 --- a/createinstalliso +++ b/createinstalliso @@ -27,6 +27,7 @@ fi # ====================================================================== declare -r const_script_name="createinstalliso" +declare -r const_script_version="1.1.0" declare -r const_minimum_required_macos_version_string="10.6" declare -r -i const_root_uid=0