├── .gitattributes ├── .gitignore ├── Build Tools ├── Clean Mac Test Boot Source Drive.applescript ├── Create macOS USB Installer Commands.txt ├── Download-macOS-Installer-DMGs.sh ├── Mac Test Boot Disk Image Commands.txt ├── MacLand Script Builder.applescript └── update-mac-drives.sh ├── LICENSE ├── Other Scripts ├── every_apple_marketing_model_name_with_grouped_serial_config_codes.txt ├── every_apple_serial_config_code_with_marketing_model_name.txt ├── every_intel_mac_marketing_model_name_with_grouped_model_ids_and_serial_config_codes.txt ├── every_mac_marketing_model_name_with_grouped_serial_config_codes.txt ├── generate_csreq_hex_for_tcc_db.jxa ├── get_bluetooth_from_all_mac_specs_pages.sh ├── get_every_apple_serial_config_code_with_marketing_model_name.sh ├── get_marketing_model_name.sh ├── get_power_adapters_from_all_mac_specs_pages.sh ├── get_specs_url_from_serial.sh ├── get_truetone_from_all_mac_specs_pages.sh ├── group_every_intel_mac_marketing_model_name_with_model_ids_and_serial_config_codes.sh └── quit_apps_by_bundle_id.sh ├── Production Scripts ├── Cleanup After QA Complete │ └── Source │ │ └── Cleanup After QA Complete.applescript ├── Free Geek Demo Helper │ └── Source │ │ └── Free Geek Demo Helper.applescript ├── Free Geek Login Progress │ └── Source │ │ └── Free Geek Login Progress.applescript ├── Free Geek Reset │ └── Source │ │ └── Free Geek Reset.applescript ├── Free Geek Setup │ └── Source │ │ └── Free Geek Setup.applescript └── Free Geek Snapshot Helper │ └── Source │ └── Free Geek Snapshot Helper.applescript ├── README.md ├── Testing Scripts ├── Audio Test │ └── Source │ │ └── Audio Test.applescript ├── CPU Stress Test │ └── Source │ │ └── CPU Stress Test.applescript ├── Camera Test │ └── Source │ │ └── Camera Test.applescript ├── Free Geek Updater │ └── Source │ │ └── Free Geek Updater.applescript ├── GPU Stress Test │ └── Source │ │ └── GPU Stress Test.applescript ├── Hard Drive Test │ └── Source │ │ └── Extract Info from DriveDx Report.applescript ├── Internet Test │ └── Source │ │ └── Internet Test.applescript ├── Keyboard Test │ └── Source │ │ ├── build_keyboard_test.sh │ │ ├── keyboard_test_modifications.css │ │ └── keyboard_test_modifications.js ├── Mac Scope │ └── Source │ │ └── Mac Scope.applescript ├── Microphone Test │ └── Source │ │ └── Microphone Test.applescript ├── Screen Test │ └── Source │ │ └── Screen Test.applescript ├── Startup Picker │ └── Source │ │ └── Startup Picker.applescript ├── Test Boot Setup │ └── Source │ │ └── Test Boot Setup.applescript ├── Test CD │ └── Source │ │ └── Test CD.applescript ├── Test DVD │ └── Source │ │ └── Test DVD.applescript ├── Trackpad Test │ └── Source │ │ └── Trackpad Test.applescript └── Volume Scripts for Breakaway │ ├── Set Volume for Headphones.applescript │ └── Set Volume for Speakers.applescript └── fgMIB Resources ├── Build Tools ├── Text to Speech Phrases.txt ├── build-fg-prepare-os-pkg.sh └── copy-fgMIB-resources-to-drives.sh ├── Install Packages Script └── fg-install-packages.sh ├── Prepare OS Package ├── Package Resources │ ├── fg-error-occurred │ │ └── fg-error-occurred.sh │ └── fg-snapshot-reset │ │ ├── fg-snapshot-preserver.sh │ │ └── fg-snapshot-reset.sh └── fg-prepare-os.sh └── fg-install-os.sh /.gitattributes: -------------------------------------------------------------------------------- 1 | *.jxa linguist-language=JavaScript 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore from macOS: 2 | .DS_Store 3 | 4 | # Ignore file types in any location: 5 | *.app 6 | *.dmg 7 | *.pkg 8 | *.zip 9 | *.aiff 10 | *.icns 11 | *.png 12 | *.tiff 13 | *.pdf 14 | *.svg 15 | *.pxm 16 | *.iconsproj 17 | *.driveDxLicense 18 | *.preferences 19 | 20 | # Ignore if name starts with in any location: 21 | OLD-* 22 | UNUSED-* 23 | 24 | # Ignore in "Build Tools": 25 | Free\ Geek\ Passwords.plist 26 | 27 | # Ignore in "fgMIB Resources": 28 | extra-bins/ 29 | Tools/ 30 | 31 | # Ignore in AppleScript Source folders: 32 | DriveDx/ 33 | Remote\ Management\ Debugging/ 34 | ZIPs\ for\ Auto-Update/ -------------------------------------------------------------------------------- /Build Tools/Clean Mac Test Boot Source Drive.applescript: -------------------------------------------------------------------------------- 1 | -- 2 | -- MIT License 3 | -- 4 | -- Copyright (c) 2021 Free Geek 5 | -- 6 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 7 | -- to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | -- and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | -- 12 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 14 | -- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -- 16 | 17 | use AppleScript version "2.7" 18 | use scripting additions 19 | 20 | set bundleIdentifierPrefix to "org.freegeek." 21 | 22 | try 23 | set infoPlistPath to ((POSIX path of (path to me)) & "Contents/Info.plist") 24 | ((infoPlistPath as POSIX file) as alias) 25 | 26 | set AppleScript's text item delimiters to "-" 27 | set correctBundleIdentifier to bundleIdentifierPrefix & ((words of (name of me)) as text) 28 | try 29 | set currentBundleIdentifier to ((do shell script "/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' " & (quoted form of infoPlistPath)) as text) 30 | if (currentBundleIdentifier is not equal to correctBundleIdentifier) then error "INCORRECT Bundle Identifier" 31 | on error 32 | do shell script "plutil -replace CFBundleIdentifier -string " & (quoted form of correctBundleIdentifier) & " " & (quoted form of infoPlistPath) 33 | 34 | try 35 | set currentCopyright to ((do shell script "/usr/libexec/PlistBuddy -c 'Print :NSHumanReadableCopyright' " & (quoted form of infoPlistPath)) as text) 36 | if (currentCopyright does not contain "Twemoji") then error "INCORRECT Copyright" 37 | on error 38 | do shell script "plutil -replace NSHumanReadableCopyright -string " & (quoted form of ("Copyright © " & (year of (current date)) & " Free Geek 39 | Designed and Developed by Pico Mitchell")) & " " & (quoted form of infoPlistPath) 40 | end try 41 | 42 | try 43 | set minSystemVersion to ((do shell script "/usr/libexec/PlistBuddy -c 'Print :LSMinimumSystemVersion' " & (quoted form of infoPlistPath)) as text) 44 | if (minSystemVersion is not equal to "10.13") then error "INCORRECT Minimum System Version" 45 | on error 46 | do shell script "plutil -remove LSMinimumSystemVersionByArchitecture " & (quoted form of infoPlistPath) & "; plutil -replace LSMinimumSystemVersion -string '10.13' " & (quoted form of infoPlistPath) 47 | end try 48 | 49 | try 50 | set prohibitMultipleInstances to ((do shell script "/usr/libexec/PlistBuddy -c 'Print :LSMultipleInstancesProhibited' " & (quoted form of infoPlistPath)) as number) 51 | if (prohibitMultipleInstances is equal to 0) then error "INCORRECT Multiple Instances Prohibited" 52 | on error 53 | do shell script "plutil -replace LSMultipleInstancesProhibited -bool true " & (quoted form of infoPlistPath) 54 | end try 55 | 56 | try 57 | set allowMixedLocalizations to ((do shell script "/usr/libexec/PlistBuddy -c 'Print :CFBundleAllowMixedLocalizations' " & (quoted form of infoPlistPath)) as number) 58 | if (allowMixedLocalizations is equal to 1) then error "INCORRECT Localization" 59 | on error 60 | do shell script "plutil -replace CFBundleAllowMixedLocalizations -bool false " & (quoted form of infoPlistPath) & "; plutil -replace CFBundleDevelopmentRegion -string 'en_US' " & (quoted form of infoPlistPath) 61 | end try 62 | 63 | try 64 | set currentAppleEventsUsageDescription to ((do shell script "/usr/libexec/PlistBuddy -c 'Print :NSAppleEventsUsageDescription' " & (quoted form of infoPlistPath)) as text) 65 | if (currentAppleEventsUsageDescription does not contain (name of me)) then error "INCORRECT AppleEvents Usage Description" 66 | on error 67 | do shell script "plutil -replace NSAppleEventsUsageDescription -string " & (quoted form of ("You MUST click the “OK” button for “" & (name of me) & "” to be able to function.")) & " " & (quoted form of infoPlistPath) 68 | end try 69 | 70 | try 71 | set currentVersion to ((do shell script "/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' " & (quoted form of infoPlistPath)) as text) 72 | if (currentVersion is equal to "1.0") then error "INCORRECT Version" 73 | on error 74 | set shortCreationDateString to (short date string of (creation date of (info for (path to me)))) 75 | set AppleScript's text item delimiters to "/" 76 | set correctVersion to ((text item 3 of shortCreationDateString) & "." & (text item 1 of shortCreationDateString) & "." & (text item 2 of shortCreationDateString)) 77 | do shell script "plutil -remove CFBundleVersion " & (quoted form of infoPlistPath) & "; plutil -replace CFBundleShortVersionString -string " & (quoted form of correctVersion) & " " & (quoted form of infoPlistPath) 78 | end try 79 | 80 | -- The "main.scpt" must NOT be writable to prevent the code signature from being invalidated: https://developer.apple.com/library/archive/releasenotes/AppleScript/RN-AppleScript/RN-10_8/RN-10_8.html#//apple_ref/doc/uid/TP40000982-CH108-SW8 81 | do shell script "osascript -e 'delay 0.5' -e 'repeat while (application \"" & (POSIX path of (path to me)) & "\" is running)' -e 'delay 0.5' -e 'end repeat' -e 'try' -e 'do shell script \"chmod a-w \\\"" & ((POSIX path of (path to me)) & "Contents/Resources/Scripts/main.scpt") & "\\\"\"' -e 'do shell script \"codesign -fs \\\"Developer ID Application\\\" --strict \\\"" & (POSIX path of (path to me)) & "\\\"\"' -e 'on error codeSignError' -e 'activate' -e 'display alert \"Code Sign Error\" message codeSignError' -e 'end try' -e 'do shell script \"open -na \\\"" & (POSIX path of (path to me)) & "\\\"\"' > /dev/null 2>&1 &" 82 | quit 83 | delay 10 84 | end try 85 | end try 86 | 87 | -- /Library/Caches/* ?? 88 | -- /System/Library/Caches/* ?? 89 | -- Delete user caches ?? 90 | 91 | set AppleScript's text item delimiters to "" 92 | set tmpPath to ((POSIX path of (((path to temporary items) as text) & "::")) & "fg" & ((words of (name of me)) as text) & "-") -- On Catalina, writing to trailing folder "/TemporaryItems/" often fails with "Operation not permitted" for some reason. Also, prefix all files with "fg" and name of script. 93 | 94 | try -- CLEAN MAC TEST BOOT 95 | (("/Volumes/Mac Test Boot/" as POSIX file) as alias) 96 | 97 | -- Delete a few things from: https://bombich.com/kb/ccc5/some-files-and-folders-are-automatically-excluded-from-backup-task 98 | -- Delete vm and temporary files 99 | -- "com.bombich.ccc" get's created if drive was selected with Carbon Copy Cloner 100 | do shell script ("rm -rf '/Volumes/Mac Test Boot/usr/local/bin/' " & ¬ 101 | "'/Volumes/Mac Test Boot/Users/Shared/Build Info/' " & ¬ 102 | "'/Volumes/Mac Test Boot/private/var/db/softwareupdate/journal.plist' " & ¬ 103 | "'/Volumes/Mac Test Boot/.fseventsd' " & ¬ 104 | "'/Volumes/Mac Test Boot/private/var/db/systemstats' " & ¬ 105 | "'/Volumes/Mac Test Boot/private/var/db/dyld/dyld_'* " & ¬ 106 | "'/Volumes/Mac Test Boot/.VolumeIcon.icns' " & ¬ 107 | "'/Volumes/Mac Test Boot/private/var/vm/'* " & ¬ 108 | "'/Volumes/Mac Test Boot/private/var/folders/'* " & ¬ 109 | "'/Volumes/Mac Test Boot/private/var/tmp/'* " & ¬ 110 | "'/Volumes/Mac Test Boot/private/tmp/'* " & ¬ 111 | "'/Volumes/Mac Test Boot/Library/Application Support/com.bombich.ccc' " & ¬ 112 | "'/Volumes/Mac Test Boot/Users/'*'/Desktop/QA Helper - Computer Specs.txt' " & ¬ 113 | "'/Volumes/Mac Test Boot/Users/'*'/Desktop/TESTING' " & ¬ 114 | "'/Volumes/Mac Test Boot/Users/'*'/Desktop/REINSTALL' " & ¬ 115 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/ByHost/' " & ¬ 116 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Application Support/com.apple.sharedfilelist/' " & ¬ 117 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Application Support/App Store/updatejournal.plist' " & ¬ 118 | "'/Volumes/Mac Test Boot/Users/'*'/.bash_history' " & ¬ 119 | "'/Volumes/Mac Test Boot/Users/'*'/.bash_sessions/' " & ¬ 120 | "'/Volumes/Mac Test Boot/Users/'*'/_geeks3d_gputest_log.txt' " & ¬ 121 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Safari' " & ¬ 122 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Caches/Apple - Safari - Safari Extensions Gallery' " & ¬ 123 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Caches/Metadata/Safari' " & ¬ 124 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Caches/com.apple.Safari' " & ¬ 125 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Caches/com.apple.WebKit.PluginProcess' " & ¬ 126 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Cookies/Cookies.binarycookies' " & ¬ 127 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/Apple - Safari - Safari Extensions Gallery' " & ¬ 128 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/com.apple.Safari.LSSharedFileList.plist' " & ¬ 129 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/com.apple.Safari.RSS.plist' " & ¬ 130 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/com.apple.Safari.plist' " & ¬ 131 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/com.apple.Safari.SafeBrowsing.plist' " & ¬ 132 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/com.apple.Safari.SandboxBroker.plist' " & ¬ 133 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/com.apple.SafariBookmarksSyncAgent.plist' " & ¬ 134 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/com.apple.SafariCloudHistoryPushAgent.plist' " & ¬ 135 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/com.apple.WebFoundation.plist' " & ¬ 136 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/com.apple.WebKit.PluginHost.plist' " & ¬ 137 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/com.apple.WebKit.PluginProcess.plist' " & ¬ 138 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/com.apple.SystemProfiler.plist' " & ¬ 139 | "'/Volumes/Mac Test Boot/Users/'*'/Library/PubSub/Database' " & ¬ 140 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Saved Application State/com.apple.Safari.savedState' " & ¬ 141 | "'/Volumes/Mac Test Boot/Users/'*'/Pictures/GPU Stress Test/' " & ¬ 142 | "'/Volumes/Mac Test Boot/Users/'*'/Music/iTunes/'") 143 | 144 | tell application id "com.apple.Terminal" 145 | activate 146 | do script "find '/Volumes/Mac Test Boot' -name '.DS_Store' -type f -print -delete" 147 | activate 148 | end tell 149 | 150 | delay 2 151 | end try 152 | -------------------------------------------------------------------------------- /Build Tools/Create macOS USB Installer Commands.txt: -------------------------------------------------------------------------------- 1 | # THIS IS NOT A SCRIPT. 2 | # THE FOLLOWING COMMANDS ARE INTENDED TO BE COPIED-AND-PASTED INTO TERMINAL WHEN NEEDED. 3 | # BUT, THIS *IS NOT NEEDED ANYMORE* SINCE "update-mac-drives.sh" CAN BE USED INSTEAD. 4 | 5 | # CREATE Sonoma INSTALLER USB (15.2 GB REQUIRED): 6 | sudo '/Volumes/Install macOS Sonoma beta/Install macOS Sonoma beta.app/Contents/Resources/createinstallmedia' --volume /Volumes/InstallSonoma --nointeraction 7 | 8 | # CREATE Ventura INSTALLER USB (14.8 GB REQUIRED): 9 | sudo '/Volumes/Install macOS Ventura/Install macOS Ventura.app/Contents/Resources/createinstallmedia' --volume /Volumes/InstallVentura --nointeraction 10 | 11 | # CREATE Monterey INSTALLER USB (14.7 GB REQUIRED): 12 | sudo '/Volumes/Install macOS Monterey/Install macOS Monterey.app/Contents/Resources/createinstallmedia' --volume /Volumes/InstallMonterey --nointeraction 13 | 14 | # CREATE Big Sur INSTALLER USB (14 GB REQUIRED): 15 | sudo '/Volumes/Install macOS Big Sur/Install macOS Big Sur.app/Contents/Resources/createinstallmedia' --volume /Volumes/InstallBigSur --nointeraction 16 | 17 | # CREATE Catalina INSTALLER USB (9 GB REQUIRED): 18 | sudo '/Volumes/Install macOS Catalina/Install macOS Catalina.app/Contents/Resources/createinstallmedia' --volume /Volumes/InstallCatalina --nointeraction 19 | 20 | # CREATE High Sierra INSTALLER USB (6 GB REQUIRED): 21 | sudo '/Volumes/Install macOS High Sierra/Install macOS High Sierra.app/Contents/Resources/createinstallmedia' --volume /Volumes/InstallHighSierra --nointeraction 22 | 23 | 24 | 25 | # UNUSED INSTALLER COMMANDS 26 | 27 | # CREATE Mojave INSTALLER USB (7 GB REQUIRED): 28 | sudo '/Volumes/Install macOS Mojave/Install macOS Mojave.app/Contents/Resources/createinstallmedia' --volume /Volumes/InstallMojave --nointeraction 29 | 30 | # CREATE Sierra INSTALLER USB (6 GB REQUIRED) 31 | sudo '/Applications/Install macOS Sierra.app/Contents/Resources/createinstallmedia' --applicationpath '/Applications/Install macOS Sierra.app' --volume /Volumes/InstallSierra --nointeraction 32 | # REQUIRED FIX (https://forums.macrumors.com/threads/not-a-valid-volume-mount-point-cant-make-bootable-drive.1935673/page-3?post=28197924#post-28197924): 33 | sudo plutil -replace CFBundleShortVersionString -string '12.6.03' '/Applications/Install macOS Sierra.app/Contents/Info.plist' 34 | 35 | # CREATE El Capitan INSTALLER USB (7 GB REQUIRED): 36 | sudo '/Applications/Install OS X El Capitan.app/Contents/Resources/createinstallmedia' --applicationpath '/Applications/Install OS X El Capitan.app' --volume /Volumes/InstallElCapitan --nointeraction 37 | 38 | # CREATE Yosemite INSTALLER USB (6 GB REQUIRED) 39 | sudo '/Applications/Install OS X Yosemite.app/Contents/Resources/createinstallmedia' --applicationpath '/Applications/Install OS X Yosemite.app' --volume /Volumes/InstallYosemite --nointeraction 40 | 41 | -------------------------------------------------------------------------------- /Build Tools/Download-macOS-Installer-DMGs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck enable=add-default-case,avoid-nullary-conditions,check-unassigned-uppercase,deprecate-which,quote-safe-variables,require-double-brackets 3 | 4 | # 5 | # MIT License 6 | # 7 | # Copyright (c) 2021 Free Geek 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 10 | # to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 17 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | # 19 | 20 | PATH='/usr/bin:/bin:/usr/sbin:/sbin' 21 | 22 | readonly MIST_PATH='/usr/local/bin/mist' 23 | 24 | installer_dmgs_path="${HOME}/Documents/Programming/Free Geek/MacLand Images/macOS Installers" 25 | 26 | declare -a installer_names_to_download=( 'Big Sur' 'Monterey' 'Ventura' 'Sonoma beta' ) # NOT including 'High Sierra' 'Mojave' 'Catalina' anymore since the latest installers are already downloaded and they will never get any new updates. 27 | 28 | for this_installer_name_to_download in "${installer_names_to_download[@]}"; do 29 | catalog_url='https://swscan.apple.com/content/catalogs/others/index-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' 30 | 31 | mist_list_options=( 'list' 'installer' "${this_installer_name_to_download}" ) 32 | if [[ "${this_installer_name_to_download}" == *' beta' ]]; then 33 | catalog_url='https://swscan.apple.com/content/catalogs/others/index-14seed-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.gz' 34 | mist_list_options+=( '-b' ) 35 | fi 36 | mist_list_options+=( '-c' "${catalog_url}" '-lqo' 'json' ) 37 | 38 | this_installer_info_json="$("${MIST_PATH}" "${mist_list_options[@]}")" 39 | 40 | IFS=$'\n' read -rd '' -a this_installer_info < <(osascript -l 'JavaScript' -e ' 41 | function run(argv) { 42 | const latestInstallerDict = JSON.parse(argv[0])[0] 43 | return [latestInstallerDict.name, latestInstallerDict.version, latestInstallerDict.build].join("\n") 44 | } 45 | ' -- "${this_installer_info_json}" 2> /dev/null) 46 | # NOTE: Because of JavaScript behavior, any "undefined" (or "null") values in an array would be turned into empty strings when using "join", making them empty lines. 47 | # And, because of bash behaviors with whitespace IFS treating consecutive whitespace as a single delimiter (explained in https://mywiki.wooledge.org/IFS), 48 | # any empty lines will NOT be included in the bash array being created with this technique to set all lines to an array. 49 | # So, that means if any of these values are not found, the bash array WILL NOT have a count of exactly 3 which we can check to verify all required values were properly loaded. 50 | 51 | if (( ${#this_installer_info[@]} == 3 )); then 52 | this_installer_dmg_name="Install ${this_installer_info[0]} ${this_installer_info[1]}-${this_installer_info[2]}.dmg" 53 | 54 | if [[ -f "${installer_dmgs_path}/${this_installer_dmg_name}" ]]; then 55 | echo "\"${this_installer_dmg_name}\" is up-to-date!" 56 | else 57 | echo "\"${this_installer_dmg_name}\" needs to be downloaded..." 58 | rm -f "${installer_dmgs_path}/Install ${this_installer_info[0]} "*'.dmg' # Delete any outdated installer dmgs. 59 | 60 | mist_download_options=( 'download' 'installer' "${this_installer_info[2]}" 'image' ) 61 | if [[ "${this_installer_name_to_download}" == *' beta' ]]; then 62 | mist_download_options+=( '-b' ) 63 | fi 64 | mist_download_options+=( '-c' "${catalog_url}" '-o' "${installer_dmgs_path}" ) 65 | 66 | sudo "${MIST_PATH}" "${mist_download_options[@]}" 67 | fi 68 | else 69 | echo "\"${this_installer_name_to_download}\" WAS NOT FOUND: $(declare -p this_installer_info | cut -d '=' -f 2-)" 70 | fi 71 | done 72 | -------------------------------------------------------------------------------- /Build Tools/Mac Test Boot Disk Image Commands.txt: -------------------------------------------------------------------------------- 1 | # THIS IS NOT A SCRIPT. 2 | # THE FOLLOWING COMMANDS ARE INTENDED TO BE COPIED-AND-PASTED INTO TERMINAL WHEN NEEDED. 3 | 4 | # Command to CREATE Mac Test Boot DISK IMAGE from SOURCE drive: 5 | 6 | cd "${HOME}/Documents/Programming/Free Geek/MacLand Images/" && read "?'Mac Test Boot' Device Name (Probably $(diskutil list | awk '/Mac Test Boot/ { print $NF; exit }')): " device_name && echo "$(date '+%Y%m%d')" > '/Volumes/Mac Test Boot/private/var/root/.mtbVersion' && diskutil unmountDisk "${device_name}" || diskutil unmountDisk force "${device_name}"; sudo hdiutil create "FreeGeek-MacTestBoot-HighSierra-$(date +%Y%m%d).dmg" -srcdevice "${device_name}" && sudo asr imagescan --source "FreeGeek-MacTestBoot-HighSierra-$(date +%Y%m%d).dmg" 7 | 8 | 9 | # Command to FORMAT & PARTITION fgMIB + macOS Installers + Mac Test Boot: 10 | # NOTE: For some reason (at least as of macOS 12.6.1 Monterey) each desired size need 0.13G added to it to result in the correct desired size. The sizes (plus 0.13G) being used for each installer are specified in the "Create macOS USB Installer Commands.txt" based on testing to find the minimum required size for each macOS version installer. 11 | 12 | read '?Enter Disk ID to Format and Partition for fgMIB + macOS Installers + Mac Test Boot: disk' disk_id && diskutil partitionDisk "disk${disk_id}" 6 GPT JHFS+ 'fgMIB' 2.13G JHFS+ 'Install macOS Catalina' 9.13G JHFS+ 'Install macOS Big Sur' 14.13G JHFS+ 'Install macOS Monterey' 14.83G JHFS+ 'Install macOS Ventura' 14.93G JHFS+ 'Mac Test Boot' 0B 13 | 14 | 15 | 16 | # NO LONGER USED COMMANDS 17 | # Command to Restore Mac Test Boot 18 | 19 | # VOLUMES ARE SUFFIXED WITH RANDOM NUMBER FOR LATER IDENTIFICATION 20 | # SO MULTIPLE DRIVES CAN GET STARTED AND RUN AT ONCE WITHOUT CONFLICTING 21 | # BUT, THIS *IS NOT NEEDED ANYMORE* SINCE "update-mac-drives.sh" CAN BE USED INSTEAD 22 | 23 | MTB_IMAGE_DATE=20220412; MTB_SOURCE_IMAGE=FreeGeek-MacTestBoot-HighSierra-$MTB_IMAGE_DATE.dmg; RANDOM_SUFFIX=$RANDOM; diskutil rename MTB MTB-$RANDOM_SUFFIX; cd "$HOME/Documents/Programming/Free Geek/MacLand Images/" && echo "RESTORING '$MTB_SOURCE_IMAGE' TO 'MTB-$RANDOM_SUFFIX'" && sudo asr restore --source $MTB_SOURCE_IMAGE --target /Volumes/MTB-$RANDOM_SUFFIX --erase --noprompt 24 | 25 | 26 | # Command to Restore Mac Test Boot with High Sierra, Catalina, and Big Sur Installers 27 | # BUT, THIS *IS NOT NEEDED ANYMORE* SINCE "update-mac-drives.sh" CAN BE USED INSTEAD 28 | 29 | MTB_IMAGE_DATE=20210712; MTB_SOURCE_IMAGE=FreeGeek-MacTestBoot-HighSierra-$MTB_IMAGE_DATE.dmg; RANDOM_SUFFIX=$RANDOM; diskutil rename MTB MTB-$RANDOM_SUFFIX; diskutil rename InstallHighSierra InstallHighSierra-$RANDOM_SUFFIX; diskutil rename InstallCatalina InstallCatalina-$RANDOM_SUFFIX; diskutil rename InstallBigSur InstallBigSur-$RANDOM_SUFFIX; cd "$HOME/Documents/Programming/Free Geek/MacLand Images/" && echo "RESTORING '$MTB_SOURCE_IMAGE' TO 'MTB-$RANDOM_SUFFIX'" && sudo asr restore --source $MTB_SOURCE_IMAGE --target /Volumes/MTB-$RANDOM_SUFFIX --erase --noprompt; echo "CREATING HIGH SIERRA INSTALLER ON 'InstallHighSierra-$RANDOM_SUFFIX'" && sudo '/Volumes/Install macOS High Sierra/Install macOS High Sierra.app/Contents/Resources/createinstallmedia' --volume /Volumes/InstallHighSierra-$RANDOM_SUFFIX --nointeraction; echo "CREATING CATALINA INSTALLER ON 'InstallCatalina-$RANDOM_SUFFIX'" && sudo '/Volumes/Install macOS Catalina/Install macOS Catalina.app/Contents/Resources/createinstallmedia' --volume /Volumes/InstallCatalina-$RANDOM_SUFFIX --nointeraction; echo "CREATING BIG SUR INSTALLER ON 'InstallBigSur-$RANDOM_SUFFIX'" && sudo '/Volumes/Install macOS Big Sur/Install macOS Big Sur.app/Contents/Resources/createinstallmedia' --volume /Volumes/InstallBigSur-$RANDOM_SUFFIX --nointeraction 30 | 31 | 32 | # Command to Create Production OS Images 33 | 34 | cd "$HOME/Documents/Programming/Free Geek/MacLand Images/Restore Images/" && read "?'High Sierra HD' Device Name (Probably $(diskutil list | grep "High Sierra HD" | head -1 | awk '{ print $NF }' | rev | cut -c 3- | rev)): " DEVICE_NAME && diskutil unmountDisk $DEVICE_NAME || diskutil unmountDisk force $DEVICE_NAME; sudo hdiutil create "HighSierra-ProductionRestore-$(date +%Y%m%d).dmg" -srcdevice $DEVICE_NAME && sudo asr imagescan --source "HighSierra-ProductionRestore-$(date +%Y%m%d).dmg" 35 | 36 | cd "$HOME/Documents/Programming/Free Geek/MacLand Images/Restore Images/" && read "?'Catalina HD' Device Name (Probably $(diskutil info $(diskutil list | grep "Catalina HD" | head -1 | awk '{ print $NF }') | grep "APFS Physical Store" | awk '{ print $NF }' | rev | cut -c 3- | rev)): " DEVICE_NAME && diskutil unmountDisk $DEVICE_NAME || diskutil unmountDisk force $DEVICE_NAME; sudo hdiutil create "Catalina-ProductionRestore-$(date +%Y%m%d).dmg" -srcdevice $DEVICE_NAME && sudo asr imagescan --source "Catalina-ProductionRestore-$(date +%Y%m%d).dmg" 37 | 38 | 39 | # Commands to Create Catalina Restore Boot 40 | 41 | cd "$HOME/Documents/Programming/Free Geek/MacLand Images/" && read "?'Catalina Restore Boot' Device Name (Probably $(diskutil list | grep "Catalina Restore Boot" | head -1 | awk '{ print $NF }' | rev | cut -c 3- | rev)): " DEVICE_NAME && diskutil unmountDisk $DEVICE_NAME || diskutil unmountDisk force $DEVICE_NAME; sudo hdiutil create "FreeGeek-CatalinaRestoreBoot-Catalina-$(date +%Y%m%d).dmg" -srcdevice $DEVICE_NAME && sudo asr imagescan --source "FreeGeek-CatalinaRestoreBoot-Catalina-$(date +%Y%m%d).dmg" 42 | 43 | 44 | # Commands to Restore Mac Test Boot and Catalina Restore Boot 45 | 46 | # DO THESE RESTORES IN SERIES FOR EACH DRIVE BECAUSE CATALINA 47 | # WILL OFTEN TIMEOUT IF DONE IN PARALLEL ON THE SAME DRIVE 48 | 49 | # VOLUMES ARE SUFFIXED WITH RANDOM NUMBER FOR LATER IDENTIFICATION 50 | # SO MULTIPLE DRIVES CAN GET STARTED AND RUN AT ONCE WITHOUT CONFLICTING 51 | 52 | MTB_IMAGE_DATE=20210215; CRB_IMAGE_DATE=20210215; MTB_SOURCE_IMAGE=FreeGeek-MacTestBoot-HighSierra-$MTB_IMAGE_DATE.dmg; CRB_SOURCE_IMAGE=FreeGeek-CatalinaRestoreBoot-Catalina-$CRB_IMAGE_DATE.dmg; RANDOM_SUFFIX=$RANDOM; diskutil rename MTB MTB-$RANDOM_SUFFIX; diskutil rename CRB CRB-$RANDOM_SUFFIX; cd "$HOME/Documents/Programming/Free Geek/MacLand Images/Previous Test Boot Archive/" && echo "RESTORING '$MTB_SOURCE_IMAGE' TO 'MTB-$RANDOM_SUFFIX'" && sudo asr restore --source $MTB_SOURCE_IMAGE --target /Volumes/MTB-$RANDOM_SUFFIX --erase --noprompt; echo "RESTORING '$CRB_SOURCE_IMAGE' TO 'CRB-$RANDOM_SUFFIX'" && sudo asr restore --source "Legacy Mac Test Boot/$CRB_SOURCE_IMAGE" --target /Volumes/CRB-$RANDOM_SUFFIX --erase --noprompt 53 | 54 | 55 | # LEGACY MAC TEST BOOT RESTORE: 56 | 57 | LMTB_IMAGE_DATE=121019; LMTB_SOURCE_IMAGE=FreeGeek-MacTestBoot-ElCapitan-$LMTB_IMAGE_DATE.dmg; RANDOM_SUFFIX=$RANDOM; diskutil rename LMTB LMTB-$RANDOM_SUFFIX && cd "$HOME/Documents/Programming/Free Geek/MacLand Images/Previous Test Boot Archive/Legacy Mac Test Boot" && echo "RESTORING '$LMTB_SOURCE_IMAGE' TO 'LMTB-$RANDOM_SUFFIX'"; sudo asr restore --source $LMTB_SOURCE_IMAGE --target /Volumes/LMTB-$RANDOM_SUFFIX --erase --noprompt 58 | 59 | 60 | # LEGACY APFS RESTORE BOOT RESTORE: 61 | 62 | ARB_IMAGE_DATE=121019; ARB_SOURCE_IMAGE=FreeGeek-APFSRestoreBoot-HighSierra-$ARB_IMAGE_DATE.dmg; RANDOM_SUFFIX=$RANDOM; diskutil rename ARB ARB-$RANDOM_SUFFIX && cd "$HOME/Documents/Programming/Free Geek/MacLand Images/Previous Test Boot Archive/Legacy Mac Test Boot" && echo "RESTORING '$ARB_SOURCE_IMAGE' TO 'ARB-$RANDOM_SUFFIX'"; sudo asr restore --source $ARB_SOURCE_IMAGE --target /Volumes/ARB-$RANDOM_SUFFIX --erase --noprompt 63 | -------------------------------------------------------------------------------- /Build Tools/update-mac-drives.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck enable=add-default-case,avoid-nullary-conditions,check-unassigned-uppercase,deprecate-which,quote-safe-variables,require-double-brackets 3 | 4 | # 5 | # MIT License 6 | # 7 | # Copyright (c) 2022 Free Geek 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 10 | # to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 17 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | # 19 | 20 | PATH='/usr/bin:/bin:/usr/sbin:/sbin:/usr/libexec' # Add "/usr/libexec" to PATH for easy access to PlistBuddy. 21 | 22 | PROJECT_DIR="$(cd "${BASH_SOURCE[0]%/*}" &> /dev/null && pwd -P)/.." 23 | readonly PROJECT_DIR 24 | 25 | if (( ${EUID:-$(id -u)} != 0 )); then 26 | >&2 echo 'ERROR: This script must be run as root (to be able to run the "createinstallmedia" and "asr" commands).' 27 | afplay /System/Library/Sounds/Basso.aiff 28 | exit 1 29 | fi 30 | 31 | 32 | declare -a installer_names_to_update=( 'High Sierra' 'Mojave' 'Catalina' 'Big Sur' 'Monterey' 'Ventura' 'Sonoma beta' ) 33 | 34 | for this_installer_name_to_update in "${installer_names_to_update[@]}"; do 35 | found_connected_installer_for_os=false 36 | # Suppress ShellCheck suggestion to use "find" instead of "ls" since we need "ls -t" to sort by modification date to easily get a single result of the newest installer, and this path will never contain non-alphanumeric characters. 37 | # shellcheck disable=SC2012 38 | installer_dmg_path="$(ls -t "${PROJECT_DIR}/../../MacLand Images/macOS Installers/Install macOS ${this_installer_name_to_update}"*'.dmg' | head -1)" 39 | echo -e "\nMounting Installer DMG \"${installer_dmg_path##*/}\"..." 40 | installer_source_volume="$(hdiutil attach "${installer_dmg_path}" -nobrowse -readonly -plist 2> /dev/null | xmllint --xpath 'string(//string[starts-with(text(), "/Volumes/")])' - 2> /dev/null)" 41 | if [[ -d "${installer_source_volume}" ]]; then 42 | installer_source_version="$(PlistBuddy -c 'Print :CFBundleVersion' "${installer_source_volume}/Install macOS ${this_installer_name_to_update}.app/Contents/version.plist")" 43 | 44 | echo "Mounted Installer DMG at \"${installer_source_volume}\" & Updating Connected Installers..." 45 | for this_os_installer_volume in "/Volumes/Install macOS ${this_installer_name_to_update}"*; do 46 | if [[ -d "${this_os_installer_volume}" && "${this_os_installer_volume}" != "${installer_source_volume}" ]]; then 47 | found_connected_installer_for_os=true 48 | this_os_installer_version="$(PlistBuddy -c 'Print :CFBundleVersion' "${this_os_installer_volume}/Install macOS ${this_installer_name_to_update}.app/Contents/version.plist")" 49 | if [[ "${this_os_installer_version}" != "${installer_source_version}" ]]; then 50 | echo "Updating Connected Installer at \"${this_os_installer_volume}\"..." 51 | "${installer_source_volume}/Install macOS ${this_installer_name_to_update}.app/Contents/Resources/createinstallmedia" --volume "${this_os_installer_volume}" --nointeraction & 52 | sleep 10 # Sleep a bit before starting the next "createinstallmedia" process since I've seen them fail consistently with "Couldn't find InstallInfo.plist" and "The bless of the installer disk failed." when two Montery "createinstallmedia" processes were started at the same time. 53 | else 54 | echo "Connected Installer at \"${this_os_installer_volume}\" Already Up-to-Date" 55 | fi 56 | fi 57 | done 58 | 59 | wait # Wait for child "createinstallmedia" processes for this OS version to finish before moving to the next to not tax each drive too much by writing to multiple partitions at the same time which tends to cause more failures. 60 | 61 | some_update_failed=false 62 | if ! $found_connected_installer_for_os; then 63 | echo "No Connected ${this_installer_name_to_update} Installers Found" 64 | else 65 | for this_os_installer_volume in "/Volumes/Install macOS ${this_installer_name_to_update}"*; do 66 | if [[ -d "${this_os_installer_volume}" && "${this_os_installer_volume}" != "${installer_source_volume}" ]]; then 67 | this_os_installer_version="$(PlistBuddy -c 'Print :CFBundleVersion' "${this_os_installer_volume}/Install macOS ${this_installer_name_to_update}.app/Contents/version.plist")" 68 | if [[ "${this_os_installer_version}" != "${installer_source_version}" ]]; then 69 | >&2 echo "ERROR: Failed to update connected installer at \"${this_os_installer_volume}\"." 70 | some_update_failed=true 71 | else 72 | echo "Unmounting Connected Installer at \"${this_os_installer_volume}\"..." 73 | diskutil unmount "${this_os_installer_volume}" &> /dev/null || diskutil unmount force "${this_os_installer_volume}" &> /dev/null || >&2 echo "ERROR: Failed to unmount connected installer at \"${this_os_installer_volume}\"." 74 | fi 75 | fi 76 | done 77 | fi 78 | 79 | echo "Unmounting Installer DMG at \"${installer_source_volume}\"..." 80 | hdiutil detach "${installer_source_volume}" &> /dev/null || hdiutil detach "${installer_source_volume}" -force &> /dev/null || >&2 echo "ERROR: Failed to unmount DMG at \"${installer_source_volume}\"." 81 | 82 | if $some_update_failed; then 83 | afplay /System/Library/Sounds/Basso.aiff 84 | exit 2 85 | fi 86 | else 87 | >&2 echo "ERROR: \"${this_installer_name_to_update}\" Installer DMG was not found or mounted." 88 | afplay /System/Library/Sounds/Basso.aiff 89 | exit 3 90 | fi 91 | done 92 | 93 | 94 | found_connected_mtb=false 95 | # Suppress ShellCheck suggestion to use "find" instead of "ls" since we need "ls -t" to sort by modification date to easily get a single result of the newest MTB image, and this path will never contain non-alphanumeric characters. 96 | # shellcheck disable=SC2012 97 | mtb_dmg_path="$(ls -t "${PROJECT_DIR}/../../MacLand Images/FreeGeek-MacTestBoot-"*'.dmg' | head -1)" 98 | echo -e "\nMounting MTB Source DMG \"${mtb_dmg_path##*/}\" to Get Version..." 99 | mtb_source_volume="$(hdiutil attach "${mtb_dmg_path}" -nobrowse -readonly -plist 2> /dev/null | xmllint --xpath 'string(//string[starts-with(text(), "/Volumes/")])' - 2> /dev/null)" 100 | if [[ -d "${mtb_source_volume}" ]]; then 101 | mtb_source_version="$(< "${mtb_source_volume}/private/var/root/.mtbVersion")" 102 | echo "MTB Source Version: ${mtb_source_version}" 103 | echo "Unmounting MTB Source DMG at \"${mtb_source_volume}\"..." 104 | hdiutil detach "${mtb_source_volume}" &> /dev/null || hdiutil detach "${mtb_source_volume}" -force &> /dev/null || >&2 echo "ERROR: Failed to unmount DMG at \"${mtb_source_volume}\"." 105 | 106 | echo -e '\nUpdating Connected MTBs...' 107 | for this_mtb_volume in '/Volumes/Mac Test Boot'*; do 108 | if [[ -d "${this_mtb_volume}" ]]; then 109 | found_connected_mtb=true 110 | this_connected_mtb_version="$(< "${this_mtb_volume}/private/var/root/.mtbVersion")" 111 | if [[ "${this_connected_mtb_version}" != "${mtb_source_version}" ]]; then 112 | echo "Updating Connected MTB at \"${this_mtb_volume}\"..." 113 | asr restore --source "${mtb_dmg_path}" --target "${this_mtb_volume}" --erase --noprompt & 114 | else 115 | echo "Connected MTB at \"${this_mtb_volume}\" Already Up-to-Date" 116 | fi 117 | fi 118 | done 119 | 120 | wait # Wait for child "asr" processes for the MTBs to finish before moving on. 121 | 122 | some_mtb_failed=false 123 | if ! $found_connected_mtb; then 124 | echo 'No Connected MTBs Found' 125 | else 126 | for this_mtb_volume in '/Volumes/Mac Test Boot'*; do 127 | if [[ -d "${this_mtb_volume}" ]]; then 128 | this_connected_mtb_version="$(< "${this_mtb_volume}/private/var/root/.mtbVersion")" 129 | if [[ "${this_connected_mtb_version}" != "${mtb_source_version}" ]]; then 130 | >&2 echo "ERROR: Failed to update connected MTB at \"${this_mtb_volume}\"." 131 | some_mtb_failed=true 132 | else 133 | echo "Unmounting Connected MTB at \"${this_mtb_volume}\"..." 134 | diskutil unmount "${this_mtb_volume}" &> /dev/null || diskutil unmount force "${this_mtb_volume}" &> /dev/null || >&2 echo "ERROR: Failed to unmount connected MTB at \"${this_mtb_volume}\"." 135 | fi 136 | fi 137 | done 138 | fi 139 | 140 | if $some_mtb_failed; then 141 | afplay /System/Library/Sounds/Basso.aiff 142 | exit 5 143 | fi 144 | else 145 | >&2 echo "ERROR: \"${mtb_dmg_path}\" MTB source DMG was not found or mounted." 146 | afplay /System/Library/Sounds/Basso.aiff 147 | exit 4 148 | fi 149 | 150 | echo -e '\nUpdating Connected fgMIBs...' 151 | bash "${PROJECT_DIR}/fgMIB Resources/Build Tools/copy-fgMIB-resources-to-drives.sh" 152 | 153 | afplay /System/Library/Sounds/Glass.aiff 154 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Free Geek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Other Scripts/every_intel_mac_marketing_model_name_with_grouped_model_ids_and_serial_config_codes.txt: -------------------------------------------------------------------------------- 1 | iMac (17-inch, Early 2006):iMac4,1:U2N:U2R:V4M:V4N:V4U:V66:VGB:VGZ:VH1:VHP:VV4:VV6: 2 | iMac (17-inch, Late 2006 CD):iMac5,2: 3 | iMac (17-inch, Late 2006):iMac5,1:AC1:VUX:VUY:WAR:WRR:WRW:WV8:WVR:X1A:X1W:X2W:X6Q:X9F:X9Y:XLF:Y3V:Y3W:Y3X:Y6K:Y94:Y97:YAG:YLJ: 4 | iMac (17-inch, Mid 2006):iMac4,2: 5 | iMac (20-inch, Early 2006):iMac4,1:U2P:U2S:V4P:V4Q:V4R:V67:VGC:VGM:VH0:VH2:VW4:VX0:WXN:X0U: 6 | iMac (20-inch, Early 2008):iMac8,1:28B:2PN:2PR:3FF:3FG:3SZ:5A8:5J0:6F9:8R2:8R3:ZE2:ZE3:ZE5:ZE6: 7 | iMac (20-inch, Early 2009):iMac9,1:0TF:0TH:6X0:8M5:8TS:8TT:9EX:9LN: 8 | iMac (20-inch, Late 2006):iMac5,1:VUV:VUW:WRS:WRX:WSD:X0E:X29:X6S:X9E:X9G:XA4:XCR:XCY:Y3R:Y3U:Y9B:YAE:YDW: 9 | iMac (20-inch, Mid 2007):iMac7,1:02X:09Q:0PQ:0PR:0PT:0U1:1NU:1NV:3PB:X85:X86:X87:X88:Z58:Z9G:ZEG:ZFD: 10 | iMac (20-inch, Mid 2009):iMac9,1:6MH:6MJ:9TH:BAH:DMV:DWY:E86:FUN:FXN:GM9:H1S:HS6:HS7:HT6:HUE: 11 | iMac (21.5-inch, 2017):iMac18,1: 12 | iMac (21.5-inch, Early 2013):iMac13,3: 13 | iMac (21.5-inch, Late 2009):iMac10,1:5PC:5PK:B9S:B9U:CY8:DMW:DMX:DWR:DWU:E8D:E8E:E8F:F0G:F0H:FQH:FU1:H9K:HDF: 14 | iMac (21.5-inch, Late 2011):iMac12,1:DKL9:DKLH:DPNK:DPNW: 15 | iMac (21.5-inch, Late 2012):iMac13,1: 16 | iMac (21.5-inch, Late 2013):iMac14,1:iMac14,3: 17 | iMac (21.5-inch, Late 2015):iMac16,1:iMac16,2:GF1J:GF1K:GF1L:GF1M:GG77:GG79:GG7D:GG7G:H0N6:H0P6:H1DX:H1DY:H1F1:H1F2:H1WR:H25M:H2KW:H8KX:HHMG:HQ9T:HQ9V:HQ9W:HYGQ:J0DG:J0DH:J0DJ: 18 | iMac (21.5-inch, Mid 2010):iMac11,2: 19 | iMac (21.5-inch, Mid 2011):iMac12,1:DHJF:DHJN:DHJR:DHJT:DL8M:DL8N:DMP0:DNWY:DPM0:DPNT:DWTP:DWTQ:F611: 20 | iMac (21.5-inch, Mid 2014):iMac14,4: 21 | iMac (24-inch, Early 2008):iMac8,1:0KM:0N4:1LW:28A:2E4:2NX:2PT:39S:3F9:3FH:3GS:3NX:5J1:5U6:6J3:6J6:6ZC:ZE4:ZE7: 22 | iMac (24-inch, Early 2009):iMac9,1:0TG:0TJ:0TL:0TM:250:259:6X1:6X2:6X3:8M6:8XH:9ET:9F3:9LP:9LQ:9LR:9LS:E1B: 23 | iMac (24-inch, Late 2006):iMac6,1: 24 | iMac (24-inch, Mid 2007):iMac7,1:0PL:0PM:0PN:0PP:0PU:1NW:1SC:2CB:3PA:X89:X8A:Z59:Z9F:ZCR:ZCT:ZCV:ZCW:ZEF:ZGH:ZGP: 25 | iMac (27-inch, Late 2009):iMac10,1:iMac11,1:5PE:5PJ:5PM:5RU:CYB:CYC:D4V:DMY:DMZ:DWZ:E1J:F0J:F0K:GRP:H9L:H9N:H9P:H9R: 26 | iMac (27-inch, Late 2012):iMac13,2: 27 | iMac (27-inch, Late 2013):iMac14,2: 28 | iMac (27-inch, Mid 2010):iMac11,3: 29 | iMac (27-inch, Mid 2011):iMac12,2: 30 | iMac (Retina 4K, 21.5-inch, 2017):iMac18,2: 31 | iMac (Retina 4K, 21.5-inch, 2019):iMac19,2: 32 | iMac (Retina 4K, 21.5-inch, Late 2015):iMac16,2:GG78:GG7C:GG7F:GG7H:H0KF:H0P7:H15R:H1F3:H1F5:H1F7:H1F8:H1F9:H25N:H28H:H3RJ:H8KY:H8L0:H8L1:H8L2:H8L3:HLWV: 33 | iMac (Retina 5K, 27-inch, 2017):iMac18,3: 34 | iMac (Retina 5K, 27-inch, 2019):iMac19,1: 35 | iMac (Retina 5K, 27-inch, 2020):iMac20,1:iMac20,2: 36 | iMac (Retina 5K, 27-inch, Late 2014):iMac15,1:FY11:FY14:FY68:FY6F:GCTM:GDQY:GDR3:GDR4:GDR5:GDR6:GDR7:GDR8:GDR9:GDRC:GFFQ:GJDM:GJDN:GJDP:GJDQ:GPJN:GV7V:H5DN:H682: 37 | iMac (Retina 5K, 27-inch, Late 2015):iMac17,1: 38 | iMac (Retina 5K, 27-inch, Mid 2015):iMac15,1:FY10:FY13:FY67:FY6D:GL1Q:GL1R:GL1T:GL1V:GL1W: 39 | iMac Pro (2017):iMacPro1,1: 40 | Mac mini (2018):Macmini8,1: 41 | Mac mini (Early 2006):Macmini1,1:U35:U36:U38:U39:VJN:VLK:VS5:VS7:VU2:VU4:WBZ:WCU:WEN: 42 | Mac mini (Early 2009):Macmini3,1:19X:19Y:1BU:1BV:8NC:92G:9RR:9RS:AFR:BAV: 43 | Mac mini (Late 2006):Macmini1,1:W0A:W0B:W0C:W0D:WKN:X1X:X1Y:X1Z:X20:XAS:Y9E: 44 | Mac mini (Late 2009):Macmini3,1:306:307:9G5:9G6:9G7:9G8:AFK:B9X:CS6:DMG:DMH:F6J: 45 | Mac mini (Late 2012):Macmini6,1:Macmini6,2:DWYL:DWYM:DY3G:DY3H:F9RK:F9RL:F9RM:F9VV:F9VW:F9W0:F9W1:F9W2:FD9G:FD9H:FD9J:FD9K:FDWK:FGML:FRFP:FW56:FW57:G430: 46 | Mac mini (Late 2014):Macmini7,1: 47 | Mac mini (Mid 2007):Macmini2,1: 48 | Mac mini (Mid 2010):Macmini4,1:DD6H:DD6L:DDQ9:DDVN:DFDK: 49 | Mac mini (Mid 2011):Macmini5,1:Macmini5,2: 50 | Mac mini Server (Late 2012):Macmini6,2:DWYN:DY3J:F9VY:F9W3:FC08:FCCW:FP14:FP39: 51 | Mac mini Server (Mid 2010):Macmini4,1:DD6K:DD6N:DDJF: 52 | Mac mini Server (Mid 2011):Macmini5,3: 53 | Mac Pro (2019):MacPro7,1:K7GD:K7GF:NYGV:P7QJ:P7QK:P7QL:P7QM:P7QN:P7QP:PLXV:PLXW:PLXX:PLXY: 54 | Mac Pro (Early 2008):MacPro3,1: 55 | Mac Pro (Early 2009):MacPro4,1:20G:20H:4PC:4PD:7BF:8MC:8PZ:8Q0:8TR:8TU:8XG:8XL:93H:9EU:9EV:9MC:9MD:9MG:9MJ:9MK:9ML:9QK:ANS:BXD:BXE:BXT:CZ2:CZ3:CZ4:E1C:E1D:E1E:EAA:EYX:EYY:F6H:GYH: 56 | Mac Pro (Late 2013):MacPro6,1: 57 | Mac Pro (Mid 2010):MacPro5,1:EUE:EUF:EUG:EUH:GWR:GY5:GZH:GZJ:GZK:GZL:GZM:H0X:H2N:H2P:H97:H99:HF7:HF8:HF9:HFA:HFC:HFD:HFF:HFG:HFJ:HFK:HFL:HFN:HG1:HG3:HP9:HPA: 58 | Mac Pro (Mid 2012):MacPro5,1:F4MC:F4MD:F4MG:F4MH:F4YY:F500:F648:F649:F64C:F64D:F64F:F6T9:F6TC:F6TD:F6TF:F6TG: 59 | Mac Pro (Rack, 2019):MacPro7,1:N5RH:N5RN:P7QQ:P7QR:P7QT:P7QV:PNTN:PNTP:PNTQ:PP3Y: 60 | Mac Pro Server (Mid 2010):MacPro5,1:HPV:HPW:HPY: 61 | Mac Pro Server (Mid 2012):MacPro5,1:F4MF:F4MJ:F501: 62 | Mac Pro:MacPro1,1:MacPro2,1: 63 | MacBook (13-inch):MacBook1,1: 64 | MacBook (13-inch, Aluminum, Late 2008):MacBook5,1: 65 | MacBook (13-inch, Early 2008):MacBook4,1:0P0:0P1:0P2:0P4:0P5:0P6:1LX:1PX:1Q2:1Q7:1QA:1QB:1QE:1ZY:27H:27J:28C:28D:28E:385:3N9:3NA:3ND:3NE:3NF:3X6:47Z:4R7:4R8: 66 | MacBook (13-inch, Early 2009):MacBook5,2:4R1:4R2:4R3:79D:79E:79F:7A2:85D:88J:8CP:8SJ:93K: 67 | MacBook (13-inch, Late 2006):MacBook2,1:WGK:WGL:WGM:WGN:WGP:WGQ:WGS:WGT:WGU:WVN:X6G:X6H:X6J:X6K:X6L:X7X:X97:X98:XAR:XAT:XC5:XDN:XDR:XDS:XDT:XDU:XDV:XDW:XDX:XDY:XDZ:XE0:XE1:XE2:XE3:XHB:XHC:XKT:XMF:Y6L:Y6M:Y9A:YCU: 68 | MacBook (13-inch, Late 2007):MacBook3,1: 69 | MacBook (13-inch, Late 2008):MacBook4,1:3VY:5AQ:5HS:5HU:67C:6ES:6HY:6LL:6LM:6M1:6V9:6YP:7XD: 70 | MacBook (13-inch, Late 2009):MacBook6,1: 71 | MacBook (13-inch, Mid 2007):MacBook2,1:YA2:YA3:YA4:YA5:YA6:YA7:YA8:YA9:YJJ:YJK:YJL:YJM:YJN:YQ7:YQ8:YRG:YRH:YRJ:YRK:YSH:YSJ:YSK:YSL:YSM:YTK:YTL:YV8:YX1:YX2:YX4:YX5:YXZ:YY1:YYW:Z5V:Z5W:Z5X:Z5Y:Z5Z:Z60:Z88:ZA8:ZA9:ZAP:ZAQ:ZAS:ZAU:ZAV:ZAW:ZAX:ZAY:ZAZ:ZB0:ZB1:ZB2:ZB7:ZB8:ZB9:ZBA:ZBB:ZBE:ZBF:ZBG:ZBH:ZBJ:ZBK:ZCN: 72 | MacBook (13-inch, Mid 2009):MacBook5,2:9GU:9GV:A1W:A1X:A1Y:A9P:A9Q:A9Y:ABW:ASC: 73 | MacBook (13-inch, Mid 2010):MacBook7,1: 74 | MacBook (Retina, 12-inch, 2017):MacBook10,1: 75 | MacBook (Retina, 12-inch, Early 2015):MacBook8,1: 76 | MacBook (Retina, 12-inch, Early 2016):MacBook9,1: 77 | MacBook Air (11-inch, Early 2014):MacBookAir6,1:FM72:G083:G084:G2CF:G2GH:G2GJ:G2PY:G2Q0:G4FY:G4H0:G4H4:G4HK:G4HM:G58J:G5RK:G5RL:G5RM:G6D3:GLK9:GP4N:GP4P: 78 | MacBook Air (11-inch, Early 2015):MacBookAir7,1: 79 | MacBook Air (11-inch, Late 2010):MacBookAir3,1: 80 | MacBook Air (11-inch, Mid 2011):MacBookAir4,1: 81 | MacBook Air (11-inch, Mid 2012):MacBookAir5,1: 82 | MacBook Air (11-inch, Mid 2013):MacBookAir6,1:F5N7:F5N8:F5YV:F5YW:FH51:FH52:FKYN:FKYP:FLCF:FMR5:FMR6:FMR9:FMRC:FMRD:FMRF:FMRG:FMRM:FMRN:FN5M:FN7F:FP2N:FP3C:FQLG:FT30: 83 | MacBook Air (13-inch, 2017):MacBookAir7,2:J1WK:J1WL:J1WM:J1WT:J1WV:J8N7:J8XG:J8XH:J9HX:J9TN:J9TP:J9TQ:JC9H:JCD6:JFLY:JKHD:JKHF:LQ07:LQF1:MFWJ: 84 | MacBook Air (13-inch, Early 2014):MacBookAir6,2:G085:G086:G2CC:G2CD:G2GK:G2GL:G2GM:G2GN:G356:G4H1:G4H2:G4H3:G4HN:G4HP:G58K:G5RN:G5RP:G5RQ:G6D4:G6D5:G829:G8J1:GLK7:GLK8:GP4L:GP4M: 85 | MacBook Air (13-inch, Early 2015):MacBookAir7,2:G940:G941:G942:G943:G944:GKJT:GKJV:GL20:GL21:GL22:GL23:GL24:GL25:GLCN:GLCP:GM14:GM15:GM38:GM6M:GM9G:GMC3:GMD3:GN8C:GNJJ:GNKM:H3QD:H3QF:H3QJ:H3QK:H569:H8VT:H8VV:H8VW:H8VX:HD7X:HD80:HD98:HDV4:HDV5:HDV6:HF4F:HF4H:HF9N:J6VL: 86 | MacBook Air (13-inch, Late 2010):MacBookAir3,2: 87 | MacBook Air (13-inch, Mid 2011):MacBookAir4,2: 88 | MacBook Air (13-inch, Mid 2012):MacBookAir5,2: 89 | MacBook Air (13-inch, Mid 2013):MacBookAir6,2:F5V7:F5V8:F6T5:F6T6:FH53:FKYQ:FKYR:FLCG:FM23:FM3Y:FM74:FMR7:FMR8:FMRH:FMRJ:FMRK:FMRL:FMRV:FMRW:FMRY:FN3Y:FN40:FN7G:FP2P:FQL9:FQLC:FQLD:FQLF:G6PM: 90 | MacBook Air (Late 2008):MacBookAir2,1:22D:22E:5L9:5LA:5TX:5U1:5U7:60R:62W:63V:63W:6JN: 91 | MacBook Air (Mid 2009):MacBookAir2,1:9A5:9A6:9A7:9A8: 92 | MacBook Air (Original):MacBookAir1,1: 93 | MacBook Air (Retina, 13-inch, 2018):MacBookAir8,1: 94 | MacBook Air (Retina, 13-inch, 2019):MacBookAir8,2: 95 | MacBook Air (Retina, 13-inch, 2020):MacBookAir9,1: 96 | MacBook Pro (13-inch, 2016, Four Thunderbolt 3 Ports):MacBookPro13,2: 97 | MacBook Pro (13-inch, 2016, Two Thunderbolt 3 ports):MacBookPro13,1: 98 | MacBook Pro (13-inch, 2017, Four Thunderbolt 3 Ports):MacBookPro14,2: 99 | MacBook Pro (13-inch, 2017, Two Thunderbolt 3 ports):MacBookPro14,1: 100 | MacBook Pro (13-inch, 2018, Four Thunderbolt 3 Ports):MacBookPro15,2:JHC8:JHC9:JHCC:JHCD:JHCF:JHD2:JHD3:JHD4:JHD5:KK98:KK99:KK9C:KQ1X:KQ1Y:KQ20:KQ21:KQ22:KQ23:KQ24:KQ25:KQ26:KQ27:L42X:L4FC:L4FD:L4FF:L4FG:L4FJ:L4JT:L7GD:LK8C: 101 | MacBook Pro (13-inch, 2019, Four Thunderbolt 3 ports):MacBookPro15,2:LVDC:LVDD:LVDF:LVDG:LVDH:LVDL:LVDM:LVDN:LVDP:MV9K:MV9R:N5T5:NCLV:NCLW:NCLX:NCLY:NCM0:NCM1:NCM2:NQM8:P4G1:P4G2: 102 | MacBook Pro (13-inch, 2019, Two Thunderbolt 3 ports):MacBookPro15,4: 103 | MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports):MacBookPro16,2: 104 | MacBook Pro (13-inch, 2020, Two Thunderbolt 3 ports):MacBookPro16,3: 105 | MacBook Pro (13-inch, Early 2011):MacBookPro8,1:DH2G:DH2H:DH2L:DH2M:DLN5:DLN6:DM75:DMLF:DMLH:DMLJ:DNCM:DNGD:DNKP:DNKQ:DNTK:DNVY:DR7W:DRJ7:DRJ9:DRJJ:DRJK:DRW1:DRW2:DRW7:DT4G:DT4H:DT60:DT61:DT62:DT63:DT64:DT65:DT66:DT67:ST61: 106 | MacBook Pro (13-inch, Late 2011):MacBookPro8,1:DV13:DV14:DV16:DV17:DVHJ:DVHK:DVHP:DVHQ:DW13:DY1J:DY1K:DY5T:DY5V:DY6C:DY77:DYL0:DYL1:DYL2:F298:F299: 107 | MacBook Pro (13-inch, Mid 2009):MacBookPro5,5: 108 | MacBook Pro (13-inch, Mid 2010):MacBookPro7,1: 109 | MacBook Pro (13-inch, Mid 2012):MacBookPro9,2: 110 | MacBook Pro (15-inch, 2.4/2.2GHz):MacBookPro3,1:02V:0LQ:0LZ:0M0:0PA:0S3:0S6:1CY:1CZ:2QU:2QV:X91:X92:XAG:XAH:Y9S:Y9T:YAL:YAM:YKX:YKY:YKZ:YL0:YQ3:YW5:YW9:YWA:YWD:YYV:YYX:YZ0:Z05:Z09:Z0G: 111 | MacBook Pro (15-inch, 2.53GHz, Mid 2009):MacBookPro5,4: 112 | MacBook Pro (15-inch, 2016):MacBookPro13,3: 113 | MacBook Pro (15-inch, 2017):MacBookPro14,3: 114 | MacBook Pro (15-inch, 2018):MacBookPro15,1:MacBookPro15,3:JG5H:JG5J:JG5K:JG5L:JG5M:JGH5:JGH6:JGH7:JGH8:KGYF:KGYG:KGYH:KQ9Q:KQ9R:KQ9T:KQ9V:KQ9W:KQ9X:KQ9Y:KQC0:KQC1:KQC2:KQC3:KQC4:KQC5:KQC6:KQC7:KQC8:KQC9:KQCC:KQCD:KQCF:KQCG:KQCH:KQCJ:KQCK:KQCL:KQCM:KQCN:KQCP:KQCQ:KQCR:KQCT:KQCV:KQCW:KQCX:KWJ2:L4HW:L4HX:L539:L53D:L7GC:LC8J:LC8K:LC8L:LCM6:MJLR:MJLT: 115 | MacBook Pro (15-inch, 2019):MacBookPro15,1:MacBookPro15,3:LVCF:LVCG:LVCH:LVCJ:LVCK:LVCL:LVDQ:LVDR:LVDT:LVDV:MV9T:MVC0:N5T6:N6KF:N6RJ:NCM3:NCM4:NCM5:NCM6:NQM9:NQMC:NQMD:NQMF: 116 | MacBook Pro (15-inch, Core 2 Duo):MacBookPro2,2: 117 | MacBook Pro (15-inch, Early 2008):MacBookPro4,1:1AJ:1EK:1EM:1JZ:1K0:1SH:1XR:1XW:27N:2AZ:2B0:2CE:2DT:2DX:2MF:2PK:33B:3LY:3LZ:48T:4R5:4R6:YJX:YJY:YJZ:YK0:ZLU: 118 | MacBook Pro (15-inch, Early 2011):MacBookPro8,2:DF8V:DF8X:DF8Y:DF91:DLN7:DLN8:DMC8:DMC9:DMDG:DMDH:DMDJ:DMGG:DMMF:DMMH:DMMJ:DMPG:DMPK:DMPL:DMPM:DMPN:DMPP:DMPQ:DMPR:DMQP:DNC3:DNCN:DNGF:DNH5:DNHY:DNKM:DNKY:DNM4:DNMW:DNRD:DNVK:DRJC:DRJD:DRJF:DRJL:DRJM:DRW3:DRW4:DRWD:DT4J:DT54:DT55:DT56:DT57:DT58:DT59:DT5C:DT5D:DT5F:DT5G:DT5H:DT5J:DT5L:DT68:DT69:DT6C:DT6D:DT6F:DT6G:DT6H:DT6J:DT6K:DT6L:DT6M:DT6R: 119 | MacBook Pro (15-inch, Glossy):MacBookPro1,1:VWW:VWX:VWY:VWZ:W3N:W92:W93:W94:W9F:W9Q:WAG:WAW:WB8:WBE:WBF:WBH:WBJ:WD7:WD8:WD9:WDA:WDB:WDC:WDD:WTS:WW0:WW1:WW2:WW3: 120 | MacBook Pro (15-inch, Late 2008):MacBookPro5,1: 121 | MacBook Pro (15-inch, Late 2011):MacBookPro8,2:DV7L:DV7M:DV7N:DV7P:DVHL:DVHM:DVHR:DW3G:DW3H:DW3J:DW47:DY1L:DY1M:DY1N:DY1P:DY1Q:DY1R:DY1T:DY1V:DY1W:DY1Y:DY20:DY21:DY5K:DY5P:DY5Q:DY5R:DY5Y:DY60:DY7G:DYG6:DYG7:DYK9:DYKC:DYR1:F0K6:F0V2: 122 | MacBook Pro (15-inch, Mid 2009):MacBookPro5,3: 123 | MacBook Pro (15-inch, Mid 2010):MacBookPro6,2: 124 | MacBook Pro (15-inch, Mid 2012):MacBookPro9,1: 125 | MacBook Pro (16-inch, 2019):MacBookPro16,1:MacBookPro16,4: 126 | MacBook Pro (17-inch):MacBookPro1,2: 127 | MacBook Pro (17-inch, 2.4GHz):MacBookPro3,1:027:028:02D:09R:09S:0LR:0ND:0NM:0PD:1CW:1CX:1MF:1MG:2QW:X94:XA9:YAA:YAN:YAP:YNQ:YNS:YNW:YQ4:YQ5:YR2:YRD:YRE:YRF:YWB:YWC:YZ1:YZ2:Z5M: 128 | MacBook Pro (17-inch, Core 2 Duo):MacBookPro2,1: 129 | MacBook Pro (17-inch, Early 2008):MacBookPro4,1:1BY:1ED:1EN:1ER:1K2:1K8:1K9:1KA:1Q3:1SG:2CF:2DY:2DZ:2ED:3DC:3DD:3DE:3DF:3M0:3M4:3M5:YP3:YP4:ZLV: 130 | MacBook Pro (17-inch, Early 2009):MacBookPro5,2:2QP:2QT:776:77A:7AP:7AS:7XQ:7XR:7XS:87K:87L:87M:87N:8FK:8FL:8FM:8FY:8FZ:8G0: 131 | MacBook Pro (17-inch, Early 2011):MacBookPro8,3:DF92:DF93:DLN9:DLNC:DMGH:DMQT:DMQW:DMR2:DMR4:DMR5:DMR7:DMR8:DMR9:DMRC:DNGG:DNKN:DRJG:DRJH:DRJN:DRW5:DRW6:DT5M:DT5N:DT5P:DT5Q:DT5R:DT5T:DT5V:DT5W:DT5Y:DT6N:DT6P: 132 | MacBook Pro (17-inch, Late 2008):MacBookPro4,1:3R8:3R9:4RT:4RW:57J:5U0:634:65A:663:664:666:668:6CT:6JK: 133 | MacBook Pro (17-inch, Late 2011):MacBookPro8,3:AY5W:DV10:DV11:DVHN:DVHV:DVHW:DW48:DY22:DY23:DY24:DY25:DY26:DY5W:DYG8:F13Y:F140: 134 | MacBook Pro (17-inch, Mid 2009):MacBookPro5,2:8YA:8YB:91T:A3M:A3N:A5R:A5W:AF3:AKV:AKW:AMV:AMW:AN1:ANC:AND:ANE:ANF:ANJ:AUU:E6L: 135 | MacBook Pro (17-inch, Mid 2010):MacBookPro6,1: 136 | MacBook Pro (Original):MacBookPro1,1:THV:VGW:VGX:VGY:VJ0:VJ1:VJ2:VJ3:VJ5:VJ6:VJ7:VJM:VMU:VSD:VTZ:VU0:VWA:VWB:VXW:VXX:W2Q: 137 | MacBook Pro (Retina, 13-inch, Early 2013):MacBookPro10,2:FFRP:FFRR:FG1F:FG28:FGM8:FGN5:FGN6:FGPJ:FHCH:FHN0: 138 | MacBook Pro (Retina, 13-inch, Early 2015):MacBookPro12,1: 139 | MacBook Pro (Retina, 13-inch, Late 2012):MacBookPro10,2:DR53:DR54:DR55:DR56:F775:F776:F7YF:F897:F8V6:F8V7:F8V8:F9JT:F9V1:F9VQ:FG7Q:FG7R:FL85:FMLJ: 140 | MacBook Pro (Retina, 13-inch, Late 2013):MacBookPro11,1:FGYY:FH00:FH01:FH02:FH03:FH04:FH05:FRF6:FRF7:FRQF:FT4Q:FT4R:FT4T:FT4V:FTC9:FTCD:FTCH:FTCK:FTCL:FTPH:FTPJ:FTPK:FTT4:FVVW:FVWQ:FWKF:G4N6:G4N7: 141 | MacBook Pro (Retina, 13-inch, Mid 2014):MacBookPro11,1:G3QH:G3QJ:G3QK:G3QL:G3QQ:G3QR:G3QT:G7RD:G7RF:G7YQ:G7YR:G8L0:G96R:G96T:G96V:G96W:G96Y:G970:G971:G972:G9FL:G9FM:G9FN:G9FP:G9FQ:G9FR:GDJM: 142 | MacBook Pro (Retina, 15-inch, Early 2013):MacBookPro10,1:FFT0:FFT1:FFT2:FFT3:FFT4:FG1H:FG1J:FGFH:FGFJ:FGFK:FGFL:FGN7:FGWF:FGWG:FGWH:FHCQ:FHCR:FJ47:FJVJ:FL94:FMLK:FR8D: 143 | MacBook Pro (Retina, 15-inch, Late 2013):MacBookPro11,2:MacBookPro11,3:FD56:FD57:FD58:FD59:FR1M:FRDM:FRG2:FRG3:FRQH:FRQJ:FRQK:FRQL:FT4P:FTK0:FTK1:FTPL:FTPM:FTPN:FTPP:FTPQ:FTPR:FTPT:FTPV:FTPW:FTPY:FTTJ:FVN4:FVYN:FWFY:FWHW:FWKK:FWKL:G4JQ:G5HL: 144 | MacBook Pro (Retina, 15-inch, Mid 2014):MacBookPro11,2:MacBookPro11,3:G3QC:G3QD:G3QG:G3QN:G3QP:G85Y:G86P:G86Q:G86R:G8F4:G8J7:G8L1:G96K:G96L:G96M:G96N:G96P:G96Q:G973:G974:G9FT:G9JN:G9L6:G9L7:G9L8:G9L9:GDPP:ZORD: 145 | MacBook Pro (Retina, 15-inch, Mid 2015):MacBookPro11,4:MacBookPro11,5: 146 | MacBook Pro (Retina, Mid 2012):MacBookPro10,1:DKQ1:DKQ2:DKQ4:DKQ5:F51R:F5Y2:F69W:F69Y:F6DN:F6F3:F6L9:F8JY:F96W:F9F1:F9F2:FCQ3: 147 | Xserve (Early 2008):Xserve2,1: 148 | Xserve (Early 2009):Xserve3,1: 149 | Xserve (Late 2006):Xserve1,1: 150 | -------------------------------------------------------------------------------- /Other Scripts/generate_csreq_hex_for_tcc_db.jxa: -------------------------------------------------------------------------------- 1 | #!/usr/bin/osascript -l JavaScript 2 | 3 | // 4 | // Created by Pico Mitchell on 8/24/22. 5 | // For MacLand @ Free Geek 6 | // 7 | // MIT License 8 | // 9 | // Copyright (c) 2022 Free Geek 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 12 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | // 21 | 22 | 23 | // Explanation of Code Signing Requirements: https://developer.apple.com/documentation/technotes/tn3127-inside-code-signing-requirements 24 | 25 | // ABOUT CSREQS *HEX* STRING DIFFERENCES AND THE PURPOSE OF THIS SCRIPT: 26 | 27 | // Hexadecimal string representations of Code Signing Requirements data are used when setting TCC permissions directly in the global and user TCC.db files like is done in "fg-install-os.sh" and "Free Geek Setup.applescript". 28 | // In my testing, it seems that including the CSReq hex strings in the TCC.db is not actually a necessary since macOS will fill them in after the app specified by the Bundle ID is launched for the first time. 29 | // But, that would mean that the first app to launch with the specified Bundle ID would have its CSReqs analyzed and the TCC permissions would then get locked to that apps CSReqs which could mean an alternate app spoofing that Bundle ID could theoretically be run first and "steal" the TCC permissions from the intended app. 30 | // Even though that is not at all a risk in our environment or use case here, I prefer to do the most correct and complete thing. So, I went about learning about how to generate the proper CSReq hex strings for the TCC.db files so that the permissions we manually set can only be applied to the exact intended app. 31 | 32 | // When first investigating the TCC.db structure, the https://www.rainforestqa.com/blog/macos-tcc-db-deep-dive post pointed me to https://stackoverflow.com/questions/52706542/how-to-get-csreq-of-macos-application-on-command-line/57259004#57259004 33 | // which showed how to create the CSReq hex string using the "codesign" and "csreq" (and "xxd") commands, and I streamline the commands from that post into the following one-liner: 34 | // codesign -dr - '/path/to/some.app' 2> /dev/null | awk -F ' => ' '($1 == "designated") { print $2; exit }' | csreq -r - -b /dev/stdout | xxd -p | tr -d '[:space:]' 35 | // "xxd -c 0 -p" can be used instead of "xxd -p | tr -d '[:space:]'" on at least macOS 11.6.8 Big Sur or newer, but "xxd" on macOS 11.6 Big Sur and older do not support "-c 0" to output a single line (not sure when exactly "xxd" was updated between 11.6 and 11.6.8 since I don't have easy access to all those versions and it's odd that it was even updated within those security updates, but it was). 36 | 37 | // Upon initial testing the CSReq hex string generated this way appeared to work fine, but with more testing I noticed issues on macOS 10.14 Mojave and 10.15 Catalina where the the apps were requesting 38 | // AppleEvents TCC access when they ran even though the permissions showed up properly in the Automation list (where AppleEvents TCC permissions are listed) of the Privacy & Security pane of the System Preferences. 39 | // Although, all the AppleEvents TCC worked fine on macOS 11 Big Sur and newer with the same exact CSReqs hex string being used across all versions of macOS. 40 | // What's odd is that the same CSReqs hex strings worked fine on all versions of macOS for other TCC permissions such as Microphone, Accessibility, and Full Disk Access, all of which DO NOT specify a target/indirect object like the AppleEvents TCC permissions do. 41 | // So, I'm not exactly sure why, but the issue with the CSReq hex strings I was using appeared to only be an issue for TCC permissions which specified a target/indirect object and only on macOS 10.14 Mojave and macOS 10.15 Catalina. 42 | 43 | // With more investigation, I noticed that on all versions of macOS, the CSReq hex strings being set by macOS in the TCC.db files were slighly different than the CSReq hex strings that I generated with the command above. 44 | // To extract the CSReq hex strings from the global TCC.db for a specific Bundle ID (if that app has been granted TCC permissions), you can use the following command when running in Terminal with "Full Disk Access": 45 | // sqlite3 '/Library/Application Support/com.apple.TCC/TCC.db' 'SELECT lower(hex(csreq)) FROM access WHERE (client = "some.bundle.id")' 46 | // And you can also check the user TCC.db using the "${HOME}/Library/Application Support/com.apple.TCC/TCC.db" path. 47 | 48 | // When using the CSReq hex strings that were set by macOS in the TCC.db files, the AppleEvents TCC permissions worked properly on macOS 10.14 Mojave and macOS 10.15 Catalina, as well as macOS 11 Big Sur and newer. 49 | // So, the issue appeared to be because of the CSReq hex strings that were generated with the original command I used. 50 | // But what was wrong with those CSReq hex strings and how and why were they different from the CSReq hex strings generated by macOS? 51 | 52 | // Next, I tested to see if these two different forms of CSReq hex strings were equal to different CSReq string values with the following command to reverse the hex data back into the CSReq string form using the "csreq" command: 53 | // echo '[CSREQ-HEX-STRING]' | xxd -r -p | csreq -r - -t /dev/stdin 54 | // When reversing these two different forms of CSReq hex strings into their CSReq string values, they both equal the exact same CSReq strings. 55 | // So even though they are "equal", there must be something different with how these CSReq hex strings are generated. 56 | 57 | // While I could have just stopped there and used the CSReq hex strings extracted from the TCC.db instead of generated from the "csreq" command, I wanted to understand what was going on here and why these CSReq hex strings were different. 58 | // When trying to understand more about how these CSReq hex strings were generated, I found the source code for the "csreq" command line tool (https://opensource.apple.com/source/security_systemkeychain/security_systemkeychain-55202/src/csreq.cpp.auto.html) 59 | // and started experimenting with the native "Code Signing Services" functions of the "Security.framework" (https://developer.apple.com/documentation/security/code_signing_services?language=objc) using the ObjC-bridge in JavaScript for Automation (JXA). 60 | 61 | // That research and investigation led to me writing the following JavaScript for Automation (JXA) code which generates the CSReq hex string in different ways. 62 | // It was through this research that I figured out how to generate the CSReq hex string that is identical to how macOS generates them for the TCC.db as well as why that CSReq hex string is slightly different than the form generated by the original commands I found. 63 | // I don't know why the two different forms don't both work the same on macOS 10.14 Mojave and macOS 10.15 Catalina, but that seems to be an unintentional bug since the behavior was fixed in macOS 11 Big Sur which allowed both forms of the CSReq hex strings to work the same. 64 | 65 | // The difference turned out to be because of the CSReq data (which was then converted to hex) being created from the CSReq *string* value (using the "SecRequirementCreateWithString" function) 66 | // vs being created directly from the app on disk (using "SecStaticCodeCreateWithPath" and "SecCodeCopyDesignatedRequirement" functions). 67 | // Clearly, macOS was getting the CSReq data directly from the app on disk rather than first extracting the CSReq string from the app and then converting that CSReq string to CSReq data. 68 | // Even though the string values of the CSReqs are the same in both cases, something in the data representations are slightly different based on how that data is generated. 69 | 70 | // The following code outputs BOTH CSReq hex strings generated from the CSReq string as well as the CSReqs generated directly from the app on disk to demonstrate the difference between the two. 71 | // Interestingly, using this code shows that when getting CSReq hex strings for Apple-signed apps, the CSReq hex strings seem to always be the same regardless of whether the CSReq string or the app on disk is used to generate the CSReq data. 72 | // But, when getting the CSReq hex string for 3rd-party apps, the CSReq hex strings will be different for *some* apps depending on if the CSReq data was generated from the CSReq hex string vs the app on disk, and I'm not sure what about the specific CSReqs make the CSReq hex strings different for some 3rd-party apps and not others. 73 | 74 | // Along with generating and displaying both forms of CSReq hex strings for research purposes, it will also show the CSReq *string* value and note when the hex strings are same or different. 75 | // But, the CSReq hex generated from the app on disk is the on that should always be used when placing CSReq hex strings directly into the TCC.db for the most compatibility. 76 | // For convenience, the CSReq hex string generated from the app on disk will be copied to the clipboard when the script is run. 77 | 78 | 79 | 'use strict' 80 | // @ts-ignore: JXA-ObjC 81 | ObjC.import('Security') 82 | // @ts-ignore: JXA-ObjC 83 | ObjC.import('AppKit') // Only needed for "NSPasteboard". 84 | 85 | function run(argv) { 86 | const pathToApp = argv[0] 87 | if (!pathToApp) return 'ERROR: APP PATH ARGUMENT REQUIRED' 88 | 89 | let csreqsOutput = `APP PATH: ${pathToApp}` 90 | 91 | // "csreq" source reference: https://opensource.apple.com/source/security_systemkeychain/security_systemkeychain-55202/src/csreq.cpp.auto.html 92 | 93 | // @ts-ignore: JXA-ObjC 94 | const staticCodeRef = $() 95 | // @ts-ignore: JXA-ObjC 96 | $.SecStaticCodeCreateWithPath($.NSURL.fileURLWithPath(pathToApp), $.kSecCSDefaultFlags, staticCodeRef) 97 | 98 | // @ts-ignore: JXA-ObjC 99 | const csreqRef = $() 100 | // @ts-ignore: JXA-ObjC 101 | $.SecCodeCopyDesignatedRequirement(staticCodeRef, $.kSecCSDefaultFlags, csreqRef) 102 | 103 | // @ts-ignore: JXA-ObjC 104 | const csreqDataRef = $() 105 | // @ts-ignore: JXA-ObjC 106 | $.SecRequirementCopyData(csreqRef, $.kSecCSDefaultFlags, csreqDataRef) 107 | 108 | csreqsOutput += '\n\nCSREQ HEX FROM APP ON DISK (this is what is written to TCC.db by macOS and what *SHOULD BE USED* for AppleEvents TCC to work on Mojave & Catalina):\n' 109 | let csreqHexFromAppOnDisk = '' 110 | for (let thisByteIndex = 0, csreqBytesArray = csreqDataRef.bytes, csreqBytesLength = csreqDataRef.length; thisByteIndex < csreqBytesLength; thisByteIndex ++) 111 | csreqHexFromAppOnDisk += csreqBytesArray[thisByteIndex].toString(16).padStart(2, 0) 112 | csreqsOutput += csreqHexFromAppOnDisk 113 | 114 | // @ts-ignore: JXA-ObjC 115 | const pasteboard = $.NSPasteboard.generalPasteboard 116 | pasteboard.clearContents 117 | const didCopy = (pasteboard.writeObjects([csreqHexFromAppOnDisk])) // Copy this best from of the CSReq from the app on disk to the Clipboard for convenience. 118 | csreqsOutput += `\n>>> ${didCopy ? 'COPIED' : 'WARNING: *FAILED* TO COPY'} THIS CSREQ HEX STRING FROM THE APP ON DISK TO CLIPBOARD` 119 | 120 | // @ts-ignore: JXA-ObjC 121 | const csreqStringRef = $() 122 | // @ts-ignore: JXA-ObjC 123 | $.SecRequirementCopyString(csreqRef, $.kSecCSDefaultFlags, csreqStringRef) // This just gets the code requirement *string* from the specified app. 124 | // @ts-ignore: JXA-ObjC 125 | $.SecRequirementCreateWithString(csreqStringRef, $.kSecCSDefaultFlags, csreqRef) // Re-create the csreqRef based on the code requirements *string* instead of directly from the app on disk. 126 | 127 | csreqsOutput += `\n\nCODE REQUIREMENTS STRING:\n${csreqStringRef.js}` 128 | 129 | // THE FOLLOWING CSREQ HEX STRING *MAY* BE SLIGHTLY DIFFERENT BECAUSE SLIGHTLY DIFFERENT DATA IS PRODUCED WHEN 130 | // CODE REQUIREMENTS DATA IS PRODUCED FROM A CODE REQUIREMENTS *STRING* (using SecRequirementCreateWithString) 131 | // VS DIRECTLY FROM AN APP ON DISK (using SecStaticCodeCreateWithPath & SecCodeCopyDesignatedRequirement) 132 | // See comment above for more detailed information about these differences. 133 | 134 | // @ts-ignore: JXA-ObjC 135 | $.SecRequirementCopyData(csreqRef, $.kSecCSDefaultFlags, csreqDataRef) // This and the next steps are the same as above, but now the csreqRef is coming from the code requirements *string* instead of directly from the app on disk. 136 | 137 | csreqsOutput += '\n\nCSREQ HEX FROM CODE REQUIREMENTS STRING (this is what the "csreq" binary piped to "xxd" would output since the string is always the source which *MAY NOT WORK* on Mojave & Catalina for AppleEvents TCC which have a target app/indirect object):\n' 138 | let csreqHexFromReqString = '' 139 | for (let thisByteIndex = 0, csreqBytesArray = csreqDataRef.bytes, csreqBytesLength = csreqDataRef.length; thisByteIndex < csreqBytesLength; thisByteIndex ++) 140 | csreqHexFromReqString += csreqBytesArray[thisByteIndex].toString(16).padStart(2, 0) 141 | csreqsOutput += csreqHexFromReqString 142 | 143 | return `${csreqsOutput} 144 | 145 | THE CSREQ *HEX* FROM THE APP ON DISK VS FROM THE CODE REQUIREMENTS STRING ${((csreqHexFromAppOnDisk == csreqHexFromReqString) ? 'MATCH!' : '*DO NOT* MATCH (even though they equal the same string value)')} 146 | See comments within the code of this script for more information about these two CSReq hex strings. 147 | ` 148 | } 149 | -------------------------------------------------------------------------------- /Other Scripts/get_bluetooth_from_all_mac_specs_pages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck enable=add-default-case,avoid-nullary-conditions,check-unassigned-uppercase,deprecate-which,quote-safe-variables,require-double-brackets 3 | 4 | # 5 | # Created by Pico Mitchell (of Free Geek) on 8/9/22. 6 | # 7 | # MIT License 8 | # 9 | # Copyright (c) 2022 Free Geek 10 | # 11 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 12 | # to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 | # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | # 21 | 22 | PATH='/usr/bin:/bin:/usr/sbin:/sbin' 23 | 24 | declare -a all_mac_identification_pages=() # All Mac identification pages are listed on https://support.apple.com/HT213325 as well as in the "On the product or its packaging" section of https://support.apple.com/HT201581 25 | all_mac_identification_pages+=( 'HT201300' ) # MacBook Pro 26 | all_mac_identification_pages+=( 'HT201862' ) # MacBook Air 27 | all_mac_identification_pages+=( 'HT201608' ) # MacBook 28 | all_mac_identification_pages+=( 'HT201634' ) # iMac 29 | all_mac_identification_pages+=( 'HT201894' ) # Mac mini 30 | all_mac_identification_pages+=( 'HT213073' ) # Mac Studio 31 | all_mac_identification_pages+=( 'HT202888' ) # Mac Pro 32 | 33 | every_bluetooth_version='' 34 | 35 | every_bluetooth_5dot3_model='' 36 | every_bluetooth_5_model='' 37 | every_bluetooth_4dot2_model='' 38 | every_bluetooth_4_model='Macmini5,3+' # Mid 2011 Mac mini Server (Macmini5,3) is not listed in the Specs pages. 39 | every_bluetooth_2dot1plusEDR_model='' 40 | every_bluetooth_other_version_model='' 41 | every_error_model='' 42 | 43 | for this_mac_idenification_page in "${all_mac_identification_pages[@]}"; do 44 | this_mac_idenification_page_source="$(curl -m 5 -sfL "https://support.apple.com/${this_mac_idenification_page}")" 45 | 46 | this_model_identifier='' 47 | while IFS='' read -r this_model_id_or_specs_url; do 48 | if [[ "${this_model_id_or_specs_url}" == 'https://'* ]]; then 49 | echo " (${this_model_id_or_specs_url}):" 50 | specs_page_source="$(curl -m 5 -sfL "${this_model_id_or_specs_url}")" 51 | bluetooth_element_from_page="$(echo "${specs_page_source}" | xmllint --html --xpath '//li[contains(text(),"Bluetooth")]/text()' - 2> /dev/null)" 52 | 53 | if [[ -z "${bluetooth_element_from_page}" ]]; then 54 | bluetooth_element_from_page="$(echo "${specs_page_source}" | xmllint --html --xpath '//p[contains(text(),"Bluetooth")]/text()' - 2> /dev/null)" 55 | fi 56 | 57 | if [[ -z "${bluetooth_element_from_page}" ]]; then 58 | echo 'ERROR DETECTING BLUETOOTH FOR MODEL' 59 | every_error_model+="${this_model_identifier}+" 60 | else 61 | bluetooth_version="$(echo "${bluetooth_element_from_page}" | tr -dc '[:digit:].')" 62 | 63 | if [[ "${bluetooth_version}" == '.'* ]]; then 64 | bluetooth_version="${bluetooth_version#.}" 65 | fi 66 | 67 | if [[ "${bluetooth_element_from_page}" == *'EDR'* ]]; then 68 | bluetooth_version+=' + EDR' 69 | fi 70 | 71 | case "${bluetooth_version}" in 72 | '5.3') 73 | every_bluetooth_5dot3_model+="${this_model_identifier}+" 74 | ;; 75 | '5.0') 76 | every_bluetooth_5_model+="${this_model_identifier}+" 77 | ;; 78 | '4.2') 79 | every_bluetooth_4dot2_model+="${this_model_identifier}+" 80 | ;; 81 | '4.0') 82 | every_bluetooth_4_model+="${this_model_identifier}+" 83 | ;; 84 | '2.1 + EDR') 85 | every_bluetooth_2dot1plusEDR_model+="${this_model_identifier}+" 86 | ;; 87 | *) 88 | every_bluetooth_other_version_model+="${this_model_identifier}+" 89 | ;; 90 | esac 91 | 92 | every_bluetooth_version+=$'\n'"${bluetooth_version}" 93 | 94 | echo "Bluetooth ${bluetooth_version}" 95 | fi 96 | else 97 | this_model_identifier="${this_model_id_or_specs_url}" 98 | echo -en "\n${this_model_identifier}" 99 | fi 100 | done < <(echo "${this_mac_idenification_page_source}" | awk -F ':|"' '/Model Identifier:/ { gsub(" ", " ", $NF); gsub(", ", "+", $NF); gsub("; ", "+", $NF); gsub(" ", "", $NF); gsub("
", "", $NF); print ""; print $NF } /Tech Specs:/ { print "https:" $4 }') 101 | # echo "${this_mac_idenification_page_source}" | xmllint --html --xpath '//a[contains(@href,"/kb/SP")]/@href' - 2> /dev/null | tr '"' '\n' | grep '/kb/SP' | sort -ur 102 | done 103 | 104 | echo -e '\n\nEvery Bluetooth Verison' 105 | echo "${every_bluetooth_version}" | sort -urV 106 | 107 | echo 'Bluetooth 5.3' 108 | every_bluetooth_5dot3_model="$(echo "${every_bluetooth_5dot3_model%+}" | tr '+' '\n' | sort -uV)" 109 | echo "\"${every_bluetooth_5dot3_model//$'\n'/", "}\"" 110 | 111 | echo -e '\nBluetooth 5.0' 112 | every_bluetooth_5_model="$(echo "${every_bluetooth_5_model%+}" | tr '+' '\n' | sort -uV)" 113 | echo "\"${every_bluetooth_5_model//$'\n'/", "}\"" 114 | 115 | echo -e '\nBluetooth 4.2' 116 | every_bluetooth_4dot2_model="$(echo "${every_bluetooth_4dot2_model%+}" | tr '+' '\n' | sort -uV)" 117 | echo "\"${every_bluetooth_4dot2_model//$'\n'/", "}\"" 118 | 119 | echo -e '\nBluetooth 4.0' 120 | every_bluetooth_4_model="$(echo "${every_bluetooth_4_model%+}" | tr '+' '\n' | sort -uV)" 121 | echo "\"${every_bluetooth_4_model//$'\n'/", "}\"" 122 | 123 | echo -e '\nBluetooth 2.1 + EDR' 124 | every_bluetooth_2dot1plusEDR_model="$(echo "${every_bluetooth_2dot1plusEDR_model%+}" | tr '+' '\n' | sort -uV)" 125 | echo "\"${every_bluetooth_2dot1plusEDR_model//$'\n'/", "}\"" 126 | 127 | echo -e '\nBluetooth OTHER VERSION' 128 | every_bluetooth_other_version_model="$(echo "${every_bluetooth_other_version_model%+}" | tr '+' '\n' | sort -uV)" 129 | echo "\"${every_bluetooth_other_version_model//$'\n'/", "}\"" 130 | 131 | echo -e '\nERROR DETECTING BLUETOOTH' 132 | every_error_model="$(echo "${every_error_model%+}" | tr '+' '\n' | sort -uV)" 133 | echo "\"${every_error_model//$'\n'/", "}\"" 134 | 135 | echo '' 136 | 137 | # Example output from 2/1/23: 138 | 139 | # Every Bluetooth Verison 140 | # 5.3 141 | # 5.0 142 | # 4.2 143 | # 4.0 144 | # 2.1 + EDR 145 | 146 | # Bluetooth 5.3 147 | # "Mac14,3", "Mac14,5", "Mac14,6", "Mac14,9", "Mac14,10", "Mac14,12" 148 | 149 | # Bluetooth 5.0 150 | # "Mac13,1", "Mac13,2", "Mac14,2", "Mac14,7", "MacBookAir9,1", "MacBookAir10,1", "MacBookPro15,1", "MacBookPro15,2", "MacBookPro15,3", "MacBookPro15,4", "MacBookPro16,1", "MacBookPro16,2", "MacBookPro16,3", "MacBookPro16,4", "MacBookPro17,1", "MacBookPro18,1", "MacBookPro18,2", "MacBookPro18,3", "MacBookPro18,4", "MacPro7,1", "Macmini8,1", "Macmini9,1", "iMac20,1", "iMac20,2", "iMac21,1", "iMac21,2", "iMacPro1,1" 151 | 152 | # Bluetooth 4.2 153 | # "MacBook10,1", "MacBookAir8,1", "MacBookAir8,2", "MacBookPro11,4", "MacBookPro11,5", "MacBookPro13,1", "MacBookPro13,2", "MacBookPro13,3", "MacBookPro14,1", "MacBookPro14,2", "MacBookPro14,3", "iMac18,1", "iMac18,2", "iMac18,3", "iMac19,1", "iMac19,2" 154 | 155 | # Bluetooth 4.0 156 | # "MacBook8,1", "MacBook9,1", "MacBookAir4,1", "MacBookAir4,2", "MacBookAir5,1", "MacBookAir5,2", "MacBookAir6,1", "MacBookAir6,2", "MacBookAir7,1", "MacBookAir7,2", "MacBookPro9,1", "MacBookPro9,2", "MacBookPro10,1", "MacBookPro10,2", "MacBookPro11,1", "MacBookPro11,2", "MacBookPro11,3", "MacBookPro12,1", "MacPro6,1", "Macmini5,1", "Macmini5,2", "Macmini5,3", "Macmini6,1", "Macmini6,2", "Macmini7,1", "iMac13,1", "iMac13,2", "iMac14,1", "iMac14,2", "iMac14,4", "iMac15,1", "iMac16,1", "iMac16,2", "iMac17,1" 157 | 158 | # Bluetooth 2.1 + EDR 159 | # "MacBook5,2", "MacBook6,1", "MacBook7,1", "MacBookAir2,1", "MacBookAir3,1", "MacBookAir3,2", "MacBookPro4,1", "MacBookPro5,1", "MacBookPro5,2", "MacBookPro5,3", "MacBookPro5,5", "MacBookPro6,1", "MacBookPro6,2", "MacBookPro7,1", "MacBookPro8,1", "MacBookPro8,2", "MacBookPro8,3", "MacPro4,1", "MacPro5,1", "Macmini3,1", "Macmini4,1", "iMac9,1", "iMac10,1", "iMac11,2", "iMac11,3", "iMac12,1", "iMac12,2" 160 | 161 | # Bluetooth OTHER VERSION 162 | # "" 163 | 164 | # ERROR DETECTING BLUETOOTH 165 | # "" 166 | -------------------------------------------------------------------------------- /Other Scripts/get_power_adapters_from_all_mac_specs_pages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck enable=add-default-case,avoid-nullary-conditions,check-unassigned-uppercase,deprecate-which,quote-safe-variables,require-double-brackets 3 | 4 | # 5 | # Created by Pico Mitchell (of Free Geek) on 8/9/22. 6 | # 7 | # MIT License 8 | # 9 | # Copyright (c) 2022 Free Geek 10 | # 11 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 12 | # to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 | # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | # 21 | 22 | PATH='/usr/bin:/bin:/usr/sbin:/sbin' 23 | 24 | # The https://support.apple.com/en-us/HT201700 page is also useful to identify power adapters, but it's not as specific and doesn't include Model IDs, so there's not much value in scraping it. 25 | 26 | declare -a all_mac_identification_pages=() # All Mac identification pages are listed on https://support.apple.com/HT213325 as well as in the "On the product or its packaging" section of https://support.apple.com/HT201581 27 | all_mac_identification_pages+=( 'HT201300' ) # MacBook Pro 28 | all_mac_identification_pages+=( 'HT201862' ) # MacBook Air 29 | all_mac_identification_pages+=( 'HT201608' ) # MacBook 30 | # all_mac_identification_pages+=( 'HT201634' ) # iMac 31 | # all_mac_identification_pages+=( 'HT201894' ) # Mac mini 32 | # all_mac_identification_pages+=( 'HT213073' ) # Mac Studio 33 | # all_mac_identification_pages+=( 'HT202888' ) # Mac Pro 34 | 35 | # NOTE: MagSafe 1 lists have older Model IDs that aren't included on the specs pages pre-included in them. 36 | every_85w_magsafe1_model='MacBookPro1,1+MacBookPro1,2+MacBookPro2,1+MacBookPro2,2+MacBookPro3,1+' 37 | every_60w_magsafe1_model='MacBook1,1+MacBook2,1+MacBook3,1+MacBook4,1+MacBook5,1+' 38 | every_45w_magsafe1_model='MacBookAir1,1+' 39 | every_85w_magsafe2_model='' 40 | every_60w_magsafe2_model='' 41 | every_45w_magsafe2_model='' 42 | every_96w_usbc_model='' 43 | every_87w_usbc_model='' 44 | every_67w_usbc_model='' 45 | every_61w_usbc_model='' 46 | every_30w_usbc_model='' 47 | every_29w_usbc_model='' 48 | 49 | # NOTE: The newer Apple Silicon Macs with MagSafe 3 tend to support multiple different wattages with and without fast 50 | # charge capabilities so their descriptions are not as clear and clean to analyze in code as the past power adapter types 51 | # since the MagSafe 3 capability is listed on a separate line as just the cable and not the power adapter itself. 52 | # So, instead of trying, just manually check the outputs and come up with more concise descriptions and pre-add them to their respective lists instead. 53 | every_140w_magsafe3_model='MacBookPro18,1+MacBookPro18,2+Mac14,6+Mac14,10+' 54 | every_67w_or_96w_magsafe3_model='MacBookPro18,3+MacBookPro18,4+Mac14,5+Mac14,9+' 55 | every_30w_or_35W_dp_or_67w_magsafe3_model='Mac14,2+' 56 | 57 | every_unknown_model='' 58 | 59 | for this_mac_idenification_page in "${all_mac_identification_pages[@]}"; do 60 | this_mac_idenification_page_source="$(curl -m 5 -sfL "https://support.apple.com/${this_mac_idenification_page}")" 61 | 62 | this_model_identifier='' 63 | while IFS='' read -r this_model_id_or_specs_url; do 64 | if [[ "${this_model_id_or_specs_url}" == 'https://'* ]]; then 65 | echo " (${this_model_id_or_specs_url}):" 66 | power_adapter_elements_from_page="$(curl -m 5 -sfL "${this_model_id_or_specs_url}" | xmllint --html --xpath '//*[contains(text(),"Power Adapter") or contains(text(),"MagSafe")]' - 2> /dev/null)" 67 | power_adapter_elements_from_page="${power_adapter_elements_from_page//>$'\n'<}" 68 | power_adapter_elements_from_page="${power_adapter_elements_from_page//; /$'\n'}" 69 | 70 | # Suppress ShellCheck warning to use bash string replacement since it cannot do this regex style replacement. 71 | # shellcheck disable=SC2001 72 | power_adapter_elements_from_page="$(echo "${power_adapter_elements_from_page}" | sed 's/<[^>]*>//g')" 73 | 74 | if [[ "${power_adapter_elements_from_page}" == *'with cable management'* ]]; then 75 | power_adapter_elements_from_page="$(echo "${power_adapter_elements_from_page}" | grep 'with cable management')" # Remove extraneous lines for MagSafe 2 and MagSafe 1 that aren't for the actual power adapter. 76 | power_adapter_elements_from_page="${power_adapter_elements_from_page% with cable management*}" 77 | else 78 | power_adapter_elements_from_page="$(echo "${power_adapter_elements_from_page}" | grep -v 'USB-C power port\|Power Adapter Extension Cable')" # Remove extraneous lines for some USB-C adapters. 79 | power_adapter_elements_from_page="$(echo "${power_adapter_elements_from_page}" | grep -v 'MagSafe 3 port\|MagSafe 3 charging port\|USB-C to MagSafe 3 Cable (2 m)')" # Remove extraneous lines for some MagSafe 3 adapters. 80 | fi 81 | 82 | power_adapter_elements_from_page="$(echo "${power_adapter_elements_from_page//‑/-}" | sort -u)" 83 | echo -e "\t${power_adapter_elements_from_page//$'\n'/$'\n\t'}" 84 | 85 | power_adapter_elements_from_page_lowercase="$(echo "${power_adapter_elements_from_page}" | tr '[:upper:]' '[:lower:]')" 86 | case "${power_adapter_elements_from_page_lowercase}" in 87 | '85w magsafe power adapter') 88 | every_85w_magsafe1_model+="${this_model_identifier}+" 89 | ;; 90 | '60w magsafe power adapter') 91 | every_60w_magsafe1_model+="${this_model_identifier}+" 92 | ;; 93 | '60w or 85w magsafe power adapter') 94 | if [[ "${this_model_identifier}" == 'MacBookPro5,3' || "${this_model_identifier}" == 'MacBookPro5,4' ]]; then 95 | # The specs pages (https://support.apple.com/kb/SP544?locale=en_US) combine MacBookPro5,3 and MacBookPro5,4 (which isn't even listed, but check for it just in case), 96 | # and list them both as MacBookPro5,3 even though "MacBook Pro (15-inch, 2.53 GHz, Mid 2009)" is actually MacBookPro5,4 (and not MacBookPro5,3) 97 | # and state "60W or 85W MagSafe Power Adapter" in the "Battery and power" section, but the table in the "15-inch Configurations" section properly shows 98 | # that the "2.53GHz MacBook Pro (MC118LL/A)" model (which is actually MacBookPro5,4), takes the 60W MagSafe Power Adapter while the other configurations 99 | # (which are the MacBookPro5,3 models) take the 85W MagSafe Power Adapter rather than both of these models being able to take either/or power adapter wattage. 100 | # 60W = MacBookPro5,4 / MC118LL/A: https://everymac.com/systems/apple/macbook_pro/specs/macbook-pro-core-2-duo-2.53-aluminum-15-mid-2009-sd-unibody-specs.html 101 | # 85W = MacBookPro5,3 / MB985LL/A: https://everymac.com/systems/apple/macbook_pro/specs/macbook-pro-core-2-duo-2.66-aluminum-15-mid-2009-sd-unibody-specs.html 102 | # 85W = MacBookPro5,3 / MB986LL/A: https://everymac.com/systems/apple/macbook_pro/specs/macbook-pro-core-2-duo-2.8-aluminum-15-mid-2009-sd-unibody-specs.html 103 | 104 | echo -e '\tMANUAL CORRECTION: MacBookPro5,4 = 60W MagSafe 1\n\tMANUAL CORRECTION: MacBookPro5,3 = 85W MagSafe 1' 105 | every_85w_magsafe1_model+="MacBookPro5,3+" 106 | every_60w_magsafe1_model+="MacBookPro5,4+" 107 | else 108 | echo -e '\tERROR: UNKNOWN Power Adater' 109 | every_unknown_model+="${this_model_identifier}+" 110 | fi 111 | ;; 112 | '45w magsafe power adapter') 113 | every_45w_magsafe1_model+="${this_model_identifier}+" 114 | ;; 115 | '85w magsafe 2 power adapter') 116 | every_85w_magsafe2_model+="${this_model_identifier}+" 117 | ;; 118 | '60w magsafe 2 power adapter') 119 | every_60w_magsafe2_model+="${this_model_identifier}+" 120 | ;; 121 | '45w magsafe 2 power adapter') 122 | every_45w_magsafe2_model+="${this_model_identifier}+" 123 | ;; 124 | '96w usb-c power adapter') 125 | every_96w_usbc_model+="${this_model_identifier}+" 126 | ;; 127 | '87w usb-c power adapter') 128 | every_87w_usbc_model+="${this_model_identifier}+" 129 | ;; 130 | '67w usb-c power adapter') 131 | every_67w_usbc_model+="${this_model_identifier}+" 132 | ;; 133 | '61w usb-c power adapter') 134 | every_61w_usbc_model+="${this_model_identifier}+" 135 | ;; 136 | '30w usb-c power adapter') 137 | every_30w_usbc_model+="${this_model_identifier}+" 138 | ;; 139 | '29w usb-c power adapter') 140 | every_29w_usbc_model+="${this_model_identifier}+" 141 | ;; 142 | *) 143 | if [[ "+${every_140w_magsafe3_model}" == *"+${this_model_identifier}+"* ]]; then 144 | echo -e '\tMANUAL CONCISE DESCRIPTION: 140W USB-C/MagSafe 3' 145 | elif [[ "+${every_67w_or_96w_magsafe3_model}" == *"+${this_model_identifier}+"* ]]; then 146 | echo -e '\tMANUAL CONCISE DESCRIPTION: 67W or 96W USB-C/MagSafe 3' 147 | elif [[ "+${every_30w_or_35W_dp_or_67w_magsafe3_model}" == *"+${this_model_identifier}+"* ]]; then 148 | echo -e '\tMANUAL CONCISE DESCRIPTION: 30W or 35W Dual Port or 67W USB-C/MagSafe 3' 149 | else 150 | echo -e '\tERROR: UNKNOWN Power Adater' 151 | every_unknown_model+="${this_model_identifier}+" 152 | fi 153 | ;; 154 | esac 155 | else 156 | this_model_identifier="${this_model_id_or_specs_url}" 157 | echo -en "\n${this_model_identifier}" 158 | fi 159 | done < <(echo "${this_mac_idenification_page_source}" | awk -F ':|"' '/Model Identifier:/ { gsub(" ", " ", $NF); gsub(", ", "+", $NF); gsub("; ", "+", $NF); gsub(" ", "", $NF); gsub("
", "", $NF); print ""; print $NF } /Tech Specs:/ { print "https:" $4 }') 160 | # echo "${this_mac_idenification_page_source}" | xmllint --html --xpath '//a[contains(@href,"/kb/SP")]/@href' - 2> /dev/null | tr '"' '\n' | grep '/kb/SP' | sort -ur 161 | done 162 | 163 | echo -e '\n\n85W MagSafe 1' 164 | every_85w_magsafe1_model="$(echo "${every_85w_magsafe1_model%+}" | tr '+' '\n' | sort -uV)" 165 | echo "\"${every_85w_magsafe1_model//$'\n'/", "}\"" 166 | 167 | echo -e '\n60W MagSafe 1' 168 | every_60w_magsafe1_model="$(echo "${every_60w_magsafe1_model%+}" | tr '+' '\n' | sort -uV)" 169 | echo "\"${every_60w_magsafe1_model//$'\n'/", "}\"" 170 | 171 | echo -e '\n45W MagSafe 1' 172 | every_45w_magsafe1_model="$(echo "${every_45w_magsafe1_model%+}" | tr '+' '\n' | sort -uV)" 173 | echo "\"${every_45w_magsafe1_model//$'\n'/", "}\"" 174 | 175 | echo -e '\n85W MagSafe 2' 176 | every_85w_magsafe2_model="$(echo "${every_85w_magsafe2_model%+}" | tr '+' '\n' | sort -uV)" 177 | echo "\"${every_85w_magsafe2_model//$'\n'/", "}\"" 178 | 179 | echo -e '\n60W MagSafe 2' 180 | every_60w_magsafe2_model="$(echo "${every_60w_magsafe2_model%+}" | tr '+' '\n' | sort -uV)" 181 | echo "\"${every_60w_magsafe2_model//$'\n'/", "}\"" 182 | 183 | echo -e '\n45W MagSafe 2' 184 | every_45w_magsafe2_model="$(echo "${every_45w_magsafe2_model%+}" | tr '+' '\n' | sort -uV)" 185 | echo "\"${every_45w_magsafe2_model//$'\n'/", "}\"" 186 | 187 | echo -e '\n96W USB-C' 188 | every_96w_usbc_model="$(echo "${every_96w_usbc_model%+}" | tr '+' '\n' | sort -uV)" 189 | echo "\"${every_96w_usbc_model//$'\n'/", "}\"" 190 | 191 | echo -e '\n87W USB-C' 192 | every_87w_usbc_model="$(echo "${every_87w_usbc_model%+}" | tr '+' '\n' | sort -uV)" 193 | echo "\"${every_87w_usbc_model//$'\n'/", "}\"" 194 | 195 | echo -e '\n67W USB-C' 196 | every_67w_usbc_model="$(echo "${every_67w_usbc_model%+}" | tr '+' '\n' | sort -uV)" 197 | echo "\"${every_67w_usbc_model//$'\n'/", "}\"" 198 | 199 | echo -e '\n61W USB-C' 200 | every_61w_usbc_model="$(echo "${every_61w_usbc_model%+}" | tr '+' '\n' | sort -uV)" 201 | echo "\"${every_61w_usbc_model//$'\n'/", "}\"" 202 | 203 | echo -e '\n30W USB-C' 204 | every_30w_usbc_model="$(echo "${every_30w_usbc_model%+}" | tr '+' '\n' | sort -uV)" 205 | echo "\"${every_30w_usbc_model//$'\n'/", "}\"" 206 | 207 | echo -e '\n29W USB-C' 208 | every_29w_usbc_model="$(echo "${every_29w_usbc_model%+}" | tr '+' '\n' | sort -uV)" 209 | echo "\"${every_29w_usbc_model//$'\n'/", "}\"" 210 | 211 | echo -e '\n140W USB-C/MagSafe 3' 212 | every_140w_magsafe3_model="$(echo "${every_140w_magsafe3_model%+}" | tr '+' '\n' | sort -uV)" 213 | echo "\"${every_140w_magsafe3_model//$'\n'/", "}\"" 214 | 215 | echo -e '\n67W or 96W USB-C/MagSafe 3' 216 | every_67w_or_96w_magsafe3_model="$(echo "${every_67w_or_96w_magsafe3_model%+}" | tr '+' '\n' | sort -uV)" 217 | echo "\"${every_67w_or_96w_magsafe3_model//$'\n'/", "}\"" 218 | 219 | echo -e '\n30W or 35W Dual Port or 67W USB-C/MagSafe 3' 220 | every_30w_or_35W_dp_or_67w_magsafe3_model="$(echo "${every_30w_or_35W_dp_or_67w_magsafe3_model%+}" | tr '+' '\n' | sort -uV)" 221 | echo "\"${every_30w_or_35W_dp_or_67w_magsafe3_model//$'\n'/", "}\"" 222 | 223 | echo -e '\nUNKNOWN Power Adapter (REQUIRES MANUAL EXAMINATION)' 224 | every_unknown_model="$(echo "${every_unknown_model%+}" | tr '+' '\n' | sort -uV)" 225 | echo "\"${every_unknown_model//$'\n'/", "}\"" 226 | 227 | echo '' 228 | 229 | # Example output from 2/1/23: 230 | 231 | # 85W MagSafe 1 232 | # "MacBookPro1,1", "MacBookPro1,2", "MacBookPro2,1", "MacBookPro2,2", "MacBookPro3,1", "MacBookPro4,1", "MacBookPro5,1", "MacBookPro5,2", "MacBookPro5,3", "MacBookPro6,1", "MacBookPro6,2", "MacBookPro8,2", "MacBookPro8,3", "MacBookPro9,1" 233 | 234 | # 60W MagSafe 1 235 | # "MacBook1,1", "MacBook2,1", "MacBook3,1", "MacBook4,1", "MacBook5,1", "MacBook5,2", "MacBook6,1", "MacBook7,1", "MacBookPro5,4", "MacBookPro5,5", "MacBookPro7,1", "MacBookPro8,1", "MacBookPro9,2" 236 | 237 | # 45W MagSafe 1 238 | # "MacBookAir1,1", "MacBookAir2,1", "MacBookAir3,1", "MacBookAir3,2", "MacBookAir4,1", "MacBookAir4,2" 239 | 240 | # 85W MagSafe 2 241 | # "MacBookPro10,1", "MacBookPro11,2", "MacBookPro11,3", "MacBookPro11,4", "MacBookPro11,5" 242 | 243 | # 60W MagSafe 2 244 | # "MacBookPro10,2", "MacBookPro11,1", "MacBookPro12,1" 245 | 246 | # 45W MagSafe 2 247 | # "MacBookAir5,1", "MacBookAir5,2", "MacBookAir6,1", "MacBookAir6,2", "MacBookAir7,1", "MacBookAir7,2" 248 | 249 | # 96W USB-C 250 | # "MacBookPro16,1", "MacBookPro16,4" 251 | 252 | # 87W USB-C 253 | # "MacBookPro13,3", "MacBookPro14,3", "MacBookPro15,1", "MacBookPro15,3" 254 | 255 | # 67W USB-C 256 | # "Mac14,7" 257 | 258 | # 61W USB-C 259 | # "MacBookPro13,1", "MacBookPro13,2", "MacBookPro14,1", "MacBookPro14,2", "MacBookPro15,2", "MacBookPro15,4", "MacBookPro16,2", "MacBookPro16,3", "MacBookPro17,1" 260 | 261 | # 30W USB-C 262 | # "MacBook10,1", "MacBookAir8,1", "MacBookAir8,2", "MacBookAir9,1", "MacBookAir10,1" 263 | 264 | # 29W USB-C 265 | # "MacBook8,1", "MacBook9,1" 266 | 267 | # 140W USB-C/MagSafe 3 268 | # "Mac14,6", "Mac14,10", "MacBookPro18,1", "MacBookPro18,2" 269 | 270 | # 67W or 96W USB-C/MagSafe 3 271 | # "Mac14,5", "Mac14,9", "MacBookPro18,3", "MacBookPro18,4" 272 | 273 | # 30W or 35W Dual Port or 67W USB-C/MagSafe 3 274 | # "Mac14,2" 275 | 276 | # UNKNOWN Power Adapter (REQUIRES MANUAL EXAMINATION) 277 | # "" -------------------------------------------------------------------------------- /Other Scripts/get_specs_url_from_serial.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck enable=add-default-case,avoid-nullary-conditions,check-unassigned-uppercase,deprecate-which,quote-safe-variables,require-double-brackets 3 | 4 | # 5 | # Created by Pico Mitchell (of Free Geek) on 8/9/22. 6 | # 7 | # MIT License 8 | # 9 | # Copyright (c) 2022 Free Geek 10 | # 11 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 12 | # to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 | # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | # 21 | 22 | PATH='/usr/bin:/bin:/usr/sbin:/sbin' 23 | 24 | serial_number="$1" # Use a specified serial number from the first argument. 25 | 26 | if [[ -z "$1" ]]; then # Get the current Mac serial number if no argument specified. 27 | serial_number="$(/usr/libexec/PlistBuddy -c 'Print :0:IOPlatformSerialNumber' /dev/stdin <<< "$(ioreg -arc IOPlatformExpertDevice -k IOPlatformSerialNumber -d 1)" 2> /dev/null)" 28 | fi 29 | 30 | # The following URLs were discovered from examining how "https://support.apple.com/specs/${serial_number}" loads the specs URL via JavaScript (as of August 9th, 2022 in case this breaks in the future). 31 | 32 | serial_search_results_json="$(curl -m 10 -sfL "https://km.support.apple.com/kb/index?page=categorydata&serialnumber=${serial_number}" 2> /dev/null)" # I have seem this URL API timeout after 5 seconds when called multiple times rapidly (likely because of rate limiting), so give it a 10 second timeout which seems to always work. 33 | 34 | if [[ -z "${serial_search_results_json}" ]]; then 35 | >&2 echo 'INTERNET REQUIRED - SERIAL SEARCH FAILED' 36 | exit 1 37 | fi 38 | 39 | IFS=$'\n' read -rd '' -a serial_search_results_values < <(osascript -l 'JavaScript' -e ' 40 | function run(argv) { 41 | const serialSearchResultsDict = JSON.parse(argv[0]) 42 | return [serialSearchResultsDict.name, serialSearchResultsDict.id, serialSearchResultsDict.parent, serialSearchResultsDict.grandparent, serialSearchResultsDict.greatgrandparent].join("\n") 43 | } 44 | ' -- "${serial_search_results_json}" 2> /dev/null) 45 | # NOTE: Because of JavaScript behavior, any "undefined" (or "null") values in an array would be turned into empty strings when using "join", making them empty lines. 46 | # And, because of bash behaviors with whitespace IFS treating consecutive whitespace as a single delimiter (explained in https://mywiki.wooledge.org/IFS), 47 | # any empty lines will NOT be included in the bash array being created with this technique to set all lines to an array. 48 | # So, that means if any of these values are not found, the bash array WILL NOT have a count of exactly 5 which we can check to verify all required values were properly loaded. 49 | 50 | if (( ${#serial_search_results_values[@]} != 5 )); then 51 | >&2 echo "UNEXPECTED ERROR - SERIAL SEARCH DOES NOT CONTAIN EXPECTED/REQUIRED VALUES: $(declare -p serial_search_results_values | cut -d '=' -f 2-)" 52 | exit 2 53 | fi 54 | 55 | specs_search_results_json="$(curl -m 10 -sfL "https://km.support.apple.com/kb/index?page=specs_browse&category=${serial_search_results_values[1]}&parent=${serial_search_results_values[2]}&grandparent=${serial_search_results_values[3]}&greatgrandparent=${serial_search_results_values[4]}" 2> /dev/null)" 56 | 57 | if [[ -z "${specs_search_results_json}" ]]; then 58 | >&2 echo 'INTERNET REQUIRED - SPECS SEARCH FAILED' 59 | exit 3 60 | fi 61 | 62 | specs_url_kb_part="$(osascript -l 'JavaScript' -e 'run = argv => JSON.parse(argv[0]).specs[0].url' -- "${specs_search_results_json}" 2> /dev/null)" 63 | 64 | if [[ -z "${specs_url_kb_part}" ]]; then 65 | >&2 echo 'UNEXPECTED ERROR - SPECS SEARCH DOES NOT CONTAIN KB SPECS URL' 66 | exit 4 67 | fi 68 | 69 | specs_url="https://support.apple.com${specs_url_kb_part}" 70 | 71 | echo "Specs URL for \"${serial_search_results_values[0]}\": ${specs_url}" 72 | -------------------------------------------------------------------------------- /Other Scripts/get_truetone_from_all_mac_specs_pages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck enable=add-default-case,avoid-nullary-conditions,check-unassigned-uppercase,deprecate-which,quote-safe-variables,require-double-brackets 3 | 4 | # 5 | # Created by Pico Mitchell (of Free Geek) on 8/9/22. 6 | # 7 | # MIT License 8 | # 9 | # Copyright (c) 2022 Free Geek 10 | # 11 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 12 | # to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 | # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | # 21 | 22 | PATH='/usr/bin:/bin:/usr/sbin:/sbin' 23 | 24 | declare -a all_mac_identification_pages=() # All Mac identification pages are listed on https://support.apple.com/HT213325 as well as in the "On the product or its packaging" section of https://support.apple.com/HT201581 25 | all_mac_identification_pages+=( 'HT201300' ) # MacBook Pro 26 | all_mac_identification_pages+=( 'HT201862' ) # MacBook Air 27 | all_mac_identification_pages+=( 'HT201608' ) # MacBook 28 | all_mac_identification_pages+=( 'HT201634' ) # iMac 29 | #all_mac_identification_pages+=( 'HT201894' ) # Mac mini 30 | #all_mac_identification_pages+=( 'HT213073' ) # Mac Studio 31 | #all_mac_identification_pages+=( 'HT202888' ) # Mac Pro 32 | 33 | every_truetone_model='' 34 | 35 | for this_mac_idenification_page in "${all_mac_identification_pages[@]}"; do 36 | this_mac_idenification_page_source="$(curl -m 5 -sfL "https://support.apple.com/${this_mac_idenification_page}")" 37 | 38 | this_model_identifier='' 39 | while IFS='' read -r this_model_id_or_specs_url; do 40 | if [[ "${this_model_id_or_specs_url}" == 'https://'* ]]; then 41 | echo " (${this_model_id_or_specs_url}):" 42 | truetone_element_from_page="$(curl -m 5 -sfL "${this_model_id_or_specs_url}" | xmllint --html --xpath 'string(//*[contains(text(),"True Tone")])' - 2> /dev/null)" 43 | if [[ -n "${truetone_element_from_page}" ]]; then 44 | echo "Supports True Tone" 45 | every_truetone_model+="${this_model_identifier}+" 46 | else 47 | echo "DOES NOT SUPPORT True Tone" 48 | fi 49 | else 50 | this_model_identifier="${this_model_id_or_specs_url}" 51 | echo -en "\n${this_model_identifier}" 52 | fi 53 | done < <(echo "${this_mac_idenification_page_source}" | awk -F ':|"' '/Model Identifier:/ { gsub(" ", " ", $NF); gsub(", ", "+", $NF); gsub("; ", "+", $NF); gsub(" ", "", $NF); gsub("
", "", $NF); print ""; print $NF } /Tech Specs:/ { print "https:" $4 }') 54 | # echo "${this_mac_idenification_page_source}" | xmllint --html --xpath '//a[contains(@href,"/kb/SP")]/@href' - 2> /dev/null | tr '"' '\n' | grep '/kb/SP' | sort -ur 55 | done 56 | 57 | echo -e "\n\nSupports True Tone" 58 | every_truetone_model="$(echo "${every_truetone_model%+}" | tr '+' '\n' | sort -uV)" 59 | echo "\"${every_truetone_model//$'\n'/", "}\"" 60 | 61 | echo '' 62 | 63 | # Example output from 2/1/23: 64 | 65 | # Supports True Tone 66 | # "Mac14,2", "Mac14,5", "Mac14,6", "Mac14,7", "Mac14,9", "Mac14,10", "MacBookAir8,2", "MacBookAir9,1", "MacBookAir10,1", "MacBookPro15,1", "MacBookPro15,2", "MacBookPro15,3", "MacBookPro15,4", "MacBookPro16,1", "MacBookPro16,2", "MacBookPro16,3", "MacBookPro16,4", "MacBookPro17,1", "MacBookPro18,1", "MacBookPro18,2", "MacBookPro18,3", "MacBookPro18,4", "iMac20,1", "iMac20,2", "iMac21,1", "iMac21,2" 67 | -------------------------------------------------------------------------------- /Other Scripts/quit_apps_by_bundle_id.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # shellcheck enable=add-default-case,avoid-nullary-conditions,check-unassigned-uppercase,deprecate-which,quote-safe-variables 3 | 4 | quit_apps_by_bundle_id() { # Arguments = Bundle IDs to Quit (multiple can be specified) 5 | # 6 | # Created by Pico Mitchell (of Free Geek) on 9/13/22 (updated on 1/9/23). 7 | # 8 | # MIT License 9 | # 10 | # Copyright (c) 2022 Free Geek 11 | # 12 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 13 | # to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 14 | # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | # 22 | 23 | if [ "$#" -eq '0' ]; then 24 | >&2 echo 'Quit App by Bundle ID ERROR: At least one Bundle ID must be specified.' 25 | return 1 26 | fi 27 | 28 | # Based On: https://github.com/t-lark/Auto-Update/blob/master/app_quitter.py#L179-L190 (Copyright (c) 2019 Snowflake Inc. Licensed under the Apache License, Version 2.0) 29 | /usr/bin/osascript -l 'JavaScript' -e ' 30 | "use strict" 31 | ObjC.import("AppKit") 32 | 33 | function run(argv) { 34 | for (const thisBundleID of argv) { 35 | const runningAppsForBundleID = $.NSRunningApplication.runningApplicationsWithBundleIdentifier(thisBundleID).js 36 | 37 | for (const thisRunningApp of runningAppsForBundleID) { // An array is always returned, so must iterate all NSRunningApplication objects. 38 | thisRunningApp.terminate // First tell the app to quit itself gracefully. 39 | 40 | for (let waitForQuitSeconds = 0; waitForQuitSeconds < 6; waitForQuitSeconds ++) { // Wait for UP TO 3 seconds for the app to quit. 41 | delay(0.5) // Wait in half seconds so that we can be done quickly when the app quits itself gracefully. 42 | if (thisRunningApp.terminated) 43 | break 44 | } 45 | 46 | if (!thisRunningApp.terminated) // If app has not quit gracefully after 3 seconds, force quit it. 47 | thisRunningApp.forceTerminate 48 | } 49 | } 50 | } 51 | ' -- "$@" 2> /dev/null 52 | } 53 | 54 | quit_apps_by_bundle_id "$@" 55 | -------------------------------------------------------------------------------- /Production Scripts/Free Geek Login Progress/Source/Free Geek Login Progress.applescript: -------------------------------------------------------------------------------- 1 | -- By: Pico Mitchell 2 | -- For: MacLand @ Free Geek 3 | -- 4 | -- MIT License 5 | -- 6 | -- Copyright (c) 2021 Free Geek 7 | -- 8 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 9 | -- to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | -- and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 11 | -- 12 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | -- 14 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | -- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -- 18 | 19 | -- Version: 2023.2.9-1 20 | 21 | -- Build Flag: LSUIElement 22 | -- Build Flag: IncludeSignedLauncher 23 | 24 | use AppleScript version "2.7" 25 | use scripting additions 26 | use framework "AppKit" 27 | 28 | set currentBundleIdentifier to "UNKNOWN" 29 | 30 | try 31 | set infoPlistPath to ((POSIX path of (path to me)) & "Contents/Info.plist") 32 | ((infoPlistPath as POSIX file) as alias) 33 | 34 | set intendedAppName to "Free Geek Login Progress" -- Hardcode intended App name because Name or Bundle Identifier changes should not be done lightly or accidentally. 35 | 36 | try 37 | do shell script ("/usr/libexec/PlistBuddy -c 'Print :FGBuiltByMacLandScriptBuilder' " & (quoted form of infoPlistPath)) 38 | ((((POSIX path of (path to me)) & "Contents/MacOS/" & intendedAppName) as POSIX file) as alias) 39 | on error 40 | try 41 | activate 42 | end try 43 | display alert " 44 | “" & (name of me) & "” must be built by the “MacLand Script Builder” script." buttons {"Quit"} default button 1 as critical 45 | quit 46 | delay 10 47 | end try 48 | 49 | set AppleScript's text item delimiters to "-" 50 | set intendedBundleIdentifier to ("org.freegeek." & ((words of intendedAppName) as text)) 51 | set currentBundleIdentifier to ((do shell script ("/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' " & (quoted form of infoPlistPath))) as text) 52 | if (currentBundleIdentifier is not equal to intendedBundleIdentifier) then error "“" & (name of me) & "” does not have the correct Bundle Identifier. 53 | 54 | 55 | Current Bundle Identifier: 56 | " & currentBundleIdentifier & " 57 | 58 | Intended Bundle Identifier: 59 | " & intendedBundleIdentifier 60 | on error checkInfoPlistError 61 | if (checkInfoPlistError does not start with "Can’t make file") then 62 | try 63 | activate 64 | end try 65 | display alert checkInfoPlistError buttons {"Quit"} default button 1 as critical 66 | quit 67 | delay 10 68 | end if 69 | end try 70 | 71 | try 72 | (current application's NSApp's mainMenu's removeAllItems()) -- Remove all menu items so shortcuts like Command+E to "Edit Script" cannot be used to disrupt anything. 73 | end try 74 | 75 | set isRunningAtLoginWindow to false 76 | try 77 | set isRunningAtLoginWindow to ((do shell script "launchctl managername") is equal to "LoginWindow") 78 | end try 79 | 80 | set isResetting to ((POSIX path of (path to me)) contains "/fg-snapshot-reset/") 81 | 82 | set logPath to "/Users/Shared/Build Info/Prepare OS Log.txt" 83 | if (isResetting) then set logPath to "/Users/Shared/fg-snapshot-reset/log.txt" 84 | 85 | set progress total steps to -1 86 | set progress completed steps to 0 87 | 88 | set doNotDisturbNote to "🚫 DO NOT DISTURB THIS MAC WHILE IT IS BEING CUSTOMIZED" 89 | 90 | if (isResetting) then 91 | set progress description to "🧹 Resetting this Mac…" 92 | set doNotDisturbNote to "🚫 DO NOT DISTURB THIS MAC WHILE IT IS BEING RESET" 93 | else 94 | set progress description to "🚧 Customizing this Mac…" 95 | end if 96 | 97 | set progress additional description to (" 98 | " & doNotDisturbNote) 99 | 100 | try 101 | repeat with thisWindow in (current application's NSApp's |windows|()) 102 | if (thisWindow's isVisible() is true) then 103 | if (((thisWindow's title()) as text) is equal to (name of me)) then 104 | repeat with thisProgressWindowSubView in ((thisWindow's contentView())'s subviews()) 105 | if (((thisProgressWindowSubView's className()) as text) is equal to "NSProgressIndicator") then 106 | if (isResetting) then 107 | (thisWindow's setTitle:"Free Geek Reset Progress") 108 | else 109 | (thisWindow's setTitle:"Free Geek Customizations Progress") 110 | end if 111 | 112 | -- Set Style Mask to ONLY be Titled, which make it not minimizable or resizable and hides all the titlebar buttons. 113 | (thisWindow's setStyleMask:(current application's NSWindowStyleMaskTitled as integer)) -- MUST be "as integer" instead of "as number" for ObjC-bridge casting to not throw an exception. 114 | 115 | -- Also do not want window to be movable so that it stays over the login fields. 116 | if (isRunningAtLoginWindow) then (thisWindow's setMovable:false) -- Only do this if at the login window to make debugging easier when running in OS. 117 | 118 | -- Make the window wider so that the width doesn't need to be expanded automatically when long log entries get added, which makes re-centering look funky. 119 | set thisWindowSize to (item 2 of (thisWindow's frame())) 120 | (thisWindow's setContentSize:{650, (item 2 of thisWindowSize)}) -- Largest width seen was 630 for the log line deleting Secure Token References with the UUID. 121 | 122 | -- Center the window so it's in a nice spot on the screen and will leave a lot of room below for the log. 123 | -- The window will also keep being re-centered as the log grows so that the most contents will always stay visible no matter what the screen size is. 124 | -- Previously was just moving the window up 100 points to hide the login icon/fields, but that didn't work great as I added more logging and the window could grow to go below the bottom of the screen. 125 | -- So now, when the log is short the login icon/fields may be visible but should get covered as the log grows throughout the process. 126 | (thisWindow's |center|()) 127 | 128 | -- References for Visibility at Login Screen: 129 | -- https://developer.apple.com/library/archive/samplecode/PreLoginAgents/Listings/PreLoginAgentCocoa_AppDelegate_m.html#//apple_ref/doc/uid/DTS10004414-PreLoginAgentCocoa_AppDelegate_m-DontLinkElementID_5 130 | -- https://bitbucket.org/twocanoes/macdeploystick/src/a7989eddae93d3339b54e30356da0c6ff13fd795/first-run-install/Scripts/com.twocanoes.mds/LoginLog.app/Contents/Resources/LLLogWindowController.py#lines-130 131 | -- https://bitbucket.org/twocanoes/loginlog/src/6a15de70660a7d93b9c9bcbf3ed7efd85fb57d17/LoginLog/LLLogWindowController.swift#lines-108 132 | (thisWindow's setCanBecomeVisibleWithoutLogin:true) 133 | (thisWindow's setLevel:2.147483647E+9) -- The highest defined window level is "kCGMaximumWindowLevelKey" which is equal to "2147483631" (https://michelf.ca/blog/2016/choosing-window-level/). We are setting an even higher level of "2147483647" which is the signed 32-bit interger max which seems to be the highest possible level since any higher and the value appears to roll over and no longer be topmost. 134 | (thisWindow's orderFrontRegardless()) -- This seemed to not be necessary on macOS 11 Big Sur and older, but I found that it is very necessary on macOS 12 Monterey and newer since this (or presumably any) app cannot actually activate itself to become frontmost. 135 | else if ((((thisProgressWindowSubView's className()) as text) is equal to "NSButton") and ((thisProgressWindowSubView's title() as text) is equal to "Stop")) then 136 | if (isRunningAtLoginWindow) then (thisProgressWindowSubView's setEnabled:false) -- Only do this if at the login window to make debugging easier when running in OS. 137 | end if 138 | end repeat 139 | end if 140 | end if 141 | end repeat 142 | end try 143 | 144 | repeat 145 | try 146 | activate -- On macOS 12 Monterey and newer, it seems that this (or presumably any) app cannot activate itself at the login window using either AppleScript "activate" or ObjC "activateIgnoringOtherApps" (but still "activate" anyway for older macOS versions). 147 | -- It appears that "com.apple.SecurityAgent" is always the frontmost application at the login window on macOS 12 Monterey and newer and nothing I've tried can change that. 148 | -- The failure of this app to be able to activate itself on macOS 12 Monterey and newer makes having the window "orderFrontRegardless" (done above and below here) very important so the window is visible at all, but it won't be the focused key window since this app isn't frontmost. 149 | end try 150 | 151 | if ((progress completed steps) is equal to 0) then 152 | try 153 | ((logPath as POSIX file) as alias) 154 | 155 | set logOutput to (do shell script ("cat " & (quoted form of logPath))) 156 | 157 | considering case -- Make sure the following check for an error is case senstive to not have any false matched warning messages. 158 | if (logOutput contains " ERROR:") then -- Also check for the TAB before "ERROR:" to be sure this is one of our error messages and not part of a warning message. 159 | set progress total steps to 1 160 | set progress completed steps to 1 161 | 162 | if (isResetting) then 163 | set progress description to "❌ Error Occurred While Resetting this Mac" 164 | else 165 | set progress description to "❌ Error Occurred While Customizing this Mac" 166 | end if 167 | 168 | set progress additional description to (" 169 | ‼️ PLEASE INFORM AND DELIVER THIS MAC TO FREE GEEK I.T. 170 | 171 | " & logOutput) 172 | else 173 | set progress additional description to (" 174 | " & doNotDisturbNote & " 175 | 176 | " & logOutput) 177 | end if 178 | end considering 179 | end try 180 | 181 | if ((progress completed steps) is equal to 0) then -- Do not check for completion if an error was detected in the log. 182 | try 183 | -- The LaunchDaemon being deleted indicates successful completion. 184 | if (isResetting) then 185 | (("/Library/LaunchDaemons/org.freegeek.fg-snapshot-reset.plist" as POSIX file) as alias) 186 | else 187 | (("/Library/LaunchDaemons/org.freegeek.fg-install-packages.plist" as POSIX file) as alias) 188 | end if 189 | on error 190 | set progress total steps to 1 191 | set progress completed steps to 1 192 | 193 | if (isResetting) then 194 | set progress description to "✅ Successfully Reset this Mac" 195 | set progress additional description to (progress additional description & " 196 | 197 | ⤵️ SHUTTING DOWN THIS MAC") 198 | else 199 | set progress description to "✅ Successfully Customized this Mac" 200 | set progress additional description to (progress additional description & " 201 | 202 | 🔄 REBOOTING THIS MAC") 203 | end if 204 | end try 205 | end if 206 | 207 | try 208 | repeat with thisWindow in (current application's NSApp's |windows|()) 209 | if (thisWindow's isVisible() is true) then 210 | set thisWindowTitle to ((thisWindow's title()) as text) 211 | if ((thisWindowTitle is equal to (name of me)) or (thisWindowTitle ends with " Progress")) then 212 | ((thisWindow's contentView())'s display()) -- Force display before re-centering since sometimes it takes a moment for the window contents to be updated on their own which can cause the centering to happen before the contents update. 213 | (thisWindow's |center|()) -- Keep re-centering window so the most contents will always be displayed no matter how long the log gets and what the screen size is. 214 | (thisWindow's orderFrontRegardless()) -- Also keep ordering front just in case. 215 | end if 216 | end if 217 | end repeat 218 | end try 219 | end if 220 | 221 | delay 0.25 222 | end repeat 223 | -------------------------------------------------------------------------------- /Production Scripts/Free Geek Snapshot Helper/Source/Free Geek Snapshot Helper.applescript: -------------------------------------------------------------------------------- 1 | -- By: Pico Mitchell 2 | -- For: MacLand @ Free Geek 3 | -- 4 | -- MIT License 5 | -- 6 | -- Copyright (c) 2021 Free Geek 7 | -- 8 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 9 | -- to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | -- and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 11 | -- 12 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | -- 14 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | -- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -- 18 | 19 | -- Version: 2023.7.7-3 20 | 21 | -- Build Flag: LSUIElement 22 | 23 | use AppleScript version "2.7" 24 | use scripting additions 25 | 26 | set currentBundleIdentifier to "UNKNOWN" 27 | 28 | try 29 | set infoPlistPath to ((POSIX path of (path to me)) & "Contents/Info.plist") 30 | ((infoPlistPath as POSIX file) as alias) 31 | 32 | set intendedAppName to "Free Geek Snapshot Helper" -- Hardcode intended App name because Name or Bundle Identifier changes should not be done lightly or accidentally. 33 | 34 | try 35 | do shell script ("/usr/libexec/PlistBuddy -c 'Print :FGBuiltByMacLandScriptBuilder' " & (quoted form of infoPlistPath)) 36 | ((((POSIX path of (path to me)) & "Contents/MacOS/" & intendedAppName) as POSIX file) as alias) 37 | on error 38 | try 39 | activate 40 | end try 41 | display alert " 42 | “" & (name of me) & "” must be built by the “MacLand Script Builder” script." buttons {"Quit"} default button 1 as critical 43 | quit 44 | delay 10 45 | end try 46 | 47 | set AppleScript's text item delimiters to "-" 48 | set intendedBundleIdentifier to ("org.freegeek." & ((words of intendedAppName) as text)) 49 | set currentBundleIdentifier to ((do shell script ("/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' " & (quoted form of infoPlistPath))) as text) 50 | if (currentBundleIdentifier is not equal to intendedBundleIdentifier) then error "“" & (name of me) & "” does not have the correct Bundle Identifier. 51 | 52 | 53 | Current Bundle Identifier: 54 | " & currentBundleIdentifier & " 55 | 56 | Intended Bundle Identifier: 57 | " & intendedBundleIdentifier 58 | on error checkInfoPlistError 59 | if (checkInfoPlistError does not start with "Can’t make file") then 60 | try 61 | activate 62 | end try 63 | display alert checkInfoPlistError buttons {"Quit"} default button 1 as critical 64 | quit 65 | delay 10 66 | end if 67 | end try 68 | 69 | try 70 | set mainScptPath to ((POSIX path of (path to me)) & "Contents/Resources/Scripts/main.scpt") 71 | ((mainScptPath as POSIX file) as alias) 72 | do shell script "osadecompile " & (quoted form of mainScptPath) 73 | error " 74 | “" & (name of me) & "” must be exported as a Run-Only Script." 75 | on error checkReadOnlyErrorMessage 76 | if ((checkReadOnlyErrorMessage does not contain "errOSASourceNotAvailable") and (checkReadOnlyErrorMessage does not start with "Can’t make file")) then 77 | try 78 | activate 79 | end try 80 | display alert checkReadOnlyErrorMessage buttons {"Quit"} default button 1 as critical 81 | quit 82 | delay 10 83 | end if 84 | end try 85 | 86 | 87 | global adminUsername, adminPassword, lastDoShellScriptAsAdminAuthDate -- Needs to be accessible in doShellScriptAsAdmin function. 88 | set lastDoShellScriptAsAdminAuthDate to 0 89 | 90 | set adminUsername to "fg-admin" 91 | set adminPassword to "[MACLAND SCRIPT BUILDER WILL REPLACE THIS PLACEHOLDER WITH OBFUSCATED ADMIN PASSWORD]" 92 | 93 | set demoUsername to "fg-demo" 94 | 95 | 96 | if (((short user name of (system info)) is equal to demoUsername) and ((POSIX path of (path to me)) is equal to ("/Users/" & demoUsername & "/Applications/" & (name of me) & ".app/"))) then 97 | set freeGeekUpdaterAppPath to ("/Users/" & demoUsername & "/Applications/Free Geek Updater.app") 98 | try 99 | ((freeGeekUpdaterAppPath as POSIX file) as alias) 100 | 101 | if (application freeGeekUpdaterAppPath is running) then -- Quit if Updater is running so that this app can be updated if needed. 102 | quit 103 | delay 10 104 | end if 105 | end try 106 | 107 | try 108 | (("/Users/Shared/.fg-snapshot-preserver" as POSIX file) as alias) 109 | 110 | set shouldShutDownAfterError to false 111 | try 112 | (("/Users/Shared/.fgResetSnapshotCreated" as POSIX file) as alias) 113 | 114 | set systemVersion to (system version of (system info)) 115 | considering numeric strings 116 | set isBigSurOrNewer to (systemVersion ≥ "11.0") 117 | end considering 118 | 119 | if (isBigSurOrNewer) then 120 | try 121 | -- Do not bother try re-mounting if it's already mounted. 122 | (("/Users/Shared/.fg-snapshot-preserver/mount/Users/Shared/fg-snapshot-reset" as POSIX file) as alias) 123 | try 124 | doShellScriptAsAdmin("echo \"$(date '+%D %T') Snapshot Helper: Reset Snapshot Already Mounted\" >> '/Users/Shared/.fg-snapshot-preserver/log.txt'") 125 | end try 126 | on error 127 | set resetSnapshotName to (do shell script "head -1 /Users/Shared/.fgResetSnapshotCreated") 128 | 129 | try 130 | if (resetSnapshotName starts with "com.apple.TimeMachine") then 131 | try 132 | (("/Users/Shared/.fg-snapshot-preserver/mount" as POSIX file) as alias) 133 | on error 134 | try 135 | -- Needs admin privileges since root owns ".fg-snapshot-preserver" folder. 136 | doShellScriptAsAdmin("mkdir '/Users/Shared/.fg-snapshot-preserver/mount'") 137 | end try 138 | end try 139 | 140 | try 141 | -- But the mount folder needs to be writeable by demoUsername or mounting the snapshot will fail (even when using administrator privileges). 142 | doShellScriptAsAdmin("chown " & demoUsername & " '/Users/Shared/.fg-snapshot-preserver/mount'") 143 | end try 144 | 145 | try 146 | -- Mounting the reset Snapshot will prevent macOS from deleting it after 24 hours: https://eclecticlight.co/2021/03/28/last-week-on-my-mac-macos-at-20-apfs-at-4/#comment-59001 147 | do shell script ("bash -c " & (quoted form of ("mount_apfs -o rdonly,nobrowse -s " & (quoted form of resetSnapshotName) & " \"$(/usr/libexec/PlistBuddy -c 'Print :DeviceNode' /dev/stdin <<< \"$(diskutil info -plist '/System/Volumes/Data')\")\" '/Users/Shared/.fg-snapshot-preserver/mount'"))) 148 | try 149 | doShellScriptAsAdmin("echo \"$(date '+%D %T') Snapshot Helper: Successfully Mounted Reset Snapshot\" >> '/Users/Shared/.fg-snapshot-preserver/log.txt'") 150 | end try 151 | on error mountErrorMessage number mountErrorNumber 152 | set snapshotMountError to ("FAILED to Mount Reset Snapshot (Error Code " & (mountErrorNumber as text) & ": " & mountErrorMessage & ")") 153 | try 154 | doShellScriptAsAdmin("echo \"$(date '+%D %T') Snapshot Helper: " & snapshotMountError & "\" >> '/Users/Shared/.fg-snapshot-preserver/log.txt'") 155 | end try 156 | 157 | error snapshotMountError 158 | end try 159 | else 160 | set snapshotNameError to ("Invalid Reset Snapshot Name (" & resetSnapshotName & ")") 161 | try 162 | doShellScriptAsAdmin("echo \"$(date '+%D %T') Snapshot Helper: " & snapshotNameError & "\" >> '/Users/Shared/.fg-snapshot-preserver/log.txt'") 163 | end try 164 | 165 | error snapshotNameError 166 | end if 167 | on error snapshotErrorMessage 168 | try 169 | activate 170 | end try 171 | try 172 | do shell script "afplay /System/Library/Sounds/Basso.aiff > /dev/null 2>&1 &" 173 | end try 174 | display alert ("CRITICAL “" & (name of me) & "” ERROR: 175 | 176 | " & snapshotErrorMessage) message "This should not have happened, please inform and deliver this Mac to Free Geek I.T. for further research." buttons {"Shut Down"} default button 1 as critical 177 | 178 | set shouldShutDownAfterError to true 179 | end try 180 | end try 181 | else 182 | try 183 | doShellScriptAsAdmin("echo \"$(date '+%D %T') Snapshot Helper: Not Mounting Reset Snapshot on Catalina\" >> '/Users/Shared/.fg-snapshot-preserver/log.txt'") 184 | end try 185 | end if 186 | on error 187 | set resetSnapshotName to "UNKNOWN SNAPSHOT NAME" 188 | set resetSnapshotLostReason to "UNKNOWN LOST REASON" 189 | try 190 | (("/Users/Shared/.fgResetSnapshotLost" as POSIX file) as alias) 191 | try 192 | set resetSnapshotLostReason to (do shell script "tail -1 /Users/Shared/.fgResetSnapshotLost") 193 | end try 194 | try 195 | set possibleResetSnapshotName to (do shell script "head -1 /Users/Shared/.fgResetSnapshotLost") -- ".fgResetSnapshotLost" could only have one line if the ".fgResetSnapshotCreated" file was manually deleted (which shouldn't happene except in my testing). 196 | if (possibleResetSnapshotName is not equal to resetSnapshotLostReason) then set resetSnapshotName to possibleResetSnapshotName 197 | end try 198 | end try 199 | 200 | repeat -- dialogs timeout when screen is asleep or locked (just in case) 201 | set isAwake to true 202 | try 203 | set isAwake to ((run script "ObjC.import('CoreGraphics'); $.CGDisplayIsActive($.CGMainDisplayID())" in "JavaScript") is equal to 1) 204 | end try 205 | 206 | set isUnlocked to true 207 | try 208 | set isUnlocked to ((do shell script ("bash -c " & (quoted form of "/usr/libexec/PlistBuddy -c 'Print :IOConsoleUsers:0:CGSSessionScreenIsLocked' /dev/stdin <<< \"$(ioreg -ac IORegistryEntry -k IOConsoleUsers -d 1)\""))) is not equal to "true") 209 | end try 210 | 211 | if (isAwake and isUnlocked) then 212 | exit repeat 213 | else 214 | delay 1 215 | end if 216 | end repeat 217 | 218 | try 219 | activate 220 | end try 221 | try 222 | do shell script "afplay /System/Library/Sounds/Basso.aiff > /dev/null 2>&1 &" 223 | end try 224 | display alert ("CRITICAL “" & (name of me) & "” ERROR: 225 | 226 | Reset Snapshot Has Been Lost 227 | 228 | " & resetSnapshotLostReason & " 229 | 230 | This Mac CANNOT BE SOLD since it cannot be reset.") message (" 231 | Reset Snapshot Name: " & resetSnapshotName & " 232 | 233 | This should not have happened, please inform and deliver this Mac to Free Geek I.T. for further research.") buttons {"Shut Down"} default button 1 as critical 234 | 235 | set shouldShutDownAfterError to true 236 | end try 237 | 238 | try 239 | doShellScriptAsAdmin("rm -rf '/Users/Shared/.fg-snapshot-preserver/.launchedSnapshotHelper'") 240 | end try 241 | 242 | if (shouldShutDownAfterError) then 243 | tell application id "com.apple.systemevents" to shut down with state saving preference 244 | quit 245 | delay 10 246 | end if 247 | end try 248 | else 249 | try 250 | (("/Users/Shared/.fg-snapshot-preserver" as POSIX file) as alias) 251 | 252 | try 253 | doShellScriptAsAdmin("echo \"$(date '+%D %T') Snapshot Helper: NOT RUNNING Because Not Logged In or Not Installed in Correct Location\" >> '/Users/Shared/.fg-snapshot-preserver/log.txt'") 254 | end try 255 | on error 256 | try 257 | activate 258 | end try 259 | display alert "Cannot Run “" & (name of me) & "”" message "“" & (name of me) & "” must be installed at 260 | “/Users/" & demoUsername & "/Applications/” and run from the “" & demoUsername & "” user account." buttons {"Quit"} default button 1 as critical 261 | end try 262 | end if 263 | 264 | on doShellScriptAsAdmin(command) 265 | -- "do shell script with administrator privileges" caches authentication for 5 minutes: https://developer.apple.com/library/archive/technotes/tn2065/_index.html#//apple_ref/doc/uid/DTS10003093-CH1-TNTAG1-HOW_DO_I_GET_ADMINISTRATOR_PRIVILEGES_FOR_A_COMMAND_ & https://developer.apple.com/library/archive/releasenotes/AppleScript/RN-AppleScript/RN-10_4/RN-10_4.html#//apple_ref/doc/uid/TP40000982-CH104-SW10 266 | -- And, it takes reasonably longer to run "do shell script with administrator privileges" when credentials are passed vs without. 267 | -- In testing, 100 iteration with credentials took about 30 seconds while 100 interations without credentials after authenticated in advance took only 2 seconds. 268 | -- So, this function makes it easy to call "do shell script with administrator privileges" while only passing credentials when needed. 269 | -- Also, from testing, this 5 minute credential caching DOES NOT seem to be affected by any custom "sudo" timeout set in the sudoers file. 270 | -- And, from testing, unlike "sudo" the timeout DOES NOT keep extending from the last "do shell script with administrator privileges" without credentials but only from the last time credentials were passed. 271 | -- To be safe, "do shell script with administrator privileges" will be re-authenticated with the credentials every 4.5 minutes. 272 | -- NOTICE: "do shell script" calls are intentionally NOT in "try" blocks since detecting and catching those errors may be critical to the code calling the "doShellScriptAsAdmin" function. 273 | 274 | set currentDate to (current date) 275 | if ((lastDoShellScriptAsAdminAuthDate is equal to 0) or (currentDate ≥ (lastDoShellScriptAsAdminAuthDate + 270))) then -- 270 seconds = 4.5 minutes. 276 | set commandOutput to (do shell script command user name adminUsername password adminPassword with administrator privileges) 277 | set lastDoShellScriptAsAdminAuthDate to currentDate -- Set lastDoShellScriptAsAdminAuthDate to date *BEFORE* command was run since the command itself could have updated the date and the 5 minute timeout started when the command started, not when it finished. 278 | else 279 | set commandOutput to (do shell script command with prompt "This “" & (name of me) & "” password prompt should not have been displayed. 280 | 281 | Please inform Free Geek I.T. that you saw this password prompt. 282 | 283 | You can just press “Cancel” below to continue." with administrator privileges) 284 | end if 285 | 286 | return commandOutput 287 | end doShellScriptAsAdmin 288 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # macOS Testing and Deployment Scripts 2 | 3 | These are the scripts used by Free Geek to test and deploy macOS. 4 | -------------------------------------------------------------------------------- /Testing Scripts/Camera Test/Source/Camera Test.applescript: -------------------------------------------------------------------------------- 1 | -- By: Pico Mitchell 2 | -- For: MacLand @ Free Geek 3 | -- 4 | -- MIT License 5 | -- 6 | -- Copyright (c) 2021 Free Geek 7 | -- 8 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 9 | -- to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | -- and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 11 | -- 12 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | -- 14 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | -- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -- 18 | 19 | -- Version: 2023.7.7-3 20 | 21 | -- App Icon is “Movie Camera” from Twemoji (https://twemoji.twitter.com/) by Twitter (https://twitter.com) 22 | -- Licensed under CC-BY 4.0 (https://creativecommons.org/licenses/by/4.0/) 23 | 24 | use AppleScript version "2.7" 25 | use scripting additions 26 | 27 | repeat -- dialogs timeout when screen is asleep or locked (just in case) 28 | set isAwake to true 29 | try 30 | set isAwake to ((run script "ObjC.import('CoreGraphics'); $.CGDisplayIsActive($.CGMainDisplayID())" in "JavaScript") is equal to 1) 31 | end try 32 | 33 | set isUnlocked to true 34 | try 35 | set isUnlocked to ((do shell script ("bash -c " & (quoted form of "/usr/libexec/PlistBuddy -c 'Print :IOConsoleUsers:0:CGSSessionScreenIsLocked' /dev/stdin <<< \"$(ioreg -ac IORegistryEntry -k IOConsoleUsers -d 1)\""))) is not equal to "true") 36 | end try 37 | 38 | if (isAwake and isUnlocked) then 39 | exit repeat 40 | else 41 | delay 1 42 | end if 43 | end repeat 44 | 45 | try 46 | set infoPlistPath to ((POSIX path of (path to me)) & "Contents/Info.plist") 47 | ((infoPlistPath as POSIX file) as alias) 48 | 49 | set intendedAppName to "Camera Test" -- Hardcode intended App name because Name or Bundle Identifier changes should not be done lightly or accidentally. 50 | 51 | try 52 | do shell script ("/usr/libexec/PlistBuddy -c 'Print :FGBuiltByMacLandScriptBuilder' " & (quoted form of infoPlistPath)) 53 | ((((POSIX path of (path to me)) & "Contents/MacOS/" & intendedAppName) as POSIX file) as alias) 54 | on error 55 | try 56 | activate 57 | end try 58 | display alert " 59 | “" & (name of me) & "” must be built by the “MacLand Script Builder” script." buttons {"Quit"} default button 1 as critical 60 | quit 61 | delay 10 62 | end try 63 | 64 | set AppleScript's text item delimiters to "-" 65 | set intendedBundleIdentifier to ("org.freegeek." & ((words of intendedAppName) as text)) 66 | set currentBundleIdentifier to ((do shell script ("/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' " & (quoted form of infoPlistPath))) as text) 67 | if (currentBundleIdentifier is not equal to intendedBundleIdentifier) then error "“" & (name of me) & "” does not have the correct Bundle Identifier. 68 | 69 | 70 | Current Bundle Identifier: 71 | " & currentBundleIdentifier & " 72 | 73 | Intended Bundle Identifier: 74 | " & intendedBundleIdentifier 75 | on error checkInfoPlistError 76 | if (checkInfoPlistError does not start with "Can’t make file") then 77 | try 78 | activate 79 | end try 80 | display alert checkInfoPlistError buttons {"Quit"} default button 1 as critical 81 | quit 82 | delay 10 83 | end if 84 | end try 85 | 86 | try 87 | set mainScptPath to ((POSIX path of (path to me)) & "Contents/Resources/Scripts/main.scpt") 88 | ((mainScptPath as POSIX file) as alias) 89 | do shell script "osadecompile " & (quoted form of mainScptPath) 90 | error " 91 | “" & (name of me) & "” must be exported as a Run-Only Script." 92 | on error checkReadOnlyErrorMessage 93 | if ((checkReadOnlyErrorMessage does not contain "errOSASourceNotAvailable") and (checkReadOnlyErrorMessage does not start with "Can’t make file")) then 94 | activate 95 | display alert checkReadOnlyErrorMessage buttons {"Quit"} default button 1 as critical 96 | quit 97 | delay 10 98 | end if 99 | end try 100 | 101 | 102 | set freeGeekUpdaterAppPath to "/Applications/Free Geek Updater.app" 103 | set freeGeekUpdaterIsRunning to false 104 | try 105 | ((freeGeekUpdaterAppPath as POSIX file) as alias) 106 | set freeGeekUpdaterIsRunning to (application freeGeekUpdaterAppPath is running) 107 | end try 108 | 109 | set adminUsername to "Staff" 110 | set adminPassword to "[MACLAND SCRIPT BUILDER WILL REPLACE THIS PLACEHOLDER WITH OBFUSCATED ADMIN PASSWORD]" 111 | 112 | set buildInfoPath to ((POSIX path of (path to shared documents folder)) & "Build Info/") 113 | 114 | try 115 | (((buildInfoPath & ".fgSetupSkipped") as POSIX file) as alias) 116 | 117 | try 118 | do shell script ("mkdir " & (quoted form of buildInfoPath)) 119 | end try 120 | try 121 | set AppleScript's text item delimiters to "-" 122 | do shell script ("touch " & (quoted form of (buildInfoPath & ".fgLaunchAfterSetup-org.freegeek." & ((words of (name of me)) as text)))) user name adminUsername password adminPassword with administrator privileges 123 | end try 124 | 125 | if (not freeGeekUpdaterIsRunning) then 126 | try 127 | -- For some reason, on Big Sur, apps are not opening unless we specify "-n" to "Open a new instance of the application(s) even if one is already running." All scripts have LSMultipleInstancesProhibited to this will not actually ever open a new instance. 128 | do shell script "open -na '/Applications/Test Boot Setup.app'" 129 | end try 130 | end if 131 | 132 | quit 133 | delay 10 134 | end try 135 | 136 | if (freeGeekUpdaterIsRunning) then -- Quit if Updater is running so that this app can be updated if needed. 137 | quit 138 | delay 10 139 | end if 140 | 141 | 142 | set systemVersion to (system version of (system info)) 143 | considering numeric strings 144 | set isMojaveOrNewer to (systemVersion ≥ "10.14") 145 | set isCatalinaOrNewer to (systemVersion ≥ "10.15") 146 | end considering 147 | 148 | if (isMojaveOrNewer) then 149 | try 150 | tell application id "com.apple.QuickTimePlayerX" to every window -- To prompt for Automation access on Mojave 151 | on error automationAccessErrorMessage number automationAccessErrorNumber 152 | if (automationAccessErrorNumber is equal to -1743) then 153 | try 154 | tell application id "com.apple.systempreferences" to activate 155 | end try 156 | try 157 | open location "x-apple.systempreferences:com.apple.preference.security?Privacy_Automation" -- The "Privacy_Automation" anchor is not exposed/accessible via AppleScript, but can be accessed via URL Scheme. 158 | end try 159 | try 160 | activate 161 | end try 162 | try 163 | display dialog "“" & (name of me) & "” must be allowed to control and perform actions in “QuickTime Player” to be able to function. 164 | 165 | 166 | USE THE FOLLOWING STEPS TO FIX THIS ISSUE: 167 | 168 | • Open the “System Preferences” application. 169 | 170 | • Click the “Security & Privacy” preference pane. 171 | 172 | • Select the “Privacy” tab. 173 | 174 | • Select “Automation” in the source list on the left. 175 | 176 | • Find “" & (name of me) & "” in the list on the right and turn on the “QuickTime Player” checkbox underneath it. 177 | 178 | • Relaunch “" & (name of me) & "” (using the button below)." buttons {"Quit", "Relaunch “" & (name of me) & "”"} cancel button 1 default button 2 with title (name of me) with icon caution 179 | try 180 | do shell script "osascript -e 'delay 0.5' -e 'repeat while (application \"" & (POSIX path of (path to me)) & "\" is running)' -e 'delay 0.5' -e 'end repeat' -e 'do shell script \"open -na \\\"" & (POSIX path of (path to me)) & "\\\"\"' > /dev/null 2>&1 &" 181 | end try 182 | end try 183 | quit 184 | delay 10 185 | end if 186 | end try 187 | try 188 | with timeout of 1 second 189 | tell application id "com.apple.QuickTimePlayerX" to quit 190 | end timeout 191 | end try 192 | end if 193 | 194 | 195 | set cameraTestDuration to 10 196 | set testCount to 0 197 | 198 | try 199 | repeat 200 | set cameraTestButtons to {"Quit", "Test Camera"} 201 | if (testCount ≥ 1) then set cameraTestButtons to {"Test Camera Again", "Done"} 202 | 203 | set shouldTestCamera to false 204 | try 205 | activate 206 | end try 207 | try 208 | display dialog " 🎥 Camera Test will open a camera feed in 209 | full screen and keep it open for " & cameraTestDuration & " seconds. 210 | 211 | 👉 YOU DO NOT NEED TO RECORD THE VIDEO! 212 | 213 | 👋 Wave your hands in front of the camera to 214 | make sure the camera feed updates properly. 215 | 216 | 👀 Look around the entire image to make sure 217 | the camera feed is crisp, clear, and bright. 218 | Also, make sure there is no darkness around 219 | the edge as well as no spots or artifacts. 220 | 221 | ⏱ After " & cameraTestDuration & " seconds, the camera feed will 222 | be closed and this window will open again. 223 | 224 | 225 | ✅ CAMERA TEST PASSED IF: 226 | ⁃ The image is crisp, clear, and bright. 227 | ⁃ There is no dark edge around the image. 228 | ⁃ There are no spots or artifacts in the image. 229 | 230 | ❌ CAMERA TEST FAILED IF: 231 | ⁃ The image is blurry or dim. 232 | ⁃ There is a dark edge around the image. 233 | ⁃ There are any spots or artifacts in the image. 234 | 235 | 236 | 👉 CONSULT INSTRUCTOR IF CAMERA TEST FAILS ‼️" buttons cameraTestButtons cancel button 1 default button 2 with title "Camera Test" 237 | if ((last text item of cameraTestButtons) is equal to "Test Camera") then set shouldTestCamera to true 238 | on error 239 | if ((first text item of cameraTestButtons) is equal to "Test Camera Again") then set shouldTestCamera to true 240 | end try 241 | 242 | if (shouldTestCamera) then 243 | tell application id "com.apple.QuickTimePlayerX" 244 | try 245 | activate 246 | end try 247 | delay 1 248 | try 249 | close every window without saving 250 | end try 251 | set newMovieRecording to new movie recording 252 | delay 1 253 | try 254 | if (newMovieRecording is not presenting) then present newMovieRecording 255 | end try 256 | try 257 | activate 258 | end try 259 | end tell 260 | repeat cameraTestDuration times 261 | if (application id "com.apple.QuickTimePlayerX" is not running) then exit repeat 262 | delay 1 263 | end repeat 264 | if (application id "com.apple.QuickTimePlayerX" is running) then 265 | tell application id "com.apple.QuickTimePlayerX" 266 | try 267 | stop newMovieRecording 268 | end try 269 | delay 1 270 | try 271 | close every window without saving 272 | end try 273 | delay 1 274 | try 275 | quit 276 | end try 277 | end tell 278 | end if 279 | set testCount to (testCount + 1) 280 | else 281 | exit repeat 282 | end if 283 | end repeat 284 | end try 285 | 286 | if (testCount ≥ 1) then 287 | try 288 | (("/Applications/Screen Test.app" as POSIX file) as alias) 289 | if (application id ("org.freegeek." & "Screen-Test") is not running) then -- Break up App ID or else build will fail if not found during compilation when app is not installed. 290 | try 291 | activate 292 | end try 293 | display alert " 294 | Would you like to launch “Screen Test”?" buttons {"No", "Yes"} cancel button 1 default button 2 giving up after 15 295 | do shell script "open -na '/Applications/Screen Test.app'" 296 | end if 297 | end try 298 | end if 299 | -------------------------------------------------------------------------------- /Testing Scripts/Hard Drive Test/Source/Extract Info from DriveDx Report.applescript: -------------------------------------------------------------------------------- 1 | -- By: Pico Mitchell 2 | -- For: MacLand @ Free Geek 3 | -- 4 | -- MIT License 5 | -- 6 | -- Copyright (c) 2021 Free Geek 7 | -- 8 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 9 | -- to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | -- and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 11 | -- 12 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | -- 14 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | -- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -- 18 | 19 | -- This is just a proof-of-concept for a possible future applet. 20 | 21 | set driveDxReport to read "/Users/Shared/DriveDxReport.txt" 22 | 23 | set AppleScript's text item delimiters to " ###" 24 | repeat with thisDriveDxReportSection in (every text item of driveDxReport) 25 | set thisAdvancedSmartStatus to "UNKNOWN" 26 | set thisOverallHealthRating to "UNKNOWN" 27 | set thisOverallPerformanceRating to "UNKNOWN" 28 | set thisSSDLifetimeLeftIndicator to "UNKNOWN" 29 | set thisIssuesFound to "UNKNOWN" 30 | set thisSerialNumber to "UNKNOWN" 31 | repeat with thisDriveDxReportSectionLine in (paragraphs of thisDriveDxReportSection) 32 | if (((offset of "### " in thisDriveDxReportSectionLine) is equal to 1) or ((offset of "Report Timestamp" in thisDriveDxReportSectionLine) is equal to 1)) then exit repeat 33 | if ((length of thisDriveDxReportSectionLine) is not equal to 0) then 34 | if ((offset of "Advanced SMART Status" in thisDriveDxReportSectionLine) is equal to 1) then set thisAdvancedSmartStatus to (text 40 thru -1 of thisDriveDxReportSectionLine) 35 | if ((offset of "Overall Health Rating" in thisDriveDxReportSectionLine) is equal to 1) then set thisOverallHealthRating to (text 40 thru -1 of thisDriveDxReportSectionLine) 36 | if ((offset of "Overall Performance Rating" in thisDriveDxReportSectionLine) is equal to 1) then set thisOverallPerformanceRating to (text 40 thru -1 of thisDriveDxReportSectionLine) 37 | if ((offset of "SSD Lifetime Left Indicator" in thisDriveDxReportSectionLine) is equal to 1) then set thisSSDLifetimeLeftIndicator to (text 40 thru -1 of thisDriveDxReportSectionLine) 38 | if ((offset of "Issues found" in thisDriveDxReportSectionLine) is equal to 1) then set thisIssuesFound to (text 40 thru -1 of thisDriveDxReportSectionLine) 39 | if ((offset of "Serial Number" in thisDriveDxReportSectionLine) is equal to 1) then 40 | set thisSerialNumber to (text 40 thru -1 of thisDriveDxReportSectionLine) 41 | exit repeat -- Serial Number is the last row we care about 42 | end if 43 | end if 44 | end repeat 45 | if ((thisSerialNumber is not equal to "UNKNOWN") and (thisAdvancedSmartStatus is not equal to "UNKNOWN") and (thisOverallHealthRating is not equal to "UNKNOWN") and (thisIssuesFound is not equal to "UNKNOWN")) then 46 | log "thisSerialNumber: " & thisSerialNumber 47 | log "thisAdvancedSmartStatus: " & thisAdvancedSmartStatus 48 | log "thisOverallHealthRating: " & thisOverallHealthRating 49 | if (thisOverallPerformanceRating is not equal to "UNKNOWN") then log "thisOverallPerformanceRating: " & thisOverallPerformanceRating 50 | if (thisSSDLifetimeLeftIndicator is not equal to "UNKNOWN") then log "thisSSDLifetimeLeftIndicator: " & thisSSDLifetimeLeftIndicator 51 | log "thisIssuesFound: " & thisIssuesFound 52 | log "-----" 53 | end if 54 | end repeat -------------------------------------------------------------------------------- /Testing Scripts/Keyboard Test/Source/build_keyboard_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck enable=add-default-case,avoid-nullary-conditions,check-unassigned-uppercase,deprecate-which,quote-safe-variables,require-double-brackets 3 | 4 | # By: Pico Mitchell 5 | # For: MacLand @ Free Geek 6 | # Last Updated: 02/16/23 7 | # 8 | # MIT License 9 | # 10 | # Copyright (c) 2021 Free Geek 11 | # 12 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 13 | # to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 14 | # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | # 22 | 23 | # This script required Nativefier (https://github.com/jiahaog/nativefier) which is a Node.js package. 24 | # First, install Node.js: https://nodejs.org/en/download/ 25 | # Then, install Nativifier by running the following command in a Terminal window: sudo npm install -g nativefier 26 | # Finally, run this script by drag-and-dropping it into a Terminal window. 27 | 28 | PATH='/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin' # PATH must include "/usr/local/bin" for npm (and node) and nativefier. 29 | 30 | PROJECT_PATH="$(cd "${BASH_SOURCE[0]%/*}" &> /dev/null && pwd -P)" 31 | readonly PROJECT_PATH 32 | readonly BUILD_DIR="${PROJECT_PATH}/.." 33 | readonly ZIPS_FOR_AUTO_UPDATE_PATH="${BUILD_DIR}/../../ZIPs for Auto-Update" 34 | readonly fgMIB_USERAPPS_PATH="${BUILD_DIR}/../../fgMIB Resources/Prepare OS Package/Package Resources/User/fg-demo/Apps/darwin-all-versions" 35 | 36 | readonly KEYBOARD_TESTER_URL='https://www.keyboardtester.com/tester.html' 37 | 38 | readonly APP_BUILD='1' # REMEMBER TO RESET THIS TO "1" IF CHANGED FROM PREVIOUS BUILD! 39 | APP_VERSION="$(date '+%Y.%-m.%-d')-${APP_BUILD}" # https://strftime.org 40 | readonly APP_VERSION 41 | 42 | 43 | echo -e '\nUPDATING NATIVEFIER (ADMIN PASSWORD REQUIRED)...' 44 | sudo npm install -g nativefier 45 | 46 | 47 | echo -e "\n\nBUILDING KEYBOARD TEST APP (VERSION ${APP_VERSION}) WITH NATIVEFIER..." 48 | 49 | rm -rf "${BUILD_DIR}/Keyboard Test-darwin-universal" "${BUILD_DIR}/Keyboard Test-darwin-x64" "${BUILD_DIR}/Keyboard Test.app" "${BUILD_DIR}/Keyboard-Test.zip" "${HOME}/Library/Application Support/keyboard-test"* 50 | # Any existing Application Support files are also being deleted since that stores the last window size which we don't want to remember if it's getting changed. 51 | 52 | nativefier \ 53 | "${KEYBOARD_TESTER_URL}" \ 54 | "${BUILD_DIR}" \ 55 | --name 'Keyboard Test' \ 56 | --arch 'universal' \ 57 | --app-version "${APP_VERSION}" \ 58 | --app-copyright '© KeyboardTester.com 59 | 60 | Modifications by Pico Mitchell for Free Geek 61 | 62 | App Wrapper Built with Nativefier 63 | 64 | App Icon is “Keyboard” from Twemoji by Twitter licensed under CC-BY 4.0' \ 65 | --icon "${PROJECT_PATH}/Keyboard Test Icon/Twemoji Keyboard.icns" \ 66 | --internal-urls "${KEYBOARD_TESTER_URL//./\\.}" \ 67 | --strict-internal-urls \ 68 | --inject "${PROJECT_PATH}/keyboard_test_modifications.js" \ 69 | --inject "${PROJECT_PATH}/keyboard_test_modifications.css" \ 70 | --min-width 939 \ 71 | --min-height 490 \ 72 | --max-width 939 \ 73 | --max-height 490 \ 74 | --disable-dev-tools \ 75 | --disable-gpu \ 76 | --disable-context-menu \ 77 | --darwin-dark-mode-support \ 78 | --fast-quit \ 79 | --single-instance \ 80 | --disable-old-build-warning-yesiknowitisinsecure # Do not ever want the Keyboard Test app to prompt "Old build detected" since it really doesn't matter for this kind of app. 81 | 82 | echo -e '\n\nMODIFYING KEYBOARD TEST APP Info.plist & MOVING INTO BUILD DIR...' 83 | 84 | app_info_plist_path="${BUILD_DIR}/Keyboard Test-darwin-universal/Keyboard Test.app/Contents/Info.plist" 85 | 86 | plutil -remove 'CFBundleVersion' "${app_info_plist_path}" 87 | 88 | plutil -replace 'LSMinimumSystemVersion' -string '10.13' "${app_info_plist_path}" 89 | 90 | plutil -replace 'LSMultipleInstancesProhibited' -bool 'true' "${app_info_plist_path}" 91 | 92 | mv "${BUILD_DIR}/Keyboard Test-darwin-universal/Keyboard Test.app/Contents/Resources/electron.icns" "${BUILD_DIR}/Keyboard Test-darwin-universal/Keyboard Test.app/Contents/Resources/Keyboard Test.icns" 93 | plutil -replace 'CFBundleIconFile' -string 'Keyboard Test' "${app_info_plist_path}" 94 | 95 | plutil -replace 'CFBundleIdentifier' -string 'org.freegeek.Keyboard-Test' "${app_info_plist_path}" 96 | 97 | rm -rf "${BUILD_DIR}/Keyboard Test.app" 98 | mv -f "${BUILD_DIR}/Keyboard Test-darwin-universal/Keyboard Test.app" "${BUILD_DIR}/Keyboard Test.app" 99 | rm -rf "${BUILD_DIR}/Keyboard Test-darwin-universal" 100 | 101 | 102 | echo -e '\n\nCODE SIGNING KEYBOARD TEST APP...' 103 | codesign -fs 'Developer ID Application' --deep --strict "${BUILD_DIR}/Keyboard Test.app" 104 | 105 | 106 | echo -e '\n\nZIPPING KEYBOARD TEST APP & UPDATING VERSION IN latest-versions.txt...' 107 | 108 | rm -f "${BUILD_DIR}/Keyboard-Test.zip" 109 | rm -f "${ZIPS_FOR_AUTO_UPDATE_PATH}/Keyboard-Test.zip" 110 | ditto -ck --keepParent --sequesterRsrc --zlibCompressionLevel 9 "${BUILD_DIR}/Keyboard Test.app" "${ZIPS_FOR_AUTO_UPDATE_PATH}/Keyboard-Test.zip" 111 | 112 | mkdir -p "${fgMIB_USERAPPS_PATH}" 113 | rm -f "${fgMIB_USERAPPS_PATH}/Keyboard-Test.zip" 114 | ditto "${ZIPS_FOR_AUTO_UPDATE_PATH}/Keyboard-Test.zip" "${fgMIB_USERAPPS_PATH}/Keyboard-Test.zip" 115 | 116 | if grep -qF 'Keyboard Test:' "${ZIPS_FOR_AUTO_UPDATE_PATH}/latest-versions.txt"; then 117 | sed -i '' "s/Keyboard Test: .*/Keyboard Test: ${APP_VERSION}/" "${ZIPS_FOR_AUTO_UPDATE_PATH}/latest-versions.txt" 118 | else 119 | echo "Keyboard Test: ${APP_VERSION}" >> "${ZIPS_FOR_AUTO_UPDATE_PATH}/latest-versions.txt" 120 | fi 121 | 122 | echo -e '\n\nDONE!\n' 123 | -------------------------------------------------------------------------------- /Testing Scripts/Keyboard Test/Source/keyboard_test_modifications.css: -------------------------------------------------------------------------------- 1 | /* 2 | * By: Pico Mitchell 3 | * For: MacLand @ Free Geek 4 | * Last Updated: September 8th, 2022 5 | * 6 | * MIT License 7 | * 8 | * Copyright (c) 2021 Free Geek 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 11 | * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | * 20 | */ 21 | 22 | body { 23 | background-image: none !important; 24 | background-color: white; 25 | overflow: hidden; 26 | display: none; 27 | } 28 | 29 | #logo { 30 | margin: 10px 0px 6px 0px; 31 | text-align: center; 32 | } 33 | 34 | #logo a, #logo a:hover, #logo a:visited { 35 | text-decoration: none; 36 | color:#0d2693; 37 | font-size: 23px; 38 | } 39 | 40 | #resetbutton { 41 | padding: 0px !important; 42 | } 43 | 44 | #testarea { 45 | font-size: 16px; 46 | font-family: Arial, sans-serif; 47 | resize: none; 48 | display: block; 49 | margin: 0px auto 11px auto; 50 | padding: 5px; 51 | width: 877px; 52 | height: 123px; 53 | } 54 | 55 | #testarea::placeholder { 56 | text-align: center; 57 | font-weight: bold; 58 | font-size: 15px; 59 | } 60 | 61 | #hidden_text_before { 62 | width: 1px; 63 | height: 1px; 64 | border: 0px; 65 | } 66 | 67 | #soundArea, input[name="reset"], #key44, #key145, #key19, #key45, #key36, #key33, #key46, #key35, #key34, #key144, #key111, #key106, #key109b, #key103, #key104, #key105, #key107b, #key100, #key101, #key102, #key97, #key98, #key99, #key13b, #key96, #key110, #key224, #key224b, #key92, #key17b, .label, #notes { 68 | display: none; 69 | } 70 | 71 | #keyboard { 72 | padding-bottom: 100px; /* Push Ad Banner Down Below Window Height */ 73 | background-color: white; 74 | } 75 | 76 | table { 77 | margin: 0px auto; 78 | } 79 | 80 | td { 81 | vertical-align: bottom !important; 82 | padding: 0px !important; 83 | } 84 | 85 | .key_block td, .key_row div { /* All Keys */ 86 | font-size: 16px; 87 | font-family: Arial, sans-serif !important; 88 | border-radius: 6px; 89 | padding: 6px 8px !important; 90 | min-width: 22px; 91 | text-align: center; 92 | background-color: white; 93 | } 94 | 95 | .key_highlight { 96 | background-color: #FFC864 !important; 97 | } 98 | 99 | .key_pressed, .key_pressed_m { 100 | border-color: green !important; 101 | background-color: #2CB32C !important; 102 | color: white; 103 | } 104 | 105 | .key_small div { /* Function Keys */ 106 | font-size: 12px; 107 | min-width: 29px; 108 | } 109 | 110 | #key8, #key9 { /* Delete, Tab */ 111 | min-width: 63px; 112 | } 113 | 114 | #key20 { /* Caps */ 115 | min-width: 68px; 116 | } 117 | 118 | #key13 { /* Return */ 119 | min-width: 67px; 120 | } 121 | 122 | #key16 { /* Left Shift */ 123 | min-width: 92px; 124 | } 125 | 126 | #key16b { /* Right Shift */ 127 | min-width: 93px; 128 | } 129 | 130 | #key32 { /* Spacebar */ 131 | min-width: 297px; 132 | } 133 | 134 | table.key_block { /* Arrow Keys */ 135 | margin: 0px -4px -4px 0px; 136 | } 137 | 138 | .key_block td { /* Arrow Keys */ 139 | font-size: 11px; 140 | vertical-align: middle !important; 141 | } 142 | 143 | #key38, #key40 { /* Up Arrow, Down Arrow */ 144 | font-size: 12px; 145 | padding: 8px !important; 146 | } 147 | 148 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { 149 | #testarea { 150 | padding: 10px; 151 | width: 867px; 152 | height: 105px; 153 | } 154 | 155 | #key32 { /* Spacebar */ 156 | min-width: 297.5px; 157 | } 158 | 159 | #key38, #key40 { /* Up Arrow, Down Arrow */ 160 | font-size: 12.5px; 161 | } 162 | } -------------------------------------------------------------------------------- /Testing Scripts/Keyboard Test/Source/keyboard_test_modifications.js: -------------------------------------------------------------------------------- 1 | /* 2 | * By: Pico Mitchell 3 | * For: MacLand @ Free Geek 4 | * Last Updated: September 8th, 2022 5 | * 6 | * MIT License 7 | * 8 | * Copyright (c) 2021 Free Geek 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 11 | * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | * 20 | */ 21 | 22 | try { 23 | document.title = 'Keyboard Test' 24 | 25 | window.addEventListener('scroll', function() { window.scrollTo(0, 0) }) 26 | 27 | const resetButton = document.getElementById('resetbutton') 28 | resetButton.outerHTML = '' + resetButton.outerHTML 29 | 30 | document.getElementById('soundSelect').selectedIndex = 0 31 | 32 | const testTextarea = document.getElementById('testarea') 33 | testTextarea.placeholder = `🏆 The best way to test a keyboard is to TYPE ACTUAL WORDS and make sure that exactly what you typed 34 | shows up in this text box and that each key below this text box turns green as you type it. 35 | 36 | When a key is pressed on the keyboard, it will momentarily turn ✴️ orange and then turn ❇️ green in this window. 37 | 38 | 👇 SCROLL DOWN FOR MORE KEYBOARD TESTING TIPS 👇 39 | 40 | 🚫 You SHOULD NOT just slide your finger across the keyboard to hit every key. 41 | With water damaged keyboards, it’s common that one key on the keyboard may trigger the wrong key, or multiple keys. 42 | Also, modifier keys such as Shift, Control, or Option could be stuck down which can make other keys behave incorrectly. 43 | 44 | ⚠️ DO NOT just press the Shift, Option, and Caps Lock keys by themselves. 45 | Type while using these keys to make sure they are working properly. 46 | 47 | ⚠️ Make sure to test both the left and right Shift and Option keys since this test cannot tell which side is being pressed. 48 | 49 | ⚠️ The Caps Lock key WILL NOT turn green when being turned ON, but WILL turn green when turned OFF. 50 | 51 | ⚠️ The Tab key WILL turn green but WILL NOT output a tab character. 52 | 53 | ☝️ Hold down the FN key to test the top row of Function keys. 54 | 55 | 👉 Also, make sure that no keys feel funky, sticky, or get stuck down as you type.` 56 | 57 | testTextarea.outerHTML = '' + testTextarea.outerHTML 58 | document.getElementById('testarea').addEventListener('blur', function(thisEvent) { thisEvent.target.focus() }) 59 | 60 | const leftOptionKey = document.getElementById('key18') 61 | const leftCommandKey = document.getElementById('key91') 62 | const rightOptionKey = document.getElementById('key18b') 63 | const rightCommandKey = document.getElementById('key93') 64 | 65 | leftOptionKey.parentNode.insertBefore(leftOptionKey, leftCommandKey) 66 | rightCommandKey.parentNode.insertBefore(rightCommandKey, rightOptionKey) 67 | 68 | // Correct all incorrect displayed key chars and make all modifiers lowercase. 69 | leftOptionKey.innerHTML = rightOptionKey.innerHTML = 'option' 70 | leftCommandKey.innerHTML = rightCommandKey.innerHTML = 'command' 71 | document.getElementById('key192').innerHTML = '`' 72 | document.getElementById('key27').innerHTML = 'esc' 73 | document.getElementById('key61').innerHTML = '=' 74 | document.getElementById('key8').innerHTML = 'delete' 75 | document.getElementById('key9').innerHTML = 'tab' 76 | document.getElementById('key219').innerHTML = '[' 77 | document.getElementById('key221').innerHTML = ']' 78 | document.getElementById('key220').innerHTML = '\\' // To remove extra space for padding that we don't want. 79 | document.getElementById('key20').innerHTML = 'caps' 80 | document.getElementById('key13').innerHTML = 'return' 81 | document.getElementById('key16').innerHTML = document.getElementById('key16b').innerHTML = 'shift' 82 | document.getElementById('key17').innerHTML = 'control' 83 | document.getElementById('key32').innerHTML = 'space' 84 | 85 | document.getElementById('key38').innerHTML = '▲' 86 | document.getElementById('key37').innerHTML = '◀' 87 | document.getElementById('key40').innerHTML = '▼' 88 | document.getElementById('key39').innerHTML = '▶' 89 | 90 | document.getElementById('key144').parentElement.parentElement.parentElement.parentElement.remove() // Remove the entire TD that contains the numpad so that it's not taking up width (even though all the keys have already been hidden by the CSS). 91 | 92 | document.body.style.display = 'block' 93 | 94 | setTimeout(function() { checkEveryKeyPressed() }, 1000) 95 | } catch (loadError) { 96 | const errorAlertReply = confirm('\t‼️ AN ERROR OCCURRED ‼️\n\nMake sure you\'re connected to the internet.\n\n\tClick OK to Reload and Try Again\n\n\tClick Cancel to Quit and Open\n\tKeyboardTester.com in Safari') 97 | if (errorAlertReply) { 98 | document.body.style.display = 'none' 99 | location.reload() 100 | } else { 101 | window.open('https://keyboardtester.com/tester.html') // Exclude www so it opens in browser. 102 | window.close() 103 | } 104 | } 105 | 106 | function checkEveryKeyPressed() { 107 | const everyKey = ['key27', 'key112', 'key113', 'key114', 'key115', 'key116', 'key117', 'key118', 'key119', 'key120', 'key121', 'key122', 'key123', 'key192', 'key49', 'key50', 'key51', 'key52', 'key53', 'key54', 'key55', 'key56', 'key57', 'key48', 'key173', 'key61', 'key8', 'key9', 'key81', 'key87', 'key69', 'key82', 'key84', 'key89', 'key85', 'key73', 'key79', 'key80', 'key219', 'key221', 'key220', 'key20', 'key65', 'key83', 'key68', 'key70', 'key71', 'key72', 'key74', 'key75', 'key76', 'keycolon', 'key222', 'key13', 'key16', 'key90', 'key88', 'key67', 'key86', 'key66', 'key78', 'key77', 'key188', 'key190', 'key191', 'key16b', 'key91', 'key18', 'key32', 'key18b', 'key93', 'key17b', 'key38', 'key37', 'key40', 'key39'] 108 | 109 | let everyKeyIsPressed = true 110 | for (let i = 0; i < everyKey.length; i ++) 111 | if (document.getElementById(everyKey[i]).className == 'key_un') { 112 | everyKeyIsPressed = false 113 | break 114 | } 115 | 116 | if (everyKeyIsPressed) { 117 | setTimeout(function() { 118 | const everyKeyAlertReply = confirm('🎉\tEVERY KEY WAS PRESSED!\n\n\n✅\tKEYBOARD TEST PASSED IF:\n\t⁃ Every key functioned correctly.\n\t⁃ No keys felt funky in any way.\n\t⁃ No keys felt sticky or got stuck down.\n\t⁃ No key caps are broken or missing.\n\n❌\tKEYBOARD TEST FAILED IF:\n\t⁃ Any key did not function correctly.\n\t⁃ Any key triggered the wrong key.\n\t⁃ Any key triggered multiple keys.\n\t⁃ Any key felt funky in any way.\n\t⁃ Any key felt sticky or got stuck down.\n\t⁃ Any key caps are broken or missing.\n\n\t👉\tCONSULT AN INSTRUCTOR\n\t\tIF KEYBOARD TEST FAILED ‼️\n\n\nIf Keyboard Test passed, quit and continue testing this Mac.\n\n👋\tClick OK to Quit Keyboard Test\n\n🔄\tClick Cancel to Redo Keyboard Test') 119 | if (everyKeyAlertReply) 120 | window.close() 121 | else { 122 | document.body.style.display = 'none' 123 | location.reload() 124 | } 125 | }, 500) 126 | } 127 | else 128 | setTimeout(function() { checkEveryKeyPressed() }, 1000) 129 | } 130 | -------------------------------------------------------------------------------- /Testing Scripts/Screen Test/Source/Screen Test.applescript: -------------------------------------------------------------------------------- 1 | -- By: Pico Mitchell 2 | -- For: MacLand @ Free Geek 3 | -- 4 | -- MIT License 5 | -- 6 | -- Copyright (c) 2021 Free Geek 7 | -- 8 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 9 | -- to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | -- and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 11 | -- 12 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | -- 14 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | -- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -- 18 | 19 | -- Version: 2023.7.7-3 20 | 21 | -- App Icon is “Mauritian Flag” from Twemoji (https://twemoji.twitter.com/) by Twitter (https://twitter.com) 22 | -- Licensed under CC-BY 4.0 (https://creativecommons.org/licenses/by/4.0/) 23 | 24 | use AppleScript version "2.7" 25 | use scripting additions 26 | 27 | repeat -- dialogs timeout when screen is asleep or locked (just in case) 28 | set isAwake to true 29 | try 30 | set isAwake to ((run script "ObjC.import('CoreGraphics'); $.CGDisplayIsActive($.CGMainDisplayID())" in "JavaScript") is equal to 1) 31 | end try 32 | 33 | set isUnlocked to true 34 | try 35 | set isUnlocked to ((do shell script ("bash -c " & (quoted form of "/usr/libexec/PlistBuddy -c 'Print :IOConsoleUsers:0:CGSSessionScreenIsLocked' /dev/stdin <<< \"$(ioreg -ac IORegistryEntry -k IOConsoleUsers -d 1)\""))) is not equal to "true") 36 | end try 37 | 38 | if (isAwake and isUnlocked) then 39 | exit repeat 40 | else 41 | delay 1 42 | end if 43 | end repeat 44 | 45 | 46 | try 47 | set infoPlistPath to ((POSIX path of (path to me)) & "Contents/Info.plist") 48 | ((infoPlistPath as POSIX file) as alias) 49 | 50 | set intendedAppName to "Screen Test" -- Hardcode intended App name because Name or Bundle Identifier changes should not be done lightly or accidentally. 51 | 52 | try 53 | do shell script ("/usr/libexec/PlistBuddy -c 'Print :FGBuiltByMacLandScriptBuilder' " & (quoted form of infoPlistPath)) 54 | ((((POSIX path of (path to me)) & "Contents/MacOS/" & intendedAppName) as POSIX file) as alias) 55 | on error 56 | try 57 | activate 58 | end try 59 | display alert " 60 | “" & (name of me) & "” must be built by the “MacLand Script Builder” script." buttons {"Quit"} default button 1 as critical 61 | quit 62 | delay 10 63 | end try 64 | 65 | set AppleScript's text item delimiters to "-" 66 | set intendedBundleIdentifier to ("org.freegeek." & ((words of intendedAppName) as text)) 67 | set currentBundleIdentifier to ((do shell script ("/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' " & (quoted form of infoPlistPath))) as text) 68 | if (currentBundleIdentifier is not equal to intendedBundleIdentifier) then error "“" & (name of me) & "” does not have the correct Bundle Identifier. 69 | 70 | 71 | Current Bundle Identifier: 72 | " & currentBundleIdentifier & " 73 | 74 | Intended Bundle Identifier: 75 | " & intendedBundleIdentifier 76 | on error checkInfoPlistError 77 | if (checkInfoPlistError does not start with "Can’t make file") then 78 | try 79 | activate 80 | end try 81 | display alert checkInfoPlistError buttons {"Quit"} default button 1 as critical 82 | quit 83 | delay 10 84 | end if 85 | end try 86 | 87 | try 88 | set mainScptPath to ((POSIX path of (path to me)) & "Contents/Resources/Scripts/main.scpt") 89 | ((mainScptPath as POSIX file) as alias) 90 | do shell script "osadecompile " & (quoted form of mainScptPath) 91 | error " 92 | “" & (name of me) & "” must be exported as a Run-Only Script." 93 | on error checkReadOnlyErrorMessage 94 | if ((checkReadOnlyErrorMessage does not contain "errOSASourceNotAvailable") and (checkReadOnlyErrorMessage does not start with "Can’t make file")) then 95 | activate 96 | display alert checkReadOnlyErrorMessage buttons {"Quit"} default button 1 as critical 97 | quit 98 | delay 10 99 | end if 100 | end try 101 | 102 | 103 | set freeGeekUpdaterAppPath to "/Applications/Free Geek Updater.app" 104 | set freeGeekUpdaterIsRunning to false 105 | try 106 | ((freeGeekUpdaterAppPath as POSIX file) as alias) 107 | set freeGeekUpdaterIsRunning to (application freeGeekUpdaterAppPath is running) 108 | end try 109 | 110 | set adminUsername to "Staff" 111 | set adminPassword to "[MACLAND SCRIPT BUILDER WILL REPLACE THIS PLACEHOLDER WITH OBFUSCATED ADMIN PASSWORD]" 112 | 113 | set buildInfoPath to ((POSIX path of (path to shared documents folder)) & "Build Info/") 114 | 115 | try 116 | (((buildInfoPath & ".fgSetupSkipped") as POSIX file) as alias) 117 | 118 | try 119 | do shell script ("mkdir " & (quoted form of buildInfoPath)) 120 | end try 121 | try 122 | set AppleScript's text item delimiters to "-" 123 | do shell script ("touch " & (quoted form of (buildInfoPath & ".fgLaunchAfterSetup-org.freegeek." & ((words of (name of me)) as text)))) user name adminUsername password adminPassword with administrator privileges 124 | end try 125 | 126 | if (not freeGeekUpdaterIsRunning) then 127 | try 128 | -- For some reason, on Big Sur, apps are not opening unless we specify "-n" to "Open a new instance of the application(s) even if one is already running." All scripts have LSMultipleInstancesProhibited to this will not actually ever open a new instance. 129 | do shell script "open -na '/Applications/Test Boot Setup.app'" 130 | end try 131 | end if 132 | 133 | quit 134 | delay 10 135 | end try 136 | 137 | if (freeGeekUpdaterIsRunning) then -- Quit if Updater is running so that this app can be updated if needed. 138 | quit 139 | delay 10 140 | end if 141 | 142 | 143 | try 144 | (("/Applications/PiXel Check.app" as POSIX file) as alias) 145 | on error 146 | try 147 | activate 148 | end try 149 | display alert "“Screen Test” requires “PiXel Check”" message "“PiXel Check” must be installed in the “Applications” folder." buttons {"Quit", "Download “PiXel Check”"} cancel button 1 default button 2 as critical 150 | open location "http://macguitar.me/apps/pixelcheck/" 151 | quit 152 | delay 10 153 | end try 154 | 155 | 156 | set systemVersion to (system version of (system info)) 157 | considering numeric strings 158 | set isMojaveOrNewer to (systemVersion ≥ "10.14") 159 | set isCatalinaOrNewer to (systemVersion ≥ "10.15") 160 | end considering 161 | 162 | if (isMojaveOrNewer) then 163 | try 164 | tell application id "com.apple.systemevents" to every window -- To prompt for Automation access on Mojave 165 | on error automationAccessErrorMessage number automationAccessErrorNumber 166 | if (automationAccessErrorNumber is equal to -1743) then 167 | try 168 | tell application id "com.apple.systempreferences" to activate 169 | end try 170 | try 171 | open location "x-apple.systempreferences:com.apple.preference.security?Privacy_Automation" -- The "Privacy_Automation" anchor is not exposed/accessible via AppleScript, but can be accessed via URL Scheme. 172 | end try 173 | try 174 | activate 175 | end try 176 | try 177 | display dialog "“" & (name of me) & "” must be allowed to control and perform actions in “System Events” to be able to function. 178 | 179 | 180 | USE THE FOLLOWING STEPS TO FIX THIS ISSUE: 181 | 182 | • Open the “System Preferences” application. 183 | 184 | • Click the “Security & Privacy” preference pane. 185 | 186 | • Select the “Privacy” tab. 187 | 188 | • Select “Automation” in the source list on the left. 189 | 190 | • Find “" & (name of me) & "” in the list on the right and turn on the “System Events” checkbox underneath it. 191 | 192 | • Relaunch “" & (name of me) & "” (using the button below)." buttons {"Quit", "Relaunch “" & (name of me) & "”"} cancel button 1 default button 2 with title (name of me) with icon caution 193 | try 194 | do shell script "osascript -e 'delay 0.5' -e 'repeat while (application \"" & (POSIX path of (path to me)) & "\" is running)' -e 'delay 0.5' -e 'end repeat' -e 'do shell script \"open -na \\\"" & (POSIX path of (path to me)) & "\\\"\"' > /dev/null 2>&1 &" 195 | end try 196 | end try 197 | quit 198 | delay 10 199 | end if 200 | end try 201 | end if 202 | 203 | try 204 | tell application id "com.apple.systemevents" to tell (first application process whose bundle identifier is "com.apple.finder") to (get windows) 205 | on error (assistiveAccessTestErrorMessage) 206 | if ((offset of "not allowed assistive" in assistiveAccessTestErrorMessage) > 0) then 207 | try 208 | tell application id "com.apple.finder" to reveal (path to me) 209 | end try 210 | try 211 | tell application id "com.apple.systempreferences" 212 | try 213 | activate 214 | end try 215 | reveal ((anchor "Privacy_Accessibility") of (pane id "com.apple.preference.security")) 216 | end tell 217 | end try 218 | try 219 | activate 220 | end try 221 | try 222 | display dialog "“" & (name of me) & "” must be allowed to control this computer using Accessibility Features to be able to function. 223 | 224 | 225 | USE THE FOLLOWING STEPS TO FIX THIS ISSUE: 226 | 227 | • Open the “System Preferences” application. 228 | 229 | • Click the “Security & Privacy” preference pane. 230 | 231 | • Select the “Privacy” tab. 232 | 233 | • Select “Accessibility” in the source list on the left. 234 | 235 | • Click the Lock icon at the bottom left of the window, enter the administrator username and password, and then click Unlock. 236 | 237 | • Find “" & (name of me) & "” in the list on the right and turn on the checkbox next to it. If “" & (name of me) & "” IS NOT in the list, drag-and-drop the app icon from Finder into the list. 238 | 239 | • Relaunch “" & (name of me) & "” (using the button below)." buttons {"Quit", "Relaunch “" & (name of me) & "”"} cancel button 1 default button 2 with title (name of me) with icon caution 240 | try 241 | do shell script "osascript -e 'delay 0.5' -e 'repeat while (application \"" & (POSIX path of (path to me)) & "\" is running)' -e 'delay 0.5' -e 'end repeat' -e 'do shell script \"open -na \\\"" & (POSIX path of (path to me)) & "\\\"\"' > /dev/null 2>&1 &" 242 | end try 243 | end try 244 | quit 245 | delay 10 246 | end if 247 | end try 248 | 249 | 250 | set testCount to 0 251 | 252 | try 253 | repeat 254 | set screenTestButtons to {"Quit", "Test Screen"} 255 | if (testCount ≥ 1) then set screenTestButtons to {"Test Screen Again", "Done"} 256 | 257 | set shouldTestScreen to false 258 | try 259 | activate 260 | end try 261 | try 262 | display dialog " 🇲🇺 Screen Test will cycle through solid color 263 | Red, Green, Blue, Black, and White screens. 264 | 265 | 🔴 When you start Screen Test, the whole 266 | screen will first turn solid Red. 267 | 268 | 👀 Examine the entire screen carefully, looking for 269 | any discolorations, dead pixels, or scratches. 270 | 271 | ‼️ MAKE NOTE OF ANY ISSUES YOU FIND! 272 | 273 | 👇 When you’re finished examining a screen color, 274 | CLICK ANY KEY to move to the next color and 275 | repeat your examination and note taking. 276 | 277 | 🔎 Look very carefully over the entire screen for 278 | EACH COLOR as some kinds of screen issues 279 | will show on some colors and not on others. 280 | 281 | 282 | ✅ SCREEN TEST PASSED IF: 283 | ⁃ The screen has NO discolorations or hot spots. 284 | ⁃ The screen has NO dead pixels. 285 | ⁃ The screen has NO scratches or delamination. 286 | 287 | ❌ SCREEN TEST FAILED IF: 288 | ⁃ The screen has ANY discoloration or hot spots. 289 | ⁃ The screen has ANY dead pixels. 290 | ⁃ The screen has ANY scratches or delamination. 291 | 292 | 293 | 👉 CONSULT INSTRUCTOR IF SCREEN TEST FAILS ‼️" buttons screenTestButtons cancel button 1 default button 2 with title "Screen Test" 294 | if ((last text item of screenTestButtons) is equal to "Test Screen") then set shouldTestScreen to true 295 | on error 296 | if ((first text item of screenTestButtons) is equal to "Test Screen Again") then set shouldTestScreen to true 297 | end try 298 | 299 | if (shouldTestScreen) then 300 | repeat 301 | if (application id ("me.macguitar." & "PiXel-Check") is running) then -- Break up App ID or else build will fail if not found during compilation when app is not installed. 302 | try 303 | with timeout of 1 second 304 | tell application id ("me.macguitar." & "PiXel-Check") to quit 305 | end timeout 306 | end try 307 | delay 1 308 | else 309 | exit repeat 310 | end if 311 | end repeat 312 | 313 | try 314 | do shell script "open -a '/Applications/PiXel Check.app'" 315 | end try 316 | 317 | tell application id "com.apple.systemevents" to tell (first application process whose bundle identifier is "me.macguitar.PiXel-Check") 318 | repeat while ((count of windows) < 1) 319 | delay 1 320 | end repeat 321 | repeat with thisWindow in windows 322 | if ((name of thisWindow as text) is equal to "PiXel Check") then 323 | repeat with thisButton in (buttons of thisWindow) 324 | if ((name of thisButton as text) is equal to "Automatic") then 325 | click thisButton 326 | exit repeat 327 | end if 328 | end repeat 329 | exit repeat 330 | end if 331 | end repeat 332 | end tell 333 | 334 | try 335 | tell application id "com.apple.systemevents" to tell (first application process whose bundle identifier is "me.macguitar.PiXel-Check") 336 | repeat while ((count of windows) ≥ 2) 337 | delay 1 338 | end repeat 339 | end tell 340 | end try 341 | 342 | try 343 | with timeout of 1 second 344 | if (application id ("me.macguitar." & "PiXel-Check") is running) then tell application id ("me.macguitar." & "PiXel-Check") to quit 345 | end timeout 346 | end try 347 | 348 | set testCount to (testCount + 1) 349 | else 350 | exit repeat 351 | end if 352 | end repeat 353 | end try 354 | 355 | if (testCount ≥ 1) then 356 | set shortModelName to "Unknown Model" 357 | try 358 | set shortModelName to (do shell script ("bash -c " & (quoted form of "/usr/libexec/PlistBuddy -c 'Print :0:_items:0:machine_name' /dev/stdin <<< \"$(system_profiler -xml SPHardwareDataType)\""))) 359 | end try 360 | if ((words of shortModelName) contains "MacBook") then 361 | try 362 | (("/Applications/Trackpad Test.app" as POSIX file) as alias) 363 | if (application id ("org.freegeek." & "Trackpad-Test") is not running) then -- Break up App ID or else build will fail if not found during compilation when app is not installed. 364 | try 365 | activate 366 | end try 367 | display alert " 368 | Would you like to launch “Trackpad Test”?" buttons {"No", "Yes"} cancel button 1 default button 2 giving up after 15 369 | do shell script "open -na '/Applications/Trackpad Test.app'" 370 | end if 371 | end try 372 | else 373 | try 374 | activate 375 | end try 376 | display alert " 377 | The next test is manually testing 378 | this Macs Ports and Disc Drive." 379 | end if 380 | end if 381 | -------------------------------------------------------------------------------- /Testing Scripts/Test CD/Source/Test CD.applescript: -------------------------------------------------------------------------------- 1 | -- By: Pico Mitchell 2 | -- For: MacLand @ Free Geek 3 | -- 4 | -- MIT License 5 | -- 6 | -- Copyright (c) 2021 Free Geek 7 | -- 8 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 9 | -- to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | -- and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 11 | -- 12 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | -- 14 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | -- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -- 18 | 19 | -- Version: 2023.7.7-3 20 | 21 | -- App Icon is “Optical Disc” from Twemoji (https://twemoji.twitter.com/) by Twitter (https://twitter.com) 22 | -- Licensed under CC-BY 4.0 (https://creativecommons.org/licenses/by/4.0/) 23 | 24 | -- Build Flag: LSUIElement 25 | 26 | use AppleScript version "2.7" 27 | use scripting additions 28 | 29 | repeat -- dialogs timeout when screen is asleep or locked (just in case) 30 | set isAwake to true 31 | try 32 | set isAwake to ((run script "ObjC.import('CoreGraphics'); $.CGDisplayIsActive($.CGMainDisplayID())" in "JavaScript") is equal to 1) 33 | end try 34 | 35 | set isUnlocked to true 36 | try 37 | set isUnlocked to ((do shell script ("bash -c " & (quoted form of "/usr/libexec/PlistBuddy -c 'Print :IOConsoleUsers:0:CGSSessionScreenIsLocked' /dev/stdin <<< \"$(ioreg -ac IORegistryEntry -k IOConsoleUsers -d 1)\""))) is not equal to "true") 38 | end try 39 | 40 | if (isAwake and isUnlocked) then 41 | exit repeat 42 | else 43 | delay 1 44 | end if 45 | end repeat 46 | 47 | try 48 | set infoPlistPath to ((POSIX path of (path to me)) & "Contents/Info.plist") 49 | ((infoPlistPath as POSIX file) as alias) 50 | 51 | set intendedAppName to "Test CD" -- Hardcode intended App name because Name or Bundle Identifier changes should not be done lightly or accidentally. 52 | 53 | try 54 | do shell script ("/usr/libexec/PlistBuddy -c 'Print :FGBuiltByMacLandScriptBuilder' " & (quoted form of infoPlistPath)) 55 | ((((POSIX path of (path to me)) & "Contents/MacOS/" & intendedAppName) as POSIX file) as alias) 56 | on error 57 | try 58 | activate 59 | end try 60 | display alert " 61 | “" & (name of me) & "” must be built by the “MacLand Script Builder” script." buttons {"Quit"} default button 1 as critical 62 | quit 63 | delay 10 64 | end try 65 | 66 | set AppleScript's text item delimiters to "-" 67 | set intendedBundleIdentifier to ("org.freegeek." & ((words of intendedAppName) as text)) 68 | set currentBundleIdentifier to ((do shell script ("/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' " & (quoted form of infoPlistPath))) as text) 69 | if (currentBundleIdentifier is not equal to intendedBundleIdentifier) then error "“" & (name of me) & "” does not have the correct Bundle Identifier. 70 | 71 | 72 | Current Bundle Identifier: 73 | " & currentBundleIdentifier & " 74 | 75 | Intended Bundle Identifier: 76 | " & intendedBundleIdentifier 77 | on error checkInfoPlistError 78 | if (checkInfoPlistError does not start with "Can’t make file") then 79 | try 80 | activate 81 | end try 82 | display alert checkInfoPlistError buttons {"Quit"} default button 1 as critical 83 | quit 84 | delay 10 85 | end if 86 | end try 87 | 88 | try 89 | set mainScptPath to ((POSIX path of (path to me)) & "Contents/Resources/Scripts/main.scpt") 90 | ((mainScptPath as POSIX file) as alias) 91 | do shell script "osadecompile " & (quoted form of mainScptPath) 92 | error " 93 | “" & (name of me) & "” must be exported as a Run-Only Script." 94 | on error checkReadOnlyErrorMessage 95 | if ((checkReadOnlyErrorMessage does not contain "errOSASourceNotAvailable") and (checkReadOnlyErrorMessage does not start with "Can’t make file")) then 96 | activate 97 | display alert checkReadOnlyErrorMessage buttons {"Quit"} default button 1 as critical 98 | quit 99 | delay 10 100 | end if 101 | end try 102 | 103 | 104 | set freeGeekUpdaterAppPath to "/Applications/Free Geek Updater.app" 105 | set freeGeekUpdaterIsRunning to false 106 | try 107 | ((freeGeekUpdaterAppPath as POSIX file) as alias) 108 | set freeGeekUpdaterIsRunning to (application freeGeekUpdaterAppPath is running) 109 | end try 110 | 111 | set adminUsername to "Staff" 112 | set adminPassword to "[MACLAND SCRIPT BUILDER WILL REPLACE THIS PLACEHOLDER WITH OBFUSCATED ADMIN PASSWORD]" 113 | 114 | set buildInfoPath to ((POSIX path of (path to shared documents folder)) & "Build Info/") 115 | 116 | try 117 | (((buildInfoPath & ".fgSetupSkipped") as POSIX file) as alias) 118 | 119 | try 120 | do shell script ("mkdir " & (quoted form of buildInfoPath)) 121 | end try 122 | try 123 | set AppleScript's text item delimiters to "-" 124 | do shell script ("touch " & (quoted form of (buildInfoPath & ".fgLaunchAfterSetup-org.freegeek." & ((words of (name of me)) as text)))) user name adminUsername password adminPassword with administrator privileges 125 | end try 126 | 127 | if (not freeGeekUpdaterIsRunning) then 128 | try 129 | -- For some reason, on Big Sur, apps are not opening unless we specify "-n" to "Open a new instance of the application(s) even if one is already running." All scripts have LSMultipleInstancesProhibited to this will not actually ever open a new instance. 130 | do shell script "open -na '/Applications/Test Boot Setup.app'" 131 | end try 132 | end if 133 | 134 | quit 135 | delay 10 136 | end try 137 | 138 | if (freeGeekUpdaterIsRunning) then -- Quit if Updater is running so that this app can be updated if needed. 139 | quit 140 | delay 10 141 | end if 142 | 143 | 144 | set systemVersion to (system version of (system info)) 145 | considering numeric strings 146 | set isMojaveOrNewer to (systemVersion ≥ "10.14") 147 | set isCatalinaOrNewer to (systemVersion ≥ "10.15") 148 | end considering 149 | 150 | 151 | set iTunesOrMusic to "iTunes" 152 | if (isCatalinaOrNewer) then set iTunesOrMusic to "Music" 153 | 154 | set iTunesOrMusicID to ("com.apple." & iTunesOrMusic) 155 | 156 | if (isMojaveOrNewer) then 157 | try 158 | tell application id "com.apple.systemevents" to every window -- To prompt for Automation access on Mojave 159 | on error automationAccessErrorMessage number automationAccessErrorNumber 160 | if (automationAccessErrorNumber is equal to -1743) then 161 | try 162 | tell application id "com.apple.systempreferences" to activate 163 | end try 164 | try 165 | open location "x-apple.systempreferences:com.apple.preference.security?Privacy_Automation" -- The "Privacy_Automation" anchor is not exposed/accessible via AppleScript, but can be accessed via URL Scheme. 166 | end try 167 | try 168 | activate 169 | end try 170 | try 171 | display dialog "“" & (name of me) & "” must be allowed to control and perform actions in “System Events” to be able to function. 172 | 173 | 174 | USE THE FOLLOWING STEPS TO FIX THIS ISSUE: 175 | 176 | • Open the “System Preferences” application. 177 | 178 | • Click the “Security & Privacy” preference pane. 179 | 180 | • Select the “Privacy” tab. 181 | 182 | • Select “Automation” in the source list on the left. 183 | 184 | • Find “" & (name of me) & "” in the list on the right and turn on the “System Events” checkbox underneath it. 185 | 186 | • Relaunch “" & (name of me) & "” (using the button below)." buttons {"Quit", "Relaunch “" & (name of me) & "”"} cancel button 1 default button 2 with title (name of me) with icon caution 187 | try 188 | do shell script "osascript -e 'delay 0.5' -e 'repeat while (application \"" & (POSIX path of (path to me)) & "\" is running)' -e 'delay 0.5' -e 'end repeat' -e 'do shell script \"open -na \\\"" & (POSIX path of (path to me)) & "\\\"\"' > /dev/null 2>&1 &" 189 | end try 190 | end try 191 | quit 192 | delay 10 193 | end if 194 | end try 195 | end if 196 | 197 | try 198 | tell application id "com.apple.systemevents" to tell (first application process whose bundle identifier is "com.apple.finder") to (get windows) 199 | on error (assistiveAccessTestErrorMessage) 200 | if ((offset of "not allowed assistive" in assistiveAccessTestErrorMessage) > 0) then 201 | if (isMojaveOrNewer) then 202 | try 203 | tell application id iTunesOrMusicID to every window -- To prompt for Automation access on Mojave 204 | on error automationAccessErrorMessage number automationAccessErrorNumber 205 | if (automationAccessErrorNumber is equal to -1743) then 206 | try 207 | tell application id "com.apple.systempreferences" to activate 208 | end try 209 | try 210 | open location "x-apple.systempreferences:com.apple.preference.security?Privacy_Automation" -- The "Privacy_Automation" anchor is not exposed/accessible via AppleScript, but can be accessed via URL Scheme. 211 | end try 212 | try 213 | activate 214 | end try 215 | try 216 | display dialog "“" & (name of me) & "” must be allowed to control and perform actions in “" & iTunesOrMusic & "” to be able to function. 217 | 218 | 219 | USE THE FOLLOWING STEPS TO FIX THIS ISSUE: 220 | 221 | • Open the “System Preferences” application. 222 | 223 | • Click the “Security & Privacy” preference pane. 224 | 225 | • Select the “Privacy” tab. 226 | 227 | • Select “Automation” in the source list on the left. 228 | 229 | • Find “" & (name of me) & "” in the list on the right and turn on the “" & iTunesOrMusic & "” checkbox underneath it. 230 | 231 | • Relaunch “" & (name of me) & "” (using the button below)." buttons {"Quit", "Relaunch “" & (name of me) & "”"} cancel button 1 default button 2 with title (name of me) with icon caution 232 | try 233 | do shell script "osascript -e 'delay 0.5' -e 'repeat while (application \"" & (POSIX path of (path to me)) & "\" is running)' -e 'delay 0.5' -e 'end repeat' -e 'do shell script \"open -na \\\"" & (POSIX path of (path to me)) & "\\\"\"' > /dev/null 2>&1 &" 234 | end try 235 | end try 236 | quit 237 | delay 10 238 | end if 239 | end try 240 | tell application id iTunesOrMusicID to quit 241 | end if 242 | 243 | try 244 | tell application id "com.apple.finder" to reveal (path to me) 245 | end try 246 | try 247 | tell application id "com.apple.systempreferences" 248 | try 249 | activate 250 | end try 251 | reveal ((anchor "Privacy_Accessibility") of (pane id "com.apple.preference.security")) 252 | end tell 253 | end try 254 | try 255 | activate 256 | end try 257 | try 258 | display dialog "“" & (name of me) & "” must be allowed to control this computer using Accessibility Features to be able to function. 259 | 260 | 261 | USE THE FOLLOWING STEPS TO FIX THIS ISSUE: 262 | 263 | • Open the “System Preferences” application. 264 | 265 | • Click the “Security & Privacy” preference pane. 266 | 267 | • Select the “Privacy” tab. 268 | 269 | • Select “Accessibility” in the source list on the left. 270 | 271 | • Click the Lock icon at the bottom left of the window, enter the administrator username and password, and then click Unlock. 272 | 273 | • Find “" & (name of me) & "” in the list on the right and turn on the checkbox next to it. If “" & (name of me) & "” IS NOT in the list, drag-and-drop the app icon from Finder into the list. 274 | 275 | • Relaunch “" & (name of me) & "” (using the button below)." buttons {"Quit", "Relaunch “" & (name of me) & "”"} cancel button 1 default button 2 with title (name of me) with icon caution 276 | try 277 | do shell script "osascript -e 'delay 0.5' -e 'repeat while (application \"" & (POSIX path of (path to me)) & "\" is running)' -e 'delay 0.5' -e 'end repeat' -e 'do shell script \"open -na \\\"" & (POSIX path of (path to me)) & "\\\"\"' > /dev/null 2>&1 &" 278 | end try 279 | end try 280 | quit 281 | delay 10 282 | end if 283 | end try 284 | 285 | 286 | try 287 | with timeout of 1 second 288 | tell application id "com.apple.DVDPlayer" to quit 289 | end timeout 290 | end try 291 | 292 | try 293 | set volume output volume 75 without output muted 294 | end try 295 | try 296 | set volume alert volume 100 297 | end try 298 | 299 | -- Since target may be "iTunes" or "Music", scripting must be done with "run script" to be compiled on the fly 300 | -- since compiling when targeting a variable will error because of not knowing the correct scripting dictionary. 301 | 302 | try 303 | run script " 304 | if (application id \"" & iTunesOrMusicID & "\" is running) then tell application id \"" & iTunesOrMusicID & "\" to set visible of browser window 1 to true 305 | 306 | tell application id \"" & iTunesOrMusicID & "\" 307 | try 308 | activate 309 | end try 310 | set sound volume to 80 311 | end tell 312 | " 313 | end try 314 | 315 | delay 5 -- Wait for possible CD Lookup Results window to open. 316 | 317 | try -- Mute volume so we don't beep in case CD Lookup Results are not visible. 318 | set volume output volume 0 with output muted 319 | end try 320 | try 321 | set volume alert volume 0 322 | end try 323 | 324 | try 325 | tell application id "com.apple.systemevents" to tell (first application process whose bundle identifier is iTunesOrMusicID) 326 | set frontmost to true 327 | keystroke return -- To close possible CD Lookup Results window (which doesn't show up when getting windows with AppleScript). 328 | end tell 329 | end try 330 | 331 | delay 0.5 332 | 333 | try 334 | set volume output volume 75 without output muted 335 | end try 336 | try 337 | set volume alert volume 100 338 | end try 339 | 340 | try 341 | tell application id "com.apple.systemevents" to tell (first application process whose bundle identifier is iTunesOrMusicID) 342 | set frontmost to true 343 | repeat with thisButton in (buttons of window 1) 344 | if ((name of thisButton) is "No Thanks") then 345 | set frontmost to true 346 | click thisButton 347 | exit repeat 348 | end if 349 | end repeat 350 | end tell 351 | end try 352 | 353 | delay 0.5 354 | 355 | try 356 | run script " 357 | tell application id \"" & iTunesOrMusicID & "\" 358 | try 359 | activate 360 | end try 361 | set cdPlaylist to (playlist 1 of (first source whose kind is audio CD)) 362 | reveal cdPlaylist 363 | delay 0.5 364 | play cdPlaylist 365 | end tell 366 | " 367 | end try 368 | -------------------------------------------------------------------------------- /Testing Scripts/Test DVD/Source/Test DVD.applescript: -------------------------------------------------------------------------------- 1 | -- By: Pico Mitchell 2 | -- For: MacLand @ Free Geek 3 | -- 4 | -- MIT License 5 | -- 6 | -- Copyright (c) 2021 Free Geek 7 | -- 8 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 9 | -- to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | -- and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 11 | -- 12 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | -- 14 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | -- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -- 18 | 19 | -- Version: 2023.7.7-3 20 | 21 | -- App Icon is “DVD” from Twemoji (https://twemoji.twitter.com/) by Twitter (https://twitter.com) 22 | -- Licensed under CC-BY 4.0 (https://creativecommons.org/licenses/by/4.0/) 23 | 24 | -- Build Flag: LSUIElement 25 | 26 | use AppleScript version "2.7" 27 | use scripting additions 28 | 29 | repeat -- dialogs timeout when screen is asleep or locked (just in case) 30 | set isAwake to true 31 | try 32 | set isAwake to ((run script "ObjC.import('CoreGraphics'); $.CGDisplayIsActive($.CGMainDisplayID())" in "JavaScript") is equal to 1) 33 | end try 34 | 35 | set isUnlocked to true 36 | try 37 | set isUnlocked to ((do shell script ("bash -c " & (quoted form of "/usr/libexec/PlistBuddy -c 'Print :IOConsoleUsers:0:CGSSessionScreenIsLocked' /dev/stdin <<< \"$(ioreg -ac IORegistryEntry -k IOConsoleUsers -d 1)\""))) is not equal to "true") 38 | end try 39 | 40 | if (isAwake and isUnlocked) then 41 | exit repeat 42 | else 43 | delay 1 44 | end if 45 | end repeat 46 | 47 | try 48 | set infoPlistPath to ((POSIX path of (path to me)) & "Contents/Info.plist") 49 | ((infoPlistPath as POSIX file) as alias) 50 | 51 | set intendedAppName to "Test DVD" -- Hardcode intended App name because Name or Bundle Identifier changes should not be done lightly or accidentally. 52 | 53 | try 54 | do shell script ("/usr/libexec/PlistBuddy -c 'Print :FGBuiltByMacLandScriptBuilder' " & (quoted form of infoPlistPath)) 55 | ((((POSIX path of (path to me)) & "Contents/MacOS/" & intendedAppName) as POSIX file) as alias) 56 | on error 57 | try 58 | activate 59 | end try 60 | display alert " 61 | “" & (name of me) & "” must be built by the “MacLand Script Builder” script." buttons {"Quit"} default button 1 as critical 62 | quit 63 | delay 10 64 | end try 65 | 66 | set AppleScript's text item delimiters to "-" 67 | set intendedBundleIdentifier to ("org.freegeek." & ((words of intendedAppName) as text)) 68 | set currentBundleIdentifier to ((do shell script ("/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' " & (quoted form of infoPlistPath))) as text) 69 | if (currentBundleIdentifier is not equal to intendedBundleIdentifier) then error "“" & (name of me) & "” does not have the correct Bundle Identifier. 70 | 71 | 72 | Current Bundle Identifier: 73 | " & currentBundleIdentifier & " 74 | 75 | Intended Bundle Identifier: 76 | " & intendedBundleIdentifier 77 | on error checkInfoPlistError 78 | if (checkInfoPlistError does not start with "Can’t make file") then 79 | try 80 | activate 81 | end try 82 | display alert checkInfoPlistError buttons {"Quit"} default button 1 as critical 83 | quit 84 | delay 10 85 | end if 86 | end try 87 | 88 | try 89 | set mainScptPath to ((POSIX path of (path to me)) & "Contents/Resources/Scripts/main.scpt") 90 | ((mainScptPath as POSIX file) as alias) 91 | do shell script "osadecompile " & (quoted form of mainScptPath) 92 | error " 93 | “" & (name of me) & "” must be exported as a Run-Only Script." 94 | on error checkReadOnlyErrorMessage 95 | if ((checkReadOnlyErrorMessage does not contain "errOSASourceNotAvailable") and (checkReadOnlyErrorMessage does not start with "Can’t make file")) then 96 | activate 97 | display alert checkReadOnlyErrorMessage buttons {"Quit"} default button 1 as critical 98 | quit 99 | delay 10 100 | end if 101 | end try 102 | 103 | 104 | set freeGeekUpdaterAppPath to "/Applications/Free Geek Updater.app" 105 | set freeGeekUpdaterIsRunning to false 106 | try 107 | ((freeGeekUpdaterAppPath as POSIX file) as alias) 108 | set freeGeekUpdaterIsRunning to (application freeGeekUpdaterAppPath is running) 109 | end try 110 | 111 | set adminUsername to "Staff" 112 | set adminPassword to "[MACLAND SCRIPT BUILDER WILL REPLACE THIS PLACEHOLDER WITH OBFUSCATED ADMIN PASSWORD]" 113 | 114 | set buildInfoPath to ((POSIX path of (path to shared documents folder)) & "Build Info/") 115 | 116 | try 117 | (((buildInfoPath & ".fgSetupSkipped") as POSIX file) as alias) 118 | 119 | try 120 | do shell script ("mkdir " & (quoted form of buildInfoPath)) 121 | end try 122 | try 123 | set AppleScript's text item delimiters to "-" 124 | do shell script ("touch " & (quoted form of (buildInfoPath & ".fgLaunchAfterSetup-org.freegeek." & ((words of (name of me)) as text)))) user name adminUsername password adminPassword with administrator privileges 125 | end try 126 | 127 | if (not freeGeekUpdaterIsRunning) then 128 | try 129 | -- For some reason, on Big Sur, apps are not opening unless we specify "-n" to "Open a new instance of the application(s) even if one is already running." All scripts have LSMultipleInstancesProhibited to this will not actually ever open a new instance. 130 | do shell script "open -na '/Applications/Test Boot Setup.app'" 131 | end try 132 | end if 133 | 134 | quit 135 | delay 10 136 | end try 137 | 138 | if (freeGeekUpdaterIsRunning) then -- Quit if Updater is running so that this app can be updated if needed. 139 | quit 140 | delay 10 141 | end if 142 | 143 | 144 | set systemVersion to (system version of (system info)) 145 | considering numeric strings 146 | set isMojaveOrNewer to (systemVersion ≥ "10.14") 147 | set isCatalinaOrNewer to (systemVersion ≥ "10.15") 148 | end considering 149 | 150 | 151 | set iTunesOrMusicID to "com.apple.iTunes" 152 | if (isCatalinaOrNewer) then set iTunesOrMusicID to "com.apple.Music" 153 | 154 | try 155 | with timeout of 1 second 156 | tell application id iTunesOrMusicID to quit 157 | end timeout 158 | end try 159 | 160 | try 161 | set volume output volume 75 without output muted 162 | end try 163 | try 164 | set volume alert volume 100 165 | end try 166 | 167 | try 168 | tell application id "com.apple.DVDPlayer" to activate 169 | end try 170 | 171 | if (not isMojaveOrNewer) then -- DVD Player on Mojave or newer isn't scriptable. 172 | try 173 | run script " 174 | tell application id \"com.apple.DVDPlayer\" 175 | try 176 | activate 177 | end try 178 | repeat while app initializing 179 | delay 1 180 | end repeat 181 | try 182 | activate 183 | end try 184 | set audio volume to 190 185 | play dvd 186 | end tell 187 | " -- Set DVD Player volume to 190 because 255 is max. 188 | end try 189 | end if 190 | -------------------------------------------------------------------------------- /Testing Scripts/Volume Scripts for Breakaway/Set Volume for Headphones.applescript: -------------------------------------------------------------------------------- 1 | -- By: Pico Mitchell 2 | -- For: MacLand @ Free Geek 3 | -- Last Updated: February 22nd, 2022 4 | -- 5 | -- MIT License 6 | -- 7 | -- Copyright (c) 2021 Free Geek 8 | -- 9 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 10 | -- to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | -- and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 12 | -- 13 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 14 | -- 15 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 17 | -- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | -- 19 | 20 | use AppleScript version "2.7" 21 | use scripting additions 22 | 23 | try 24 | set volume output volume 25 without output muted 25 | end try 26 | 27 | try 28 | set volume alert volume 100 29 | end try 30 | -------------------------------------------------------------------------------- /Testing Scripts/Volume Scripts for Breakaway/Set Volume for Speakers.applescript: -------------------------------------------------------------------------------- 1 | -- By: Pico Mitchell 2 | -- For: MacLand @ Free Geek 3 | -- Last Updated: February 22nd, 2022 4 | -- 5 | -- MIT License 6 | -- 7 | -- Copyright (c) 2021 Free Geek 8 | -- 9 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 10 | -- to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | -- and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 12 | -- 13 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 14 | -- 15 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 17 | -- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | -- 19 | 20 | use AppleScript version "2.7" 21 | use scripting additions 22 | 23 | try 24 | set volume output volume 75 without output muted 25 | end try 26 | 27 | try 28 | set volume alert volume 100 29 | end try 30 | -------------------------------------------------------------------------------- /fgMIB Resources/Build Tools/Text to Speech Phrases.txt: -------------------------------------------------------------------------------- 1 | Starting Free Geek customizations. 2 | Completed Free Geek customizations. 3 | 4 | Starting Free Geek reset. 5 | Completed Free Geek reset. 6 | 7 | Do not disturb this Mac! 8 | Shutting down this Mac! 9 | Rebooting this Mac! 10 | 11 | An error has occurred! 12 | This Mac cannot be sold! 13 | Please deliver this Mac to Free Geek I.T. -------------------------------------------------------------------------------- /fgMIB Resources/Build Tools/build-fg-prepare-os-pkg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck enable=add-default-case,avoid-nullary-conditions,check-unassigned-uppercase,deprecate-which,quote-safe-variables,require-double-brackets 3 | 4 | # 5 | # MIT License 6 | # 7 | # Copyright (c) 2021 Free Geek 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 10 | # to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 17 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | # 19 | 20 | PATH='/usr/bin:/bin:/usr/sbin:/sbin:/usr/libexec' # Add "/usr/libexec" to PATH for easy access to PlistBuddy. 21 | 22 | PROJECT_DIR="$(cd "${BASH_SOURCE[0]%/*}" &> /dev/null && pwd -P)/../Prepare OS Package" 23 | readonly PROJECT_DIR 24 | 25 | TMPDIR="$([[ -d "${TMPDIR}" && -w "${TMPDIR}" ]] && echo "${TMPDIR%/}/" || echo '/private/tmp/')" # Make sure "TMPDIR" is always set and that it always has a trailing slash for consistency regardless of the current environment. 26 | 27 | if ! ADMIN_PASSWORD="$(PlistBuddy -c 'Print :admin_password' "${PROJECT_DIR}/../../Build Tools/Free Geek Passwords.plist")" || [[ -z "${ADMIN_PASSWORD}" ]]; then 28 | echo 'FAILED TO GET ADMIN PASSWORD' 29 | exit 1 30 | fi 31 | readonly ADMIN_PASSWORD 32 | 33 | if ! WIFI_PASSWORD="$(PlistBuddy -c 'Print :wifi_password' "${PROJECT_DIR}/../../Build Tools/Free Geek Passwords.plist")" || [[ -z "${WIFI_PASSWORD}" ]]; then 34 | echo 'FAILED TO GET WI-FI PASSWORD' 35 | exit 1 36 | fi 37 | readonly WIFI_PASSWORD 38 | 39 | latest_firefox_version="$(curl -m 5 -sfw '%{redirect_url}' -o /dev/null 'https://download.mozilla.org/?product=firefox-pkg-latest-ssl&os=osx&lang=en-US' | awk -F '/' '{ print $7; exit }')" 40 | if [[ -n "${latest_firefox_version}" ]]; then 41 | latest_firefox_pkg_path="${PROJECT_DIR}/Package Resources/Global/Apps/darwin-le-19/Firefox ${latest_firefox_version}.pkg" 42 | if [[ -f "${latest_firefox_pkg_path}" ]]; then 43 | echo "Firefox ${latest_firefox_version} PKG Is Up-to-Date" 44 | else 45 | rm -f "${PROJECT_DIR}/Package Resources/Global/Apps/darwin-le-19/Firefox"*'.pkg' 46 | echo "Downloading Firefox ${latest_firefox_version}..." 47 | mkdir -p "${latest_firefox_pkg_path%/*}" 48 | curl --connect-timeout 5 --progress-bar -fL 'https://download.mozilla.org/?product=firefox-pkg-latest-ssl&os=osx&lang=en-US' -o "${latest_firefox_pkg_path}" 49 | fi 50 | else 51 | echo 'FAILED TO RETRIEVE LATEST FIREFOX VERSION' 52 | fi 53 | 54 | latest_drivedx_version="$(curl -m 5 -sfL 'https://binaryfruit.com/download/drivedx/mac/1/updates/?appcast&appName=DriveDxMac' | xmllint --xpath 'string(//enclosure/@*[name()="sparkle:shortVersionString"])' -)" 55 | if [[ -n "${latest_drivedx_version}" ]]; then 56 | latest_drivedx_zip_path="${PROJECT_DIR}/Package Resources/User/fg-demo/Apps/darwin-all-versions/DriveDx ${latest_drivedx_version}.zip" 57 | if [[ -f "${latest_drivedx_zip_path}" ]]; then 58 | echo "DriveDx ${latest_drivedx_version} ZIP Is Up-to-Date" 59 | else 60 | rm -f "${PROJECT_DIR}/Package Resources/User/fg-demo/Apps/darwin-all-versions/DriveDx"*'.zip' 61 | echo "Downloading DriveDx ${latest_drivedx_version}..." 62 | mkdir -p "${latest_drivedx_zip_path%/*}" 63 | curl --connect-timeout 5 --progress-bar -fL 'https://binaryfruit.com/download/drivedx/mac/1/' -o "${latest_drivedx_zip_path}" 64 | fi 65 | else 66 | echo 'FAILED TO RETRIEVE LATEST DRIVEDX VERSION' 67 | fi 68 | 69 | latest_mactracker_version="$(curl -m 5 -sfL 'https://update.mactracker.ca/appcast-b.xml' | xmllint --xpath 'string(//enclosure/@*[name()="sparkle:version"])' -)" 70 | if [[ -n "${latest_mactracker_version}" ]]; then 71 | latest_mactracker_zip_path="${PROJECT_DIR}/Package Resources/User/fg-demo/Apps/darwin-all-versions/Mactracker ${latest_mactracker_version}.zip" 72 | if [[ -f "${latest_mactracker_zip_path}" ]]; then 73 | echo "Mactracker ${latest_mactracker_version} ZIP Is Up-to-Date" 74 | else 75 | rm -f "${PROJECT_DIR}/Package Resources/User/fg-demo/Apps/darwin-all-versions/Mactracker"*'.zip' 76 | echo "Downloading Mactracker ${latest_mactracker_version}..." 77 | mkdir -p "${latest_mactracker_zip_path%/*}" 78 | curl --connect-timeout 5 --progress-bar -fL "https://mactracker.ca/downloads/Mactracker_${latest_mactracker_version}.zip" -o "${latest_mactracker_zip_path}" 79 | fi 80 | else 81 | echo 'FAILED TO RETRIEVE LATEST MACTRACKER VERSION' 82 | fi 83 | 84 | # Download the latest Geekbench 6 for macOS 11 Big Sur and newer. 85 | geekbench_download_url="$(curl -m 5 -sfL 'https://www.geekbench.com/download/mac/' | xmllint --html --xpath 'string(//a[contains(@href,"Mac.zip")]/@href)' - 2> /dev/null)" 86 | latest_geekbench_version="$(echo "${geekbench_download_url}" | cut -d '-' -f 2)" 87 | if [[ -n "${latest_geekbench_version}" ]]; then 88 | latest_geekbench_zip_path="${PROJECT_DIR}/Package Resources/User/fg-demo/Apps/darwin-ge-20/Geekbench ${latest_geekbench_version}.zip" 89 | if [[ -f "${latest_geekbench_zip_path}" ]]; then 90 | echo "Geekbench ${latest_geekbench_version} ZIP Is Up-to-Date" 91 | else 92 | rm -f "${PROJECT_DIR}/Package Resources/User/fg-demo/Apps/darwin-ge-20/Geekbench"*'.zip' 93 | echo "Downloading Geekbench ${latest_geekbench_version}..." 94 | mkdir -p "${latest_geekbench_zip_path%/*}" 95 | curl --connect-timeout 5 --progress-bar -fL "${geekbench_download_url}" -o "${latest_geekbench_zip_path}" 96 | fi 97 | else 98 | echo 'FAILED TO RETRIEVE LATEST GEEKBENCH VERSION' 99 | fi 100 | 101 | # AND ALSO download Geekbench 5 for macOS 10.15 Catalina and older (not sure if any update beyond v5.5.1 will ever be released now that v6 is out, but doesn't hurt to check). 102 | geekbench5_download_url="$(curl -m 5 -sfL 'https://www.geekbench.com/legacy/' | xmllint --html --xpath 'string(//a[contains(@href,"Geekbench-5") and contains(@href,"Mac.zip")]/@href)' - 2> /dev/null)" 103 | latest_geekbench5_version="$(echo "${geekbench5_download_url}" | cut -d '-' -f 2)" 104 | if [[ -n "${latest_geekbench5_version}" ]]; then 105 | latest_geekbench_zip_path="${PROJECT_DIR}/Package Resources/User/fg-demo/Apps/darwin-le-19/Geekbench ${latest_geekbench5_version}.zip" 106 | if [[ -f "${latest_geekbench_zip_path}" ]]; then 107 | echo "Geekbench ${latest_geekbench5_version} ZIP Is Up-to-Date" 108 | else 109 | rm -f "${PROJECT_DIR}/Package Resources/User/fg-demo/Apps/darwin-le-19/Geekbench"*'.zip' 110 | echo "Downloading Geekbench ${latest_geekbench5_version}..." 111 | mkdir -p "${latest_geekbench_zip_path%/*}" 112 | curl --connect-timeout 5 --progress-bar -fL "${geekbench5_download_url}" -o "${latest_geekbench_zip_path}" 113 | fi 114 | else 115 | echo 'FAILED TO RETRIEVE LATEST GEEKBENCH 5 VERSION' 116 | fi 117 | 118 | 119 | # NOTE: KeyboardCleanTool (https://folivora.ai/keyboardcleantool) is also installed into user apps, 120 | # but not sure how to check for latest version since the download link is always just "https://folivora.ai/releases/KeyboardCleanTool.zip". 121 | # So, will just check/update it manually periodically instead of automating re-downloading the latest version (which may be the same as we already have) for every build. 122 | 123 | 124 | # Sign "fg-snapshot-preserver.sh" so that it can be displayed nicely in macOS 13 Ventura using "AssociatedBundleIdentifiers" in the LaunchDaemon. 125 | # See "Setting Up Snapshot Preserver LaunchDaemon" section in "fg-prepare-os.sh" for more information. 126 | codesign -fs 'Developer ID Application' --strict "${PROJECT_DIR}/Package Resources/fg-snapshot-reset/fg-snapshot-preserver.sh" 127 | 128 | package_name='fg-prepare-os' 129 | package_id="org.freegeek.${package_name}" 130 | 131 | rm -rf "${PROJECT_DIR}/Package Scripts" 132 | mkdir -p "${PROJECT_DIR}/Package Scripts" 133 | 134 | # DO NOT JUST COPY "fg-prepare-os" SCRIPT SINCE ADMIN AND WI-FI PASSWORD PLACEHOLDERS NEED TO BE REPLACED WITH THE ACTUAL OBFUSCATED ADMIN AND WI-FI PASSWORDS. 135 | sed "s/'\[BUILD PACKAGE SCRIPT WILL REPLACE THIS PLACEHOLDER WITH OBFUSCATED ADMIN PASSWORD\]'/\"\$(echo '$(echo -n "${ADMIN_PASSWORD}" | base64)' | base64 -D)\"/; s/'\[BUILD PACKAGE SCRIPT WILL REPLACE THIS PLACEHOLDER WITH OBFUSCATED WI-FI PASSWORD\]'/\"\$(echo '$(echo -n "${WIFI_PASSWORD}" | base64)' | base64 -D)\"/" "${PROJECT_DIR}/fg-prepare-os.sh" > "${PROJECT_DIR}/Package Scripts/postinstall" 136 | 137 | chmod +x "${PROJECT_DIR}/Package Scripts/postinstall" 138 | rm -f "${PROJECT_DIR}/Package Scripts/.DS_Store" 139 | 140 | rm -f "${TMPDIR}${package_name}.pkg" 141 | rm -f "${PROJECT_DIR}/${package_name}.pkg" 142 | 143 | pkg_version="$(date '+%Y.%-m.%-d')" # https://strftime.org 144 | 145 | pkgbuild \ 146 | --install-location "/private/tmp/${package_id}" \ 147 | --root "${PROJECT_DIR}/Package Resources" \ 148 | --scripts "${PROJECT_DIR}/Package Scripts" \ 149 | --identifier "${package_id}" \ 150 | --version "${pkg_version}" \ 151 | "${TMPDIR}${package_name}.pkg" 152 | 153 | rm -rf "${PROJECT_DIR}/Package Scripts" 154 | 155 | productbuild \ 156 | --sign 'Developer ID Installer' \ 157 | --package "${TMPDIR}${package_name}.pkg" \ 158 | --identifier "${package_id}" \ 159 | --version "${pkg_version}" \ 160 | "${PROJECT_DIR}/${package_name}.pkg" 161 | 162 | rm -f "${TMPDIR}${package_name}.pkg" 163 | -------------------------------------------------------------------------------- /fgMIB Resources/Build Tools/copy-fgMIB-resources-to-drives.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck enable=add-default-case,avoid-nullary-conditions,check-unassigned-uppercase,deprecate-which,quote-safe-variables,require-double-brackets 3 | 4 | # 5 | # MIT License 6 | # 7 | # Copyright (c) 2021 Free Geek 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 10 | # to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 17 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | # 19 | 20 | PATH='/usr/bin:/bin:/usr/sbin:/sbin:/usr/libexec' # Add "/usr/libexec" to PATH for easy access to PlistBuddy. 21 | 22 | PROJECT_DIR="$(cd "${BASH_SOURCE[0]%/*}" &> /dev/null && pwd -P)/.." 23 | readonly PROJECT_DIR 24 | 25 | if ! WIFI_PASSWORD="$(PlistBuddy -c 'Print :wifi_password' "${PROJECT_DIR}/../Build Tools/Free Geek Passwords.plist")" || [[ -z "${WIFI_PASSWORD}" ]]; then 26 | echo 'FAILED TO GET WI-FI PASSWORD' 27 | exit 1 28 | fi 29 | readonly WIFI_PASSWORD 30 | 31 | if [[ -f "${PROJECT_DIR}/fg-install-os.sh" && -f "${PROJECT_DIR}/Prepare OS Package/fg-prepare-os.pkg" ]]; then 32 | for this_fgMIB_volume in '/Volumes/fgMIB'*; do 33 | if [[ -d "${this_fgMIB_volume}" ]]; then 34 | echo "STARTING ${this_fgMIB_volume}" 35 | 36 | # There is no point comparing "fg-install-os" files (like we do with other files) since they will never match 37 | # because the source contains the password placeholder and the target contains the obfuscated password. 38 | # Not sure if it's worth trying to compare versions or make a temp copy of the "fg-install-os" file with the obfuscated password to compare with the target since it's such a small and quick file to copy. 39 | 40 | rm -f "${this_fgMIB_volume}/fg-install-os" 41 | echo 'COPYING fg-install-os...' 42 | 43 | # DO NOT JUST COPY "fg-install-os" SCRIPT SINCE WI-FI PASSWORD PLACEHOLDER NEED TO BE REPLACED WITH THE ACTUAL OBFUSCATED WI-FI PASSWORD. 44 | # CANNOT USE "base64", "openssl base64", "xxd", or "uuencode"/"uudecode" TO OBFUSCATE WI-FI PASSWORD IN "fg-install-os" SINCE THEY ARE NOT IN RECOVERY, SO MUST USE A "tr" SHIFT. 45 | sed "s/'\[COPY RESOURCES SCRIPT WILL REPLACE THIS PLACEHOLDER WITH OBFUSCATED WI-FI PASSWORD\]'/\"\$(echo '$(echo -n "${WIFI_PASSWORD}" | tr '\!-~' 'P-~\!-O')' | tr '\!-~' 'P-~\!-O')\"/" "${PROJECT_DIR}/fg-install-os.sh" > "${this_fgMIB_volume}/fg-install-os" 46 | 47 | chmod +x "${this_fgMIB_volume}/fg-install-os" 48 | 49 | if [[ -d "${this_fgMIB_volume}/install-packages" && ! -e "${this_fgMIB_volume}/customization-resources" ]]; then # Rename old packages folder name to new folder name if needed. 50 | echo 'RENAMING install-packages TO customization-resources...' 51 | mv "${this_fgMIB_volume}/install-packages" "${this_fgMIB_volume}/customization-resources" 52 | fi 53 | 54 | if ! cmp -s "${PROJECT_DIR}/Prepare OS Package/fg-prepare-os.pkg" "${this_fgMIB_volume}/customization-resources/fg-prepare-os.pkg"; then 55 | if [[ -d "${this_fgMIB_volume}/customization-resources" ]]; then 56 | rm -f "${this_fgMIB_volume}/customization-resources/fg-prepare-os.pkg" 57 | fi 58 | 59 | echo 'COPYING customization-resources/fg-prepare-os.pkg...' 60 | ditto "${PROJECT_DIR}/Prepare OS Package/fg-prepare-os.pkg" "${this_fgMIB_volume}/customization-resources/fg-prepare-os.pkg" 61 | else 62 | echo "EXACT COPY EXISTS: customization-resources/fg-prepare-os.pkg" 63 | fi 64 | 65 | for this_install_packages_script_file_or_folder in "${PROJECT_DIR}/Install Packages Script/"*; do 66 | this_install_packages_script_file_or_folder_name="${this_install_packages_script_file_or_folder##*/}" 67 | 68 | if [[ -f "${this_install_packages_script_file_or_folder}" ]]; then 69 | if ! cmp -s "${this_install_packages_script_file_or_folder}" "${this_fgMIB_volume}/customization-resources/${this_install_packages_script_file_or_folder_name}"; then 70 | rm -f "${this_fgMIB_volume}/customization-resources/${this_install_packages_script_file_or_folder_name}" 71 | echo "COPYING customization-resources/${this_install_packages_script_file_or_folder_name}..." 72 | ditto "${this_install_packages_script_file_or_folder}" "${this_fgMIB_volume}/customization-resources/${this_install_packages_script_file_or_folder_name}" 73 | else 74 | echo "EXACT COPY EXISTS: customization-resources/${this_install_packages_script_file_or_folder_name}" 75 | fi 76 | elif [[ -d "${this_install_packages_script_file_or_folder}" ]]; then 77 | if [[ ! -d "${this_fgMIB_volume}/customization-resources/${this_install_packages_script_file_or_folder_name}" ]]; then 78 | mkdir -p "${this_fgMIB_volume}/customization-resources/${this_install_packages_script_file_or_folder_name}" 79 | fi 80 | 81 | for this_install_packages_script_subfolder_file_or_folder in "${this_install_packages_script_file_or_folder}/"*; do 82 | this_install_packages_script_subfolder_file_or_folder_name="${this_install_packages_script_subfolder_file_or_folder##*/}" 83 | 84 | if [[ -f "${this_install_packages_script_subfolder_file_or_folder}" ]]; then 85 | if ! cmp -s "${this_install_packages_script_subfolder_file_or_folder}" "${this_fgMIB_volume}/customization-resources/${this_install_packages_script_file_or_folder_name}/${this_install_packages_script_subfolder_file_or_folder_name}"; then 86 | rm -f "${this_fgMIB_volume}/customization-resources/${this_install_packages_script_file_or_folder_name}/${this_install_packages_script_subfolder_file_or_folder_name}" 87 | echo "COPYING customization-resources/${this_install_packages_script_file_or_folder_name}/${this_install_packages_script_subfolder_file_or_folder_name}..." 88 | ditto "${this_install_packages_script_subfolder_file_or_folder}" "${this_fgMIB_volume}/customization-resources/${this_install_packages_script_file_or_folder_name}/${this_install_packages_script_subfolder_file_or_folder_name}" 89 | else 90 | echo "EXACT COPY EXISTS: customization-resources/${this_install_packages_script_file_or_folder_name}/${this_install_packages_script_subfolder_file_or_folder_name}" 91 | fi 92 | elif [[ -d "${this_install_packages_script_subfolder_file_or_folder}" ]]; then 93 | # TODO: Check if exact copy exists instead of always re-copying whole dir (which will be an app). THIS IS NO LONGER CURRENTLY IMPORTANT SINCE NO LONGER INCLUDING APPS IN HERE (INCLUDING ZIP INSTEAD). 94 | rm -rf "${this_fgMIB_volume}/customization-resources/${this_install_packages_script_file_or_folder_name}/${this_install_packages_script_subfolder_file_or_folder_name}" 95 | echo "COPYING customization-resources/${this_install_packages_script_file_or_folder_name}/${this_install_packages_script_subfolder_file_or_folder_name}..." 96 | ditto "${this_install_packages_script_subfolder_file_or_folder}" "${this_fgMIB_volume}/customization-resources/${this_install_packages_script_file_or_folder_name}/${this_install_packages_script_subfolder_file_or_folder_name}" 97 | fi 98 | done 99 | fi 100 | done 101 | 102 | for this_extra_bins_folder in "${PROJECT_DIR}/extra-bins/"*; do 103 | if [[ -d "${this_extra_bins_folder}" ]]; then 104 | this_extra_bins_folder_name="${this_extra_bins_folder##*/}" 105 | 106 | if [[ ! -d "${this_fgMIB_volume}/extra-bins/${this_extra_bins_folder_name}" ]]; then 107 | mkdir -p "${this_fgMIB_volume}/extra-bins/${this_extra_bins_folder_name}" 108 | fi 109 | 110 | for this_extra_bins_versioned_file in "${this_extra_bins_folder}/"*; do 111 | if [[ -f "${this_extra_bins_versioned_file}" ]]; then 112 | this_extra_bins_versioned_file_name="${this_extra_bins_versioned_file##*/}" 113 | 114 | if ! cmp -s "${this_extra_bins_versioned_file}" "${this_fgMIB_volume}/extra-bins/${this_extra_bins_folder_name}/${this_extra_bins_versioned_file_name}"; then 115 | rm -f "${this_fgMIB_volume}/extra-bins/${this_extra_bins_folder_name}/${this_extra_bins_versioned_file_name}" 116 | echo "COPYING extra-bins/${this_extra_bins_folder_name}/${this_extra_bins_versioned_file_name}..." 117 | ditto "${this_extra_bins_versioned_file}" "${this_fgMIB_volume}/extra-bins/${this_extra_bins_folder_name}/${this_extra_bins_versioned_file_name}" 118 | else 119 | echo "EXACT COPY EXISTS: extra-bins/${this_extra_bins_folder_name}/${this_extra_bins_versioned_file_name}" 120 | fi 121 | fi 122 | done 123 | fi 124 | done 125 | 126 | echo "DONE WITH ${this_fgMIB_volume} - UNMOUNTING..." 127 | diskutil unmountDisk "${this_fgMIB_volume}" 128 | else 129 | echo "ERROR - fgMIB VOLUME NOT FOUND" 130 | fi 131 | done 132 | else 133 | echo -e "ERROR - CRITICAL FILES NOT FOUND IN PROJECT_DIR:\n${PROJECT_DIR}" 134 | fi 135 | -------------------------------------------------------------------------------- /fgMIB Resources/Prepare OS Package/Package Resources/fg-error-occurred/fg-error-occurred.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck enable=add-default-case,avoid-nullary-conditions,check-unassigned-uppercase,deprecate-which,quote-safe-variables,require-double-brackets 3 | 4 | # 5 | # Created by Pico Mitchell on 4/19/21. 6 | # For MacLand @ Free Geek 7 | # Version: 2023.3.1-1 8 | # 9 | # MIT License 10 | # 11 | # Copyright (c) 2021 Free Geek 12 | # 13 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 14 | # to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 15 | # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | # 23 | 24 | # NOTICE: This script will only exist on boot to be able to run via LaunchDaemon when booting after not successfully completing fg-prepare-os. 25 | # Actually, it will also exist when booting for fg-snapshot-reset, but will not run since there will be no error in the log. 26 | 27 | PATH='/usr/bin:/bin:/usr/sbin:/sbin:/usr/libexec' # Add "/usr/libexec" to PATH for easy access to PlistBuddy. 28 | 29 | SCRIPT_DIR="$(cd "${BASH_SOURCE[0]%/*}" &> /dev/null && pwd -P)" 30 | readonly SCRIPT_DIR 31 | 32 | launch_daemon_path='/Library/LaunchDaemons/org.freegeek.fg-error-occurred.plist' 33 | 34 | launch_login_progress_app() { 35 | if [[ -f "${SCRIPT_DIR}/Tools/Free-Geek-Login-Progress.zip" && ! -d "${SCRIPT_DIR}/Tools/Free Geek Login Progress.app" ]]; then 36 | ditto -xk --noqtn "${SCRIPT_DIR}/Tools/Free-Geek-Login-Progress.zip" "${SCRIPT_DIR}/Tools" &> /dev/null 37 | touch "${SCRIPT_DIR}/Tools/Free Geek Login Progress.app" 38 | fi 39 | 40 | if [[ -d "${SCRIPT_DIR}/Tools/Free Geek Login Progress.app" ]]; then 41 | # Cannot open "Free Geek Login Progress" directly when at Login Window, but a LaunchAgent with LimitLoadToSessionType=LoginWindow and "launchctl load -S LoginWindow" can open it. 42 | # In my testing I haven't been able to figure out how to do this with any of the modern "launchctl bootstrap" options, but maybe there is some way that I haven't found. 43 | 44 | login_progress_launch_agent_path='/Library/LaunchAgents/org.freegeek.Free-Geek-Login-Progress.plist' 45 | 46 | # NOTE: The following LaunchAgent is setup to run a signed script with launches the app and has "AssociatedBundleIdentifiers" specified to be properly displayed in the "Login Items" list in "System Settings" on macOS 13 Ventura and newer. 47 | # BUT, this is just done for consistency with other code since this particular script will never run when a user is logged in to even be able to see that list in macOS 13 Ventura. 48 | # On macOS 12 Monterey and older, the "AssociatedBundleIdentifiers" will just be ignored and the signed launcher script will behave just as if we ran "/usr/bin/open" directly via the LaunchAgent. 49 | PlistBuddy \ 50 | -c 'Add :Label string org.freegeek.Free-Geek-Login-Progress' \ 51 | -c 'Add :LimitLoadToSessionType string LoginWindow' \ 52 | -c "Add :Program string '${SCRIPT_DIR}/Tools/Free Geek Login Progress.app/Contents/Resources/Launch Free Geek Login Progress'" \ 53 | -c 'Add :AssociatedBundleIdentifiers string org.freegeek.Free-Geek-Login-Progress' \ 54 | -c 'Add :RunAtLoad bool true' \ 55 | -c 'Add :StandardOutPath string /dev/null' \ 56 | -c 'Add :StandardErrorPath string /dev/null' \ 57 | "${login_progress_launch_agent_path}" &> /dev/null 58 | 59 | launchctl load -S LoginWindow "${login_progress_launch_agent_path}" 60 | 61 | for (( wait_for_progress_app_seconds = 0; wait_for_progress_app_seconds < 15; wait_for_progress_app_seconds ++ )); do 62 | if pgrep -qax 'Free Geek Login Progress'; then 63 | break 64 | else 65 | sleep 1 66 | fi 67 | done 68 | 69 | launchctl unload -S LoginWindow "${login_progress_launch_agent_path}" 70 | rm -f "${login_progress_launch_agent_path}" 71 | fi 72 | } 73 | 74 | if [[ "${SCRIPT_DIR}" == '/Users/Shared/fg-error-occurred' && -f "${launch_daemon_path}" && -f '/private/var/db/.AppleSetupDone' && "${EUID:-$(id -u)}" == '0' && 75 | ! -f '/Library/LaunchDaemons/org.freegeek.fg-install-packages.plist' && -f '/Users/Shared/Build Info/Prepare OS Log.txt' ]] && grep -qF $'\tERROR:' '/Users/Shared/Build Info/Prepare OS Log.txt'; then 76 | # Do not run if fg-install-packages LaunchDaemon exists since that will do since it same error display on its own when rebooting after an error occurred. 77 | # The fg-install-packages LaunchDaemon needs to do its own identical error handling like this in case an error occurs before or after this was created or deleted by fg-prepare-os package. 78 | 79 | 80 | # ANNOUNCE ERROR (For some reason "say" does not work on macOS 11 Big Sur when run on boot via LaunchDaemon, so saved a recording of the text instead.) 81 | # Audio drivers (or something) need a few seconds before audio will be able to play when run early on boot via LaunchDaemon. So try for up to 60 seconds before continuing. 82 | 83 | for (( wait_to_play_seconds = 0; wait_to_play_seconds < 60; wait_to_play_seconds ++ )); do 84 | osascript -e 'set volume output volume 50 without output muted' -e 'set volume alert volume 100' &> /dev/null 85 | if afplay "${SCRIPT_DIR}/Announcements/fg-error-occurred.aiff" &> /dev/null; then 86 | break 87 | else 88 | sleep 1 89 | fi 90 | done 91 | 92 | 93 | # WAIT FOR FULL BOOT 94 | # Since LaunchDaemons start so early on boot, always wait for full boot before continuing so that everything is run in a consistent state and all system services have been started. 95 | # Through investigation, I found that "coreauthd" is consistently the last, or nearly the last, root process to be started before the login window is displayed. 96 | 97 | until pgrep -qax 'coreauthd'; do 98 | sleep 2 99 | done 100 | 101 | 102 | # DO NOT ALLOW SLEEP 103 | 104 | caffeinate -dimsuw "$$" & 105 | 106 | 107 | # LAUNCH LOGIN PROGRESS APP 108 | 109 | launch_login_progress_app 110 | 111 | 112 | # ANNOUNCE DELIVER TO I.T. *AFTER* LOGIN WINDOW IS DISPLAYED 113 | 114 | osascript -e 'set volume output volume 50 without output muted' -e 'set volume alert volume 100' &> /dev/null 115 | afplay "${SCRIPT_DIR}/Announcements/fg-deliver-to-it.aiff" 116 | fi 117 | --------------------------------------------------------------------------------