#!/usr/bin/env bash # Copyright (c) 2024–2025, LongQT-sea # mkmaciso – the ultimate tool for creating macOS installer ISO and DMG images # Run with -h or --help to show usage. # Goals: # - Use only macOS built-in tools and commands. # - Download macOS installers exclusively from official Apple sources. # - Support macOS 10.7 Lion through macOS 26 Tahoe installers. # - Produce the smallest possible installer images for both formats. # - The image must be usable on Windows and Linux. # # ISO image: # - A proper UDF DVD/CD format image (mountable in Windows). # - Intended for virtual machine use; attach as a virtual CD/DVD drive. # - Compatible with Proxmox VE, QEMU, VirtualBox, and VMware. # # DMG image: # - Raw disk image with GUID Partition Table, this is mandatory so it can be flash to a USB drive using Rufus (Windows). # - Can also be used with virtual machines, but must be attached as a virtual hard disk. # - Most VMM require convert the .dmg image to a compatible virtual disk format using qemu-img, # e.g. convert to .vhd for Hyper-V or .vmdk for VMware. QEMU can use raw disk image without conversion. # # For easier use and distribution, the final DMG image will have '.img' appended to its file name. # Rufus no longer require switching to "All files" to saw the DMG image in Explorer. # Also fix "qemu-img: Could not locate UDIF trailer in dmg file" error. # # While the script is compatible with Apple silicon Macs, it’s still recommended to run it on an x86_64 Intel Mac for optimal results. set -e # Exit on error #set -x # Debug # Official Apple download URLs for macOS 10.7-10.12 (except 10.9) # https://support.apple.com/en-hk/102662#browser LION_URL="https://updates.cdn-apple.com/2021/macos/041-7683-20210614-E610947E-C7CE-46EB-8860-D26D71F0D3EA/InstallMacOSX.dmg" MOUNTAIN_LION_URL="https://updates.cdn-apple.com/2021/macos/031-0627-20210614-90D11F33-1A65-42DD-BBEA-E1D9F43A6B3F/InstallMacOSX.dmg" YOSEMITE_URL="http://updates-http.cdn-apple.com/2019/cert/061-41343-20191023-02465f92-3ab5-4c92-bfe2-b725447a070d/InstallMacOSX.dmg" EL_CAPITAN_URL="http://updates-http.cdn-apple.com/2019/cert/061-41424-20191024-218af9ec-cf50-4516-9011-228c78eda3d2/InstallMacOSX.dmg" SIERRA_URL="http://updates-http.cdn-apple.com/2019/cert/061-39476-20191023-48f365f4-0015-4c41-9f44-39d3d2aca067/InstallOS.dmg" # Official Apple download URLs for macOS 10.13-10.15 # https://swscan.apple.com/content/catalogs/others/index-15-14-13-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog CATALINA_BASE_URL="https://swcdn.apple.com/content/downloads/26/37/001-68446/r1dbqtmf3mtpikjnd04cq31p4jk91dceh8/" MOJAVE_BASE_URL="https://swcdn.apple.com/content/downloads/17/32/061-26589-A_8GJTCGY9PC/25fhcu905eta7wau7aoafu8rvdm7k1j4el/" HIGH_SIERRA_BASE_URL="https://swcdn.apple.com/content/downloads/06/50/041-91758-A_M8T44LH2AW/b5r4og05fhbgatve4agwy4kgkzv07mdid9/" # Default parameters VERSION="${1:-}" IMAGE_FORMAT="${2:-}" OUTPUT_PATH="${3:-}" RETRIES_COUNT=10 FINAL_OUTPUT_PATH="" DISK_ID="" # Get CPU architecture CPU_ARCH=$(uname -m) # Get current macOS kernel version KERNEL_VERSION=$(uname -r | cut -d'.' -f1) # Color codes for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m' # No Color # Run on macOS only [ "$(uname -s)" != "Darwin" ] && echo "${RED}Error: macOS only${NC}" && exit 1 # Requires Mavericks (10.9) or newer to run this script if [ "$KERNEL_VERSION" -lt 13 ]; then echo "${RED}Unsupported macOS version. Requires Mavericks (10.9) or newer${NC}" exit 1 fi # Helper functions log_info() { echo -e "${GREEN}$(date +'%H:%M:%S') [INFO]${NC} $1" } log_warn() { echo -e "${YELLOW}$(date +'%H:%M:%S') [WARN]${NC} $1" } log_error() { echo -e "${RED}$(date +'%H:%M:%S') [ERROR]${NC} $1" } check_sudo_access() { if ! sudo -v; then log_error "This script requires administrator privileges" exit 1 fi # Keep sudo alive while true; do sudo -n true; sleep 60; kill -0 "$$" || exit; done 2>/dev/null & } check_disk_space() { local required_gb required_gb=40 local threshold_gb threshold_gb=15 local available available=$(df -g . | awk 'NR==2 {print $4}') if [ "$available" -lt "$threshold_gb" ]; then log_error "CRITICAL: Only ${available} GB available, (${required_gb} GB or more recommended" log_error "Cannot continue due to low disk space" exit 1 elif [ "$available" -lt "$required_gb" ]; then log_error "WARNING: Low disk space - ${available} GB available, ${required_gb} GB or more recommended" echo "" echo -ne "${YELLOW}[WARN]${NC} Continue? (Y/n - auto-yes in 10 seconds): " read -t 10 answer || answer="y" case "$answer" in [Nn]|[Nn][Oo]) log_error "Aborted" exit 1 ;; *) log_info "Continuing..." echo "" ;; esac fi } detach_disk() { local disk disk="$1" [ -z "$disk" ] && return 0 for attempt in $(seq 1 "$RETRIES_COUNT"); do log_info "Detach attempt $attempt of $RETRIES_COUNT..." if sync && sleep 5 && hdiutil detach -quiet "$disk" || hdiutil detach -quiet "$disk" -force; then log_info "Disk detached successfully" return 0 fi [ "$attempt" -lt "$RETRIES_COUNT" ] && log_warn "Detach failed, retrying..." done log_error "Failed to detach $disk after $RETRIES_COUNT attempts" return 1 } cleanup() { # Only clean up if WORK_DIR is defined and not root/home if [ -n "$WORK_DIR" ] && [ -d "$WORK_DIR" ] && [ "$WORK_DIR" != "/" ] && [ "$WORK_DIR" != "$HOME" ]; then # Unmount for mnt in "$WORK_DIR"/*_mnt; do [ -d "$mnt" ] && detach_disk "$mnt" 2>/dev/null || true done if [ -n "${DISK_ID:-}" ]; then detach_disk "$DISK_ID" fi log_info "Cleaning up temporary files..." sudo rm -rf "$WORK_DIR" fi } trap cleanup EXIT get_version_number() { local codename codename="$1" case "$codename" in "lion") echo "10.7" ;; "mountainlion") echo "10.8" ;; "mavericks") echo "10.9" ;; "yosemite") echo "10.10" ;; "elcapitan") echo "10.11" ;; "sierra") echo "10.12" ;; "highsierra") echo "10.13" ;; "mojave") echo "10.14" ;; "catalina") echo "10.15" ;; "bigsur") echo "11" ;; "monterey") echo "12" ;; "ventura") echo "13" ;; "sonoma") echo "14" ;; "sequoia") echo "15" ;; "tahoe") echo "26" ;; *) echo "" ;; esac } get_codename() { local version version="$1" case "$version" in "10.7") echo "Lion" ;; "10.8") echo "Mountain_Lion" ;; "10.9") echo "Mavericks" ;; "10.10") echo "Yosemite" ;; "10.11") echo "El_Capitan" ;; "10.12") echo "Sierra" ;; "10.13") echo "High_Sierra" ;; "10.14") echo "Mojave" ;; "10.15") echo "Catalina" ;; "11") echo "Big_Sur" ;; "12") echo "Monterey" ;; "13") echo "Ventura" ;; "14") echo "Sonoma" ;; "15") echo "Sequoia" ;; "26") echo "Tahoe" ;; *) echo "" ;; esac } get_installer_app_name() { local version_num version_num="$1" case "$version_num" in "10.7") echo "Install Mac OS X Lion" ;; "10.8") echo "Install OS X Mountain Lion" ;; "10.9") echo "Install OS X Mavericks" ;; "10.10") echo "Install OS X Yosemite" ;; "10.11") echo "Install OS X El Capitan" ;; "10.12") echo "Install macOS Sierra" ;; "10.13") echo "Install macOS High Sierra" ;; "10.14") echo "Install macOS Mojave" ;; "10.15") echo "Install macOS Catalina" ;; "11") echo "Install macOS Big Sur" ;; "12") echo "Install macOS Monterey" ;; "13") echo "Install macOS Ventura" ;; "14") echo "Install macOS Sonoma" ;; "15") echo "Install macOS Sequoia" ;; "26") echo "Install macOS Tahoe" ;; *) echo "" ;; esac } # ========================== # Interactive Menu Functions # ========================== print_header() { clear echo -e "${BOLD}${CYAN}" echo "╔══════════════════════════════════════════════════════════════════════════════╗" echo "║ mkmaciso – the ultimate tool for creating macOS installer ISO and DMG images ║" echo "╠══════════════════════════════════════════════════════════════════════════════╣" echo "║ Interactive mode ║" echo "╚══════════════════════════════════════════════════════════════════════════════╝" echo -e "${NC}" } print_separator() { echo -e "${CYAN}──────────────────────────────────────────────────────────────────────${NC}" } # Array of macOS versions for the menu declare -a MACOS_VERSIONS=( "10.7|Lion|2011" "10.8|Mountain Lion|2012" "10.9|Mavericks|2013" "10.10|Yosemite|2014" "10.11|El Capitan|2015" "10.12|Sierra|2016" "10.13|High Sierra|2017" "10.14|Mojave|2018" "10.15|Catalina|2019" "11|Big Sur|2020" "12|Monterey|2021" "13|Ventura|2022" "14|Sonoma|2023" "15|Sequoia|2024" "26|Tahoe|2025" ) show_version_menu() { print_header echo -e "${BOLD}Step 1/3: Select macOS Version${NC}" print_separator echo "" local i i=1 local col col=0 # Print versions in two columns echo -e " ${BOLD}# Version Name Year${NC} ${BOLD}# Version Name Year${NC}" print_separator local total total=${#MACOS_VERSIONS[@]} local half half=$(( (total + 1) / 2 )) for ((i=0; i/dev/null | od -An -tx1 | tr -d ' \n' | tr '[:lower:]' '[:upper:]') # Create key_info file { hex_to_bin "$client_id" hex_to_bin "$(echo $server_id | awk -F'~' '{print $2}')" hex_to_bin "$rom" printf "%s" "${board_serial_number}${board_id}" | iconv -t utf-8 | openssl dgst -sha256 -binary printf '\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC' } > "$WORK_DIR/key_info" # Generate key local key key=$(openssl dgst -sha256 -binary < "$WORK_DIR/key_info" | od -An -tx1 | tr -d ' \n' | tr '[:lower:]' '[:upper:]') rm "$WORK_DIR/key_info" # Get installation payload local installation_payload installation_payload=$(curl -fs 'http://osrecovery.apple.com/InstallationPayload/OSInstaller' -X POST \ -H 'Content-Type: text/plain' \ --cookie "session=$server_id" \ -d "cid=$client_id sn=$board_serial_number bid=$board_id k=$key") # Extract asset URL and token local mavericks_url mavericks_url=$(echo "$installation_payload" | grep AU | awk -F': ' '{print $2}') local token token=$(echo "$installation_payload" | grep AT | awk -F': ' '{print $2}') log_info "Mavericks URL detected: $mavericks_url" echo "" log_info "Downloading macOS Mavericks using special method..." # Download Mavericks InstallESD.dmg # If download does not finish within 30 minutes (--max-time 1800), redownload to establish a new connection. curl --retry 5 --max-time 1800 --connect-timeout 10 --progress-bar -L "$mavericks_url" -H "Cookie: AssetToken=$token" -o "$WORK_DIR/InstallESD.dmg" } install_legacy_app() { local version_num version_num="$1" local dmg_file dmg_file="$2" log_info "Installing macOS $(get_codename $version_num) installer to /Applications..." if [[ "$version_num" =~ ^(10\.7|10\.8|10\.10|10\.11|10\.12)$ ]]; then # Mount the downloaded DMG image hdiutil attach -nobrowse -quiet -mountpoint "$WORK_DIR/downloaded_dmg_mnt" "$dmg_file" # Manual installation process for Apple silicon. # The 'Install OSX/macOS.app' produced by manual install is not suitable # for creating a bootable USB using 'createinstallmedia'. if [ "$CPU_ARCH" == "arm64" ]; then log_info "Apple silicon detected, using manual installation..." # Extract the package pkgutil --expand "$WORK_DIR/downloaded_dmg_mnt"/*.pkg "$WORK_DIR/pkg_extracted" # Find the inner package directory (InstallMacOSX.pkg) local inner_pkg inner_pkg=$(ls -d "$WORK_DIR/pkg_extracted"/*.pkg 2>/dev/null | head -1) # Extract the app from Payload to /Applications cd /Applications sudo cpio -idm < "$inner_pkg/Payload" cd - > /dev/null # Copy InstallESD.dmg local app_path app_path="/Applications/$(get_installer_app_name $version_num).app" sudo ditto "$inner_pkg/InstallESD.dmg" "$app_path/Contents/SharedSupport/InstallESD.dmg" sudo chown root:wheel "$app_path/Contents/SharedSupport/InstallESD.dmg" log_info "Installed $(get_installer_app_name $version_num) to /Applications" log_warn "Note: This $(get_installer_app_name $version_num) is not suitable for creating a bootable USB using 'createinstallmedia'" else # Standard installation for Intel Macs sudo installer -pkg "$WORK_DIR/downloaded_dmg_mnt"/*.pkg -target / && \ log_info "Installed $(get_installer_app_name $version_num) to /Applications" fi sync && detach_disk "$WORK_DIR/downloaded_dmg_mnt" elif [ "$version_num" == "10.9" ]; then # Check if InstallESD.dmg exists if [ -f "$WORK_DIR/InstallESD.dmg" ]; then # Mount InstallESD.dmg and BaseSystem.dmg inside it hdiutil attach -nobrowse -quiet -mountpoint "$WORK_DIR/InstallESD_mnt" "$WORK_DIR/InstallESD.dmg" hdiutil attach -nobrowse -quiet -mountpoint "$WORK_DIR/BaseSystem_mnt" "$WORK_DIR/InstallESD_mnt/BaseSystem.dmg" # Copy required files sudo cp -a "$WORK_DIR/BaseSystem_mnt/Install OS X Mavericks.app" "/Applications/" sudo mkdir "/Applications/Install OS X Mavericks.app/Contents/SharedSupport" sudo cp -a "$WORK_DIR/InstallESD.dmg" "/Applications/Install OS X Mavericks.app/Contents/SharedSupport/InstallESD.dmg" sudo cp -a "$WORK_DIR/InstallESD_mnt/Packages/OSInstall.mpkg" "/Applications/Install OS X Mavericks.app/Contents/SharedSupport/" sudo chown -R root:wheel "/Applications/Install OS X Mavericks.app/Contents/SharedSupport/" log_info "Installed $(get_installer_app_name $version_num) to /Applications" # Unmount sync && detach_disk "$WORK_DIR/BaseSystem_mnt" sync && detach_disk "$WORK_DIR/InstallESD_mnt" fi fi } # Reference: https://www.insanelymac.com/forum/topic/338810-create-legit-copy-of-macos-from-apple-catalog/ direct_download_10_13_10_15() { local version_num version_num="$1" local base_url base_url="" # Determine base URL and version name case "$version_num" in "10.13") base_url="$HIGH_SIERRA_BASE_URL" ;; "10.14") base_url="$MOJAVE_BASE_URL" ;; "10.15") base_url="$CATALINA_BASE_URL" ;; *) return 1 ;; esac log_info "Downloading macOS $(get_codename $version_num) installer using direct Apple URLs..." echo "" # Required files for 10.13-10.15 local files files=( "BaseSystem.dmg" "BaseSystem.chunklist" "InstallInfo.plist" "InstallESDDmg.pkg" "AppleDiagnostics.dmg" "AppleDiagnostics.chunklist" ) # Download all required files for filename in "${files[@]}"; do log_info "Downloading ${filename}..." curl --retry 5 --max-time 1800 --connect-timeout 10 --progress-bar \ -L "${base_url}${filename}" -o "$WORK_DIR/${filename}" if [ $? -ne 0 ]; then log_error "Failed to download ${filename}" return 1 fi done echo "" log_info "All files downloaded. Building macOS $(get_codename $version_num) installer app..." # Mount BaseSystem.dmg to extract the app hdiutil attach -nobrowse -quiet -mountpoint "$WORK_DIR/BaseSystem_mnt" "$WORK_DIR/BaseSystem.dmg" # Copy installer app to /Applications local installer_app installer_app="$WORK_DIR/BaseSystem_mnt/$(get_installer_app_name $version_num).app" sudo cp -Rp "$installer_app" "/Applications/" # Create SharedSupport directory local shared_support shared_support="/Applications/$(get_installer_app_name $version_num).app/Contents/SharedSupport" sudo mkdir -p "$shared_support" # Rename InstallESDDmg.pkg to InstallESD.dmg mv "$WORK_DIR/InstallESDDmg.pkg" "$WORK_DIR/InstallESD.dmg" # Fix InstallInfo.plist sed -e "s/InstallESDDmg\.pkg/InstallESD.dmg/" \ -e "s/pkg\.InstallESDDmg/dmg.InstallESD/" \ -e "/InstallESD\.dmg/{n;N;N;N;d;}" \ "$WORK_DIR/InstallInfo.plist" > "$WORK_DIR/InstallInfo_fixed.plist" # Copy files to SharedSupport sudo cp -Rp "$WORK_DIR/BaseSystem.dmg" "$shared_support/" sudo cp -Rp "$WORK_DIR/BaseSystem.chunklist" "$shared_support/" sudo cp -Rp "$WORK_DIR/InstallInfo_fixed.plist" "$shared_support/InstallInfo.plist" sudo cp -Rp "$WORK_DIR/InstallESD.dmg" "$shared_support/" sudo cp -Rp "$WORK_DIR/AppleDiagnostics.dmg" "$shared_support/" sudo cp -Rp "$WORK_DIR/AppleDiagnostics.chunklist" "$shared_support/" # Set proper ownership sudo chown -R root:wheel "$shared_support" # Resign createinstallmedia and remove quarantine tag for Apple Silicon if [ "$CPU_ARCH" == "arm64" ] && [[ "$version_num" =~ ^10\.1[3-5]$ ]]; then sudo codesign -s - -f "/Applications/$(get_installer_app_name $version_num).app/Contents/Resources/createinstallmedia" sudo xattr -r -d com.apple.quarantine "/Applications/$(get_installer_app_name $version_num).app" fi # Detach BaseSystem.dmg sync && sleep 10 && detach_disk "$WORK_DIR/BaseSystem_mnt" log_info "Successfully installed $(get_installer_app_name $version_num) to /Applications" return 0 } download_modern_macos() { local version_num version_num="$1" version_name=$(get_codename $version_num) version_name="${version_name/_/ }" # Use direct download when softwareupdate is not available if [[ "$version_num" =~ ^10\.1[345]$ ]]; then # For Apple silicon: always use direct download for 10.13-10.15 if [ "$CPU_ARCH" == "arm64" ]; then log_info "Apple silicon detected" echo "" # Show warning on Apple silicon log_warn "╔═════════════════════════════════════════════════════════════════╗" log_warn "║ Apple silicon Limitation ║" log_warn "╠═════════════════════════════════════════════════════════════════╣" log_warn "║ On Apple silicon, softwareupdate cannot download macOS versions ║" log_warn "║ older than the one that originally shipped with your device. ║" log_warn "╚═════════════════════════════════════════════════════════════════╝" echo "" direct_download_10_13_10_15 "$version_num" return $? fi # For Intel: use direct download if running kernel < 20 (pre-Big Sur) if [ "$CPU_ARCH" = "x86_64" ] && [ "$KERNEL_VERSION" -lt 20 ]; then log_info "Intel CPU detected" log_info "This Mac is running a version older than Big Sur, will attempt direct download for macOS $version_name" direct_download_10_13_10_15 "$version_num" return $? fi fi # Use softwareupdate for macOS Big Sur and newer # Check CPU architecture compatibility for softwareupdate if [ "$CPU_ARCH" = "x86_64" ] && [ "$KERNEL_VERSION" -lt 20 ]; then log_info "Intel CPU detected" # Intel Macs require macOS 11 or newer for softwareupdate log_error "Intel Macs must be running macOS 11 (Big Sur) or newer to download macOS using softwareupdate" log_error "Please upgrade your macOS to version 11 or newer" return 1 elif [ "$CPU_ARCH" == "arm64" ]; then log_info "Apple silicon detected" # Show warning on Apple silicon log_warn "╔═════════════════════════════════════════════════════════════════╗" log_warn "║ Apple silicon Limitation ║" log_warn "╠═════════════════════════════════════════════════════════════════╣" log_warn "║ On Apple silicon, softwareupdate cannot download macOS versions ║" log_warn "║ older than the one that originally shipped with your device. ║" log_warn "╚═════════════════════════════════════════════════════════════════╝" fi log_info "Fetching available macOS installers..." softwareupdate --list-full-installers 2>/dev/null | grep "* Title:" | sed 's/^[[:space:]]*//' > "$WORK_DIR/installers.txt" # Get latest installer version local selected_version_number selected_version_number=$(grep -i "$version_name" "$WORK_DIR/installers.txt" | grep -iv "beta" | head -1 | sed -n 's/.*Version: \([^,]*\).*/\1/p') if [ -z "$selected_version_number" ]; then log_error "macOS $version_name ($version_num) is not available for download on this system" log_info "Available installers:" cat "$WORK_DIR/installers.txt" return 1 fi log_info "Found latest $version_name version: $selected_version_number" log_info "Downloading and installing macOS $version_name installer (this may take a while)..." # Download with retries (max 4 times) local download_retries download_retries=4 for attempt in $(seq 1 $download_retries); do log_info "Download attempt $attempt of $download_retries..." if softwareupdate --fetch-full-installer --full-installer-version "$selected_version_number"; then log_info "Download successful!" sleep 5 && return 0 fi if [ $attempt -eq $download_retries ]; then log_error "Download failed after $download_retries attempts" return 1 fi log_warn "Retrying in 5 seconds..." sleep 5 done } create_iso_dmg_10_7_10_8() { local installer_path installer_path="$1" local output_file output_file="$2" local volume_name volume_name="$3" local version_num version_num="$4" local image_format image_format=$(echo "$IMAGE_FORMAT" | tr '[:lower:]' '[:upper:]') log_info "Creating macOS $(get_codename $version_num) $image_format image..." echo "" # Mount InstallESD.dmg hdiutil attach -nobrowse -quiet -mountpoint "$WORK_DIR/InstallESD_mnt" "$installer_path/Contents/SharedSupport/InstallESD.dmg" # Extract the exact version number and append it to the file name local exact_version_number exact_version_number=$(defaults read "$WORK_DIR/InstallESD_mnt/System/Library/CoreServices/SystemVersion" ProductVersion) FINAL_OUTPUT_PATH="${output_file%.${IMAGE_FORMAT}}_$exact_version_number.${IMAGE_FORMAT}" # Create final image if [ "$IMAGE_FORMAT" == "dmg" ]; then # Create DMG with GUID Partition Table from mounted InstallESD.dmg sudo hdiutil create -quiet -layout GPTSPUD -format UDRW -ov -volname "$volume_name" -srcdir "$WORK_DIR/InstallESD_mnt" "$FINAL_OUTPUT_PATH" # Append .img extension mv -f "$FINAL_OUTPUT_PATH" "${FINAL_OUTPUT_PATH}.img" FINAL_OUTPUT_PATH="${FINAL_OUTPUT_PATH}.img" # Unmount sync && detach_disk "$WORK_DIR/InstallESD_mnt" else # Unmount sync && detach_disk "$WORK_DIR/InstallESD_mnt" log_info "Converting to ISO format..." sudo hdiutil makehybrid -quiet -ov -hfs -udf -default-volume-name "$volume_name" "$installer_path/Contents/SharedSupport/InstallESD.dmg" -o "$FINAL_OUTPUT_PATH" fi } create_iso_10_9_10_11() { local installer_path installer_path="$1" local output_file output_file="$2" local volume_name volume_name="$3" local version_num version_num="$4" log_info "Creating macOS $(get_codename $version_num) ISO image..." # Mount InstallESD.dmg hdiutil attach -nobrowse -quiet -mountpoint "$WORK_DIR/InstallESD_mnt" "$installer_path/Contents/SharedSupport/InstallESD.dmg" # Convert BaseSystem.dmg hdiutil convert -quiet "$WORK_DIR/InstallESD_mnt/BaseSystem.dmg" -format UDRW -o "$WORK_DIR/BaseSystem_converted.dmg" # Increase size sudo hdiutil resize -size 7.1g "$WORK_DIR/BaseSystem_converted.dmg" # Mount the converted DMG DISK_ID=$(hdiutil attach -nobrowse -mountpoint "$WORK_DIR/BaseSystem_mnt" "$WORK_DIR/BaseSystem_converted.dmg" | grep -o '/dev/disk[0-9]*' | head -1) # Remove Packages symlink and copy actual Packages rm "$WORK_DIR/BaseSystem_mnt/System/Installation/Packages" cp -R "$WORK_DIR/InstallESD_mnt/Packages" "$WORK_DIR/BaseSystem_mnt/System/Installation/" # Copy required files cp "$WORK_DIR/InstallESD_mnt/BaseSystem.dmg" "$WORK_DIR/BaseSystem_mnt/" cp "$WORK_DIR/InstallESD_mnt/BaseSystem.chunklist" "$WORK_DIR/BaseSystem_mnt/" # Note: For Mavericks UDF format, boot.efi attempts to load /mach_kernel, but none exists. # Although we can extract it from Packages/BaseSystemBinaries.pkg, there's a simpler solution: if [ "$version_num" == "10.9" ]; then cp "$WORK_DIR/BaseSystem_mnt/System/Library/Caches/com.apple.kext.caches/Startup/kernelcache" "$WORK_DIR/BaseSystem_mnt/mach_kernel" fi # Extract the exact version number and append it to the file name local exact_version_number exact_version_number=$(defaults read "$WORK_DIR/BaseSystem_mnt/System/Library/CoreServices/SystemVersion" ProductVersion) FINAL_OUTPUT_PATH="${output_file%.iso}_$exact_version_number.iso" # Unmount detach_disk "$WORK_DIR/InstallESD_mnt" detach_disk "$DISK_ID" && DISK_ID="" # Create ISO log_info "Converting to ISO format..." sudo hdiutil makehybrid -quiet -ov -hfs -udf -default-volume-name "$volume_name" \ "$WORK_DIR/BaseSystem_converted.dmg" -o "$FINAL_OUTPUT_PATH" } create_dmg_10_9_to_10_12_alt_method() { local installer_path installer_path="$1" local output_file output_file="$2" local volume_name volume_name="$3" local version_num version_num="$4" log_info "Creating macOS $(get_codename $version_num) DMG image..." echo "" # Mount InstallESD.dmg hdiutil attach -nobrowse -quiet -mountpoint "$WORK_DIR/InstallESD_mnt" "$installer_path/Contents/SharedSupport/InstallESD.dmg" if [[ "$version_num" =~ ^(10\.9|10\.10)$ ]]; then # Attach BaseSystem.dmg BaseSystem_DISK_ID=$(hdiutil attach "$WORK_DIR/InstallESD_mnt/BaseSystem.dmg" -nomount | grep -o '/dev/disk[0-9]*' | head -1) # Create a DMG with GUID Partition Table and attach it hdiutil create -quiet -ov -fs hfs+ -size 1400m "$WORK_DIR/installer_gpt.dmg" DISK_ID=$(hdiutil attach "$WORK_DIR/installer_gpt.dmg" -nomount | grep -o '/dev/disk[0-9]*' | head -1) # Write the bootable recovery partition to the first partition on the DMG image sudo dd if="${BaseSystem_DISK_ID}s2" of="${DISK_ID}s1" bs=1m && detach_disk "$BaseSystem_DISK_ID" # Unmount detach_disk "$DISK_ID" && DISK_ID="" # Increase size sudo hdiutil resize -size 7g "$WORK_DIR/installer_gpt.dmg" elif [[ "$version_num" =~ ^(10\.11|10\.12)$ ]]; then local dmg_size case "$version_num" in "10.11") dmg_size="7.1" ;; *) dmg_size="5.9" ;; esac # El Capitan and Sierra BaseSystem.dmg already use a GUID Partition Table # Convert BaseSystem.dmg hdiutil convert -quiet "$WORK_DIR/InstallESD_mnt/BaseSystem.dmg" -format UDRW -o "$WORK_DIR/installer_gpt.dmg" # Increase size sudo hdiutil resize -size ${dmg_size}g "$WORK_DIR/installer_gpt.dmg" fi # Mount DMG image and change volume name DISK_ID=$(hdiutil attach -nobrowse -mountpoint "$WORK_DIR/installer_mnt" "$WORK_DIR/installer_gpt.dmg" | grep -o '/dev/disk[0-9]*' | head -1) diskutil rename "${DISK_ID}s1" "$volume_name" # Remove Packages symlink and copy actual Packages rm "$WORK_DIR/installer_mnt/System/Installation/Packages" ditto "$WORK_DIR/InstallESD_mnt/Packages" "$WORK_DIR/installer_mnt/System/Installation/Packages" # Copy required files cp -R "$WORK_DIR/InstallESD_mnt/BaseSystem.dmg" "$WORK_DIR/installer_mnt/" cp -R "$WORK_DIR/InstallESD_mnt/BaseSystem.chunklist" "$WORK_DIR/installer_mnt/" # Extract the exact version number and append it to the file name local exact_version_number exact_version_number=$(defaults read "$WORK_DIR/installer_mnt/System/Library/CoreServices/SystemVersion" ProductVersion) FINAL_OUTPUT_PATH="${output_file%.dmg}_$exact_version_number.dmg.img" sync && sleep 5 # Unmount detach_disk "$WORK_DIR/InstallESD_mnt" detach_disk "$DISK_ID" && DISK_ID="" # Optimize DMG file size log_info "Optimizing DMG file size..." sudo hdiutil resize -size min "$WORK_DIR/installer_gpt.dmg" # Move to output log_info "Move to $FINAL_OUTPUT_PATH" mv -f "$WORK_DIR/installer_gpt.dmg" "$FINAL_OUTPUT_PATH" } create_iso_dmg_10_15_alt_method() { local installer_path installer_path="$1" local output_file output_file="$2" local volume_name volume_name="$3" local version_num version_num="$4" local image_format image_format=$(echo "$IMAGE_FORMAT" | tr '[:lower:]' '[:upper:]') log_info "Apple Silicon detected. Using alternative method for Catalina instead of 'createinstallmedia'" echo "" log_info "Creating macOS $(get_codename $version_num) $image_format image..." echo "" # Mount InstallESD.dmg hdiutil attach -nobrowse -quiet -mountpoint "$WORK_DIR/InstallESD_mnt" "$installer_path/Contents/SharedSupport/InstallESD.dmg" # Convert BaseSystem.dmg local basesystem_path basesystem_path="$installer_path/Contents/SharedSupport/BaseSystem.dmg" hdiutil convert -quiet "$basesystem_path" -format UDRW -o "$WORK_DIR/BaseSystem_converted.dmg" # Increase size sudo hdiutil resize -size 9g "$WORK_DIR/BaseSystem_converted.dmg" # Mount the converted DMG DISK_ID=$(hdiutil attach -nobrowse -mountpoint "$WORK_DIR/BaseSystem_mnt" "$WORK_DIR/BaseSystem_converted.dmg" | grep -o '/dev/disk[0-9]*' | head -1) diskutil rename "${DISK_ID}s1" "$volume_name" # Remove the stub installer app rm -rf "$WORK_DIR/BaseSystem_mnt/$(get_installer_app_name $version_num).app" # Copy actual installer app from /Applications cp -R "$installer_path" "$WORK_DIR/BaseSystem_mnt/" # Extract the exact version number and append it to the file name local exact_version_number exact_version_number=$(defaults read "$WORK_DIR/BaseSystem_mnt/System/Library/CoreServices/SystemVersion" ProductVersion) if [ "$IMAGE_FORMAT" == "dmg" ]; then FINAL_OUTPUT_PATH="${output_file%.dmg}_$exact_version_number.dmg.img" else FINAL_OUTPUT_PATH="${output_file%.iso}_$exact_version_number.iso" fi # Unmount detach_disk "$WORK_DIR/InstallESD_mnt" detach_disk "$DISK_ID" && DISK_ID="" # Create final image if [ "$IMAGE_FORMAT" == "dmg" ]; then # Optimize DMG file size log_info "Optimizing DMG file size..." sudo hdiutil resize -size min "$WORK_DIR/BaseSystem_converted.dmg" # Move to output log_info "Move to $FINAL_OUTPUT_PATH" mv -f "$WORK_DIR/BaseSystem_converted.dmg" "$FINAL_OUTPUT_PATH" else log_info "Converting to ISO format..." sudo hdiutil makehybrid -quiet -ov -hfs -udf -default-volume-name "$volume_name" "$WORK_DIR/BaseSystem_converted.dmg" -o "$FINAL_OUTPUT_PATH" fi } create_iso_10_12_and_later() { local installer_path installer_path="$1" local output_file output_file="$2" local version_num version_num="$3" # macOS Sierra and Catalina 'createinstallmedia' fails when running on Apple silicon # Redirecting to alternative method if [[ "$CPU_ARCH" == "arm64" ]]; then if [[ "$version_num" == "10.12" ]]; then create_iso_10_9_10_11 \ "$installer_path" \ "$output_file" \ "$(get_installer_app_name "$version_num")" \ "$version_num" return $? elif [[ "$version_num" == "10.15" ]]; then create_iso_dmg_10_15_alt_method \ "$installer_path" \ "$output_file" \ "$(get_installer_app_name "$version_num")" \ "$version_num" return $? fi fi log_info "Creating macOS $(get_codename $version_num) ISO image..." echo "" # Create sparse image hdiutil create -quiet -size 20g -volname "$(get_codename $version_num)_iso" -fs HFS+ -type SPARSE -attach "$WORK_DIR/installer.sparseimage" DISK_ID=$(diskutil list | grep $(get_codename $version_num)_iso | head -1 | awk '{print $NF}' | sed 's/s[0-9]*$//') # Prepare createinstallmedia command local cmd cmd="sudo '$installer_path/Contents/Resources/createinstallmedia' --volume /Volumes/$(get_codename $version_num)_iso --nointeraction" # For macOS 10.12, need to patch Info.plist # For macOS 10.10-10.12, need to add --applicationpath if [ "$version_num" == "10.12" ]; then log_info "Patching Info.plist for Sierra compatibility..." sudo /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString '12.6.03'" "$installer_path/Contents/Info.plist" cmd="$cmd --applicationpath \"$installer_path\"" fi # Create install media log_info "Running createinstallmedia..." bash -c "$cmd" sync && sleep 5 # Extract the exact version number and append it to the file name local exact_version_number exact_version_number=$(defaults read "/Volumes/$(get_installer_app_name "$version_num")/System/Library/CoreServices/SystemVersion" ProductVersion) FINAL_OUTPUT_PATH="${output_file%.iso}_$exact_version_number.iso" # Restore Info.plist if [ "$version_num" == "10.12" ]; then sudo /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString '12.6.06'" "/Applications/Install macOS Sierra.app/Contents/Info.plist" sudo /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString '12.6.06'" "/Volumes/Install macOS Sierra/Install macOS Sierra.app/Contents/Info.plist" fi # Unmount detach_disk "$DISK_ID" && DISK_ID="" # Create ISO from sparse image log_info "Converting to ISO format..." sudo hdiutil makehybrid -quiet -ov -hfs -udf "$WORK_DIR/installer.sparseimage" -o "$FINAL_OUTPUT_PATH" } create_dmg_10_10_and_later() { local installer_path installer_path="$1" local output_file output_file="$2" local version_num version_num="$3" # macOS Catalina, Sierra, El Capitan, and Yosemite 'createinstallmedia' fails when running on Apple silicon # Redirecting to alternative method if [[ "$CPU_ARCH" == "arm64" ]]; then if [[ "$version_num" =~ ^10\.1[012]$ ]]; then create_dmg_10_9_to_10_12_alt_method \ "$installer_path" \ "$output_file" \ "$(get_installer_app_name "$version_num")" \ "$version_num" return $? elif [[ "$version_num" = "10.15" ]]; then create_iso_dmg_10_15_alt_method \ "$installer_path" \ "$output_file" \ "$(get_installer_app_name "$version_num")" \ "$version_num" return $? fi fi log_info "Creating macOS $(get_codename $version_num) DMG image..." echo "" # Determine DMG size based on macOS version local dmg_size case "$version_num" in 10.10|10.11|10.12|10.13|10.14) dmg_size="7g" ;; 10.15) dmg_size="9g" ;; 11|12|13) dmg_size="15g" ;; 14|15) dmg_size="18g" ;; *) dmg_size="20g" ;; esac # Create DMG disk image hdiutil create -quiet -size $dmg_size -volname "$(get_codename $version_num)_dmg" -fs HFS+ -type UDIF -attach "$WORK_DIR/installer.dmg" DISK_ID=$(diskutil list | grep $(get_codename $version_num)_dmg | head -1 | awk '{print $NF}' | sed 's/s[0-9]*$//') # Prepare createinstallmedia command local cmd cmd="sudo '$installer_path/Contents/Resources/createinstallmedia' --volume /Volumes/$(get_codename $version_num)_dmg --nointeraction" # For macOS 10.12, need to patch Info.plist # For macOS 10.10-10.12, need to add --applicationpath case "$version_num" in 10.10|10.11) cmd="$cmd --applicationpath \"$installer_path\"" ;; 10.12) log_info "Patching Info.plist for Sierra compatibility..." sudo /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString '12.6.03'" "$installer_path/Contents/Info.plist" cmd="$cmd --applicationpath \"$installer_path\"" ;; esac # Create install media log_info "Running createinstallmedia..." bash -c "$cmd" sync && sleep 5 # Restore Info.plist on macOS 10.12 if [ "$version_num" == "10.12" ]; then sudo /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString '12.6.06'" "/Applications/Install macOS Sierra.app/Contents/Info.plist" sudo /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString '12.6.06'" "/Volumes/Install macOS Sierra/Install macOS Sierra.app/Contents/Info.plist" fi log_info "Starting DMG file size optimization..." # Check minimum size requirements MIN_SIZE=$(diskutil resizeVolume ${DISK_ID}s2 limits | grep "Minimum (constrained by file usage)" | sed 's/.*(\([0-9]*\) Bytes)/\1/') # Add 50MB to $MIN_SIZE for macOS 10.10–10.11 because the installation fails when resized to the minimum size case "$version_num" in 10.10|10.11) MIN_SIZE=$((MIN_SIZE + 52428800)) ;; esac # Consolidate the installer data partition by shrinking the macOS installer partition to the minimum size. log_info "Shrinking macOS installer partition to minimum..." diskutil resizeVolume ${DISK_ID}s2 "${MIN_SIZE}B" free free 0 >/dev/null 2>&1 # Expand back to consolidate log_info "Expanding back to consolidate data ..." for attempt in $(seq 1 $RETRIES_COUNT); do log_info "Resize attempt $attempt of $RETRIES_COUNT..." if diskutil resizeVolume ${DISK_ID}s2 0 >/dev/null 2>&1; then log_info "Partition resized successfully" break fi if [ $attempt -lt $RETRIES_COUNT ]; then log_warn "Resize failed, retrying..." sync && sleep 5 fi done # Extract the exact version number and append it to the file name local exact_version_number exact_version_number=$(defaults read "/Volumes/$(get_installer_app_name "$version_num")/System/Library/CoreServices/SystemVersion" ProductVersion) FINAL_OUTPUT_PATH="${output_file%.dmg}_$exact_version_number.dmg.img" # Unmount detach_disk "$DISK_ID" && DISK_ID="" # Optimize DMG file size log_info "Final optimization..." sudo hdiutil resize -size min "$WORK_DIR/installer.dmg" # Move to output and append .iso extension mv -f "$WORK_DIR/installer.dmg" "$FINAL_OUTPUT_PATH" } # Main script main() { echo -e "${BOLD}${CYAN}" echo "╔══════════════════════════════════════════════════════════════════════════════╗" echo "║ mkmaciso – the ultimate tool for creating macOS installer ISO and DMG images ║" echo "╚══════════════════════════════════════════════════════════════════════════════╝" echo -e "${NC}" # Check for availble disk space check_disk_space # Require sudo check_sudo_access # Validate image format (default to iso if empty, fallback to dmg if invalid input) IMAGE_FORMAT=$(echo "$IMAGE_FORMAT" | tr '[:upper:]' '[:lower:]') if [[ -z "$IMAGE_FORMAT" ]]; then IMAGE_FORMAT="iso" elif [[ "$IMAGE_FORMAT" != "iso" && "$IMAGE_FORMAT" != "dmg" ]]; then log_warn "Invalid image format '$IMAGE_FORMAT'; falling back to dmg" IMAGE_FORMAT="dmg" fi # Normalize version input VERSION=$(echo "$VERSION" | tr '[:upper:]' '[:lower:]' | tr -d '_- ') # Determine if input is a codename or version number VERSION_NUM="" VERSION_NAME="" # Try as version number first, then as codename if [[ "$VERSION" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then VERSION_NUM="$VERSION" else VERSION_NUM=$(get_version_number "$VERSION") fi # Get proper codename (also validates the version number) VERSION_NAME=$(get_codename "$VERSION_NUM") if [ -z "$VERSION_NAME" ]; then log_info "Run with -h or --help for usage" echo "" log_error "Unknown macOS version: $VERSION" log_info "Supported versions: 10.7, 10.8, 10.9, 10.10, 10.11, 10.12, 10.13, 10.14, 10.15, 11, 12, 13, 14, 15, 26" log_info "Supported codenames: lion, mountain-lion, mavericks, yosemite, el-capitan, sierra, high-sierra, mojave, catalina, big-sur, monterey, ventura, sonoma, sequoia, tahoe" exit 1 fi log_info "Target macOS:${GREEN} $VERSION_NAME ($VERSION_NUM) ${NC}" log_info "Image format:${GREEN} $(echo $IMAGE_FORMAT | tr '[:lower:]' '[:upper:]') ${NC}" # Determine output filename if not specified if [ -z "$OUTPUT_PATH" ]; then OUTPUT_PATH="$(pwd)/macOS_${VERSION_NAME}.${IMAGE_FORMAT}" else # Ensure output path has correct extension if [[ "$OUTPUT_PATH" != *.$IMAGE_FORMAT ]]; then OUTPUT_PATH="${OUTPUT_PATH%.*}.$IMAGE_FORMAT" fi # Convert to absolute path if [[ "$OUTPUT_PATH" != /* ]]; then # Use dirname + basename to avoid "./" in the middle OUTPUT_PATH="$(cd "$(dirname "$OUTPUT_PATH")" && pwd)/$(basename "$OUTPUT_PATH")" fi fi log_info "Output path:${GREEN} $OUTPUT_PATH ${NC}" echo "" # Create work directory WORK_DIR=$(mktemp -d -t LongQT-sea) log_info "Working directory:${CYAN} $WORK_DIR ${NC}" echo "" # Check if installer already exists INSTALLER_PATH="/Applications/$(get_installer_app_name "$VERSION_NUM").app" if [ -n "$INSTALLER_PATH" ] && [ -d "$INSTALLER_PATH" ]; then log_info "Found existing installer at:${CYAN} $INSTALLER_PATH ${NC}" echo "" else log_info "Installer not found, downloading..." # Download and install if [[ "$VERSION_NUM" =~ ^10\.(7|8|9|10|11|12)$ ]]; then # Legacy macOS versions download_legacy_macos "$VERSION_NUM" install_legacy_app "$VERSION_NUM" "$WORK_DIR/InstallMacOSX.dmg" else # Modern macOS (10.13 and later) download_modern_macos "$VERSION_NUM" || exit 1 fi if [ ! -d "$INSTALLER_PATH" ]; then log_error "Failed to install macOS installer app" exit 1 fi fi # Get volume name for the installer VOLUME_NAME=$(get_installer_app_name "$VERSION_NUM") if [ "$IMAGE_FORMAT" == "iso" ]; then case "$VERSION_NUM" in "10.7"|"10.8") create_iso_dmg_10_7_10_8 "$INSTALLER_PATH" "$OUTPUT_PATH" "$VOLUME_NAME" "$VERSION_NUM" ;; "10.9"|"10.10"|"10.11") create_iso_10_9_10_11 "$INSTALLER_PATH" "$OUTPUT_PATH" "$VOLUME_NAME" "$VERSION_NUM" ;; *) create_iso_10_12_and_later "$INSTALLER_PATH" "$OUTPUT_PATH" "$VERSION_NUM" ;; esac else case "$VERSION_NUM" in "10.7"|"10.8") create_iso_dmg_10_7_10_8 "$INSTALLER_PATH" "$OUTPUT_PATH" "$VOLUME_NAME" "$VERSION_NUM" ;; "10.9") create_dmg_10_9_to_10_12_alt_method "$INSTALLER_PATH" "$OUTPUT_PATH" "$VOLUME_NAME" "$VERSION_NUM" ;; *) create_dmg_10_10_and_later "$INSTALLER_PATH" "$OUTPUT_PATH" "$VERSION_NUM" ;; esac fi # Final checks if [ -f "$FINAL_OUTPUT_PATH" ]; then FILE_SIZE_BYTES=$(stat -f%z "$FINAL_OUTPUT_PATH") FILE_SIZE_GB=$(du -h "$FINAL_OUTPUT_PATH" | cut -f1) # 4GB in bytes MIN_SIZE=4294967296 if [ "$FILE_SIZE_BYTES" -lt "$MIN_SIZE" ]; then log_error "Final image is smaller than 4GB" log_error "The file is likely corrupted or incomplete" log_warn "Remove corrupted file..." rm -f "$FINAL_OUTPUT_PATH" exit 1 fi echo "" log_info "Successfully created installer image!" log_info "File: $FINAL_OUTPUT_PATH" log_info "Size: $FILE_SIZE_GB" echo "" if [ "$IMAGE_FORMAT" == "iso" ]; then log_info "This ISO image is intended for creating macOS virtual machine:" log_info " - Compatible with Proxmox VE, QEMU, VMware, and VirtualBox" log_info " - Add as DVD/CD drive to the VM" echo "" else log_info "This DMG image is intended for:" log_info " - Flash to USB drive using Rufus (Windows) or dd (Linux)" log_info " - Can also be used with virtual machines (requires conversion to .vhd for Hyper-V or .vmdk for VMware)" log_info " - Add to the VM as a virtual hard drive" echo "" fi else log_error "Failed to create macOS_${VERSION_NAME}.${IMAGE_FORMAT} image" exit 1 fi } # Show usage if -h or --help is passed if [ "$1" == "--help" ] || [ "$1" == "-h" ]; then cat << 'EOF' ╔══════════════════════════════════════════════════════════════════════════════╗" ║ mkmaciso – the ultimate tool for creating macOS installer ISO and DMG images ║" ╚══════════════════════════════════════════════════════════════════════════════╝" Usage: mkmaciso [VERSION] [FORMAT] [OUTPUT_PATH] mkmaciso # Interactive mode Parameters: VERSION macOS version number or codename, e.g. 26 or Tahoe FORMAT iso or dmg (default: iso) OUTPUT_PATH Output file path (default: ./macOS_.) Supported versions: 10.7 Lion 10.14 Mojave 10.8 Mountain Lion 10.15 Catalina 10.9 Mavericks 11 Big Sur 10.10 Yosemite 12 Monterey 10.11 El Capitan 13 Ventura 10.12 Sierra 14 Sonoma 10.13 High Sierra 15 Sequoia 26 Tahoe Examples: mkmaciso Interactive mode mkmaciso 14 Create Sonoma ISO mkmaciso 14 dmg Create Sonoma DMG mkmaciso sonoma dmg ~/Desktop/out.dmg Create Sonoma DMG at specified path Requirements: - macOS 11 (Big Sur) or later for create macOS 10.13+ image - Internet connection for downloading installers - Sufficient disk space (~20-40GB temporary, final size varies) - Administrator privileges (sudo) - For best results, run this on Intel Macs EOF exit 0 fi # Check if running in interactive mode with piped (no parameters) if [ $# -eq 0 ]; then if [ -p /dev/stdin ] || [ ! -t 0 ]; then clear log_error "Interactive mode unavailable when piped" log_info "Usage: curl -sL | bash -s [FORMAT] [OUTPUT]" echo "" log_info "Example: curl -sL | bash -s 26" log_info "Example: curl -sL | bash -s 26 dmg" log_info "Example: curl -sL | bash -s 26 iso ~/Desktop/Tahoe.iso" exit 1 fi run_interactive_menu fi # Run main function main