├── .gitignore ├── resources ├── .gitattributes ├── corsica │ ├── .gitattributes │ ├── corsica-user-file.pkg │ ├── corsica-user.pkg │ ├── power-settings.sh │ ├── disable-bluetooth-setup-assistant.sh │ ├── prompt-computername.sh │ ├── disable-screensaver.sh │ ├── set-donotdisturb.sh │ ├── firefox-settings.sh │ ├── skip-siri-setup.mobileconfig │ ├── skip-icloud-setup.mobileconfig │ ├── skip-data-privacy-setup.mobileconfig │ ├── skip-appearence-window.mobileconfig │ ├── auto-update.sh │ └── launch-firefox.sh ├── wallpaper-firefox-file.png ├── desktop-background-2023-UHD.jpg ├── wallpaper-firefox.png ├── deploy.properties ├── disable-guest.sh ├── firewall.sh ├── update-macos.sh ├── app-store-updates.sh ├── remove-pyobjc.sh ├── crashplan_preflight.sh ├── install-rosetta2.sh ├── analytics_settings.sh ├── firefox-bookmarks.sh ├── set-computername.sh ├── macos-versioncheck.sh ├── screensaver.sh ├── dock-config.sh ├── userInfo.sh ├── wallpaper.sh └── enable-filevault.sh ├── .gitattributes ├── .travis.yml ├── CREDITS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── README.md ├── dinobuildr_legacy.sh ├── dinobuildr.sh ├── production_manifest.json ├── ambient_display_manifest.json ├── LICENSE ├── dino_engine.py.bak ├── dino_engine_legacy.py └── dino_engine.py /.gitignore: -------------------------------------------------------------------------------- 1 | build-temp/ 2 | -------------------------------------------------------------------------------- /resources/.gitattributes: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/corsica/.gitattributes: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/wallpaper-firefox-file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/dinobuildr/HEAD/resources/wallpaper-firefox-file.png -------------------------------------------------------------------------------- /resources/corsica/corsica-user-file.pkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/dinobuildr/HEAD/resources/corsica/corsica-user-file.pkg -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | repo/kcpassword filter=lfs diff=lfs merge=lfs -text 2 | repo/corsica/kcpassword filter=lfs diff=lfs merge=lfs -text 3 | -------------------------------------------------------------------------------- /resources/desktop-background-2023-UHD.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/dinobuildr/HEAD/resources/desktop-background-2023-UHD.jpg -------------------------------------------------------------------------------- /resources/corsica/corsica-user.pkg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:810422bed023a2812182e1870d38aa04a6cccf08d12cc1a20a94f6dd7bb8bbef 3 | size 9041 4 | -------------------------------------------------------------------------------- /resources/wallpaper-firefox.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:8bfcd15ba06b76335e8dd54c547f3778ea064479b34f56f51a40ad75ee5d4f19 3 | size 118041 4 | -------------------------------------------------------------------------------- /resources/deploy.properties: -------------------------------------------------------------------------------- 1 | ###taken from https://support.code42.com/Administrator/Cloud/Planning_and_installing/Manage_app_installations_in_your_Code42_environment/Deployment_script_and_command_reference 2 | 3 | CP_SILENT=true 4 | 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | matrix: 2 | include: 3 | - language: shell 4 | script: 5 | - bash -c 'shopt -s globstar; shellcheck **/*.sh' 6 | - language: python 7 | python: 2.7 8 | script: 9 | - pip install flake8 10 | - flake8 --max-line-length 100 . 11 | -------------------------------------------------------------------------------- /CREDITS: -------------------------------------------------------------------------------- 1 | dinobuildr was developed by Lucius Bono, Tristan Thomas, and Vanessa White with 2 | the help and support of 3 | 4 | Mozilla IT: 5 | - End User Services 6 | - Enterprise Information Security 7 | - Systems 8 | 9 | As well as: 10 | Nick Kalister 11 | Shafer Stockton 12 | Ben Martel 13 | Michael Cooper 14 | 15 | and the MacAdmins community / Slack 16 | -------------------------------------------------------------------------------- /resources/disable-guest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # Disable the guest account via plist 8 | 9 | defaults write /Library/Preferences/com.apple.loginwindow GuestEnabled -bool FALSE 10 | 11 | exit 0 12 | -------------------------------------------------------------------------------- /resources/corsica/power-settings.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # Disable system sleep 8 | pmset sleep 0 9 | 10 | # Disable display sleep 11 | pmset displaysleep 0 12 | 13 | # Enable automatic restart on power loss 14 | pmset autorestart 1 -------------------------------------------------------------------------------- /resources/firewall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # This script enables the firewall. 8 | 9 | defaults write /Library/Preferences/com.apple.alf globalstate -int 1 || { 10 | echo "Failed to enable the firewall." 11 | exit 200 12 | } 13 | -------------------------------------------------------------------------------- /resources/update-macos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # suspend background updates for 4 hours because overkill. background updates 8 | # start back up after a reboot automatically. 9 | 10 | # then, simply grab all available updates and install. 11 | 12 | echo "Checking for macOS updates, this might take a while, please be patient..." 13 | /usr/sbin/softwareupdate --install --all 14 | -------------------------------------------------------------------------------- /resources/app-store-updates.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # Enable automatic updates for the App Store. 8 | 9 | # Enable automatic updates for apps from the App Store 10 | defaults write /Library/Preferences/com.apple.commerce AutoUpdate -bool TRUE 11 | 12 | # Enable automatic macOS updates 13 | defaults write /Library/Preferences/com.apple.commerce AutoUpdateRestartRequired -bool TRUE 14 | -------------------------------------------------------------------------------- /resources/corsica/disable-bluetooth-setup-assistant.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # This script disables Bluetooth Setup Assistant at startup if no 8 | # keyboard, mouse, or trackpad is detected. 9 | 10 | defaults write /Library/Preferences/com.apple.Bluetooth.plist BluetoothAutoSeekKeyboard 0 11 | 12 | defaults write /Library/Preferences/com.apple.Bluetooth.plist BluetoothAutoSeekPointingDevice 0 -------------------------------------------------------------------------------- /resources/remove-pyobjc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # remove pyobjc-framework-SystemConfigruation package 8 | # remove Command line Tools, the user can reinstall it (or xcode) if they want. 9 | # this is to make sure that we don't leave anything behind with dinobuildr 10 | 11 | pip3 uninstall -y pyobjc-framework-SystemConfiguration 12 | pip3 uninstall -y pyobjc-core 13 | pip3 uninstall -y pyobjc-framework-Cocoa 14 | rm -rf /Library/Developer/CommandLineTools -------------------------------------------------------------------------------- /resources/corsica/prompt-computername.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | read -r -d '' applescriptCode <<'EOF' 8 | set hostnamePrompt to text returned of (display dialog "Enter desired hostname:" default answer "") 9 | return hostnamePrompt 10 | EOF 11 | 12 | hostname=$(osascript -e "$applescriptCode"); 13 | 14 | echo "Setting LocalHostName and ComputerName to ${hostname}" 15 | 16 | scutil --set LocalHostName "$hostname" 17 | scutil --set ComputerName "$hostname" 18 | -------------------------------------------------------------------------------- /resources/crashplan_preflight.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | ############ 8 | # This script moves the deploy.properties file into the main /Library 9 | # folder on the system, which as of CrashPlan version 6.9.0 is the best 10 | # location for this file (and ~/Library wasn't working) 11 | 12 | if [ ! -d /Library/Application\ Support/CrashPlan ]; then 13 | mkdir /Library/Application\ Support/CrashPlan 14 | fi 15 | cp "${DINOPATH}/deploy.properties" "/Library/Application Support/CrashPlan" 16 | 17 | -------------------------------------------------------------------------------- /resources/corsica/disable-screensaver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # Credit for most of this code goes to barnesaw from the JAMF Nation forums. 8 | # This script disables the screensaver under the corsica user account. 9 | 10 | MAC_UUID=$(system_profiler SPHardwareDataType | awk -F" " '/UUID/{print $3}') 11 | 12 | for USER_TEMPLATE in "/System/Library/User Template"/*; do 13 | /usr/bin/defaults write "${USER_TEMPLATE}"/Library/Preferences/ByHost/com.apple.screensaver."${MAC_UUID}" "idleTime" -int 0 14 | done 15 | -------------------------------------------------------------------------------- /resources/install-rosetta2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # this script checks for machine processor architecture and 8 | # installs rosetta 2 for arm systems 9 | 10 | processor_architecture=$(uname -p) 11 | if [[ "$processor_architecture" = 'arm' ]]; then 12 | echo "Installing Rosetta 2 for Arm system" 13 | /usr/sbin/softwareupdate --install-rosetta --agree-to-license 14 | exit 0 15 | else 16 | echo "You are using the ${processor_architecture} architecture, Rosetta not needed." 17 | exit 0 18 | fi -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Participation Guidelines 2 | 3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines. 4 | For more details, please read the 5 | [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). 6 | 7 | ## How to Report 8 | For more information on how to report violations of the Community Participation Guidelines, please read our [How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/) page. 9 | 10 | 16 | -------------------------------------------------------------------------------- /resources/corsica/set-donotdisturb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # code provided by barnesaw in the JAMF Nation forums: 8 | # https://www.jamf.com/jamf-nation/discussions/19769/disable-notifcations-via-script-equivalent-to-option-clicking-notifications 9 | 10 | MAC_UUID=$(system_profiler SPHardwareDataType | awk -F" " '/UUID/{print $3}') 11 | 12 | # Setup DND on NotificationCenter 13 | for USER_TEMPLATE in "/System/Library/User Template"/* 14 | do 15 | /usr/bin/defaults write "${USER_TEMPLATE}"/Library/Preferences/ByHost/com.apple.notificationcenterui."${MAC_UUID}" "dndEnd" -float 1379 16 | /usr/bin/defaults write "${USER_TEMPLATE}"/Library/Preferences/ByHost/com.apple.notificationcenterui."${MAC_UUID}" "doNotDisturb" -bool FALSE 17 | /usr/bin/defaults write "${USER_TEMPLATE}"/Library/Preferences/ByHost/com.apple.notificationcenterui."${MAC_UUID}" "dndStart" -float 1380 18 | done 19 | -------------------------------------------------------------------------------- /resources/analytics_settings.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # This script verifies that sharing analytics with Apple and app developers is disabled. 8 | 9 | AUTO_SUBMIT=$(defaults read /Library/Application\ Support/CrashReporter/DiagnosticMessagesHistory.plist AutoSubmit) 10 | THIRD_PARTY_DATA_SUBMIT=$(defaults read /Library/Application\ Support/CrashReporter/DiagnosticMessagesHistory.plist ThirdPartyDataSubmit) 11 | 12 | if [[ ${AUTO_SUBMIT} -eq 1 ]]; then 13 | defaults write /Library/Application\ Support/CrashReporter/DiagnosticMessagesHistory.plist AutoSubmit -boolean false 14 | 15 | if [[ ${THIRD_PARTY_DATA_SUBMIT} -eq 1 ]]; then 16 | defaults write /Library/Application\ Support/CrashReporter/DiagnosticMessagesHistory.plist ThirdPartyDataSubmit -boolean false 17 | fi 18 | chmod a+r /Library/Application\ Support/CrashReporter/DiagnosticMessagesHistory.plist 19 | chown root:admin /Library/Application\ Support/CrashReporter/DiagnosticMessagesHistory.plist 20 | fi 21 | -------------------------------------------------------------------------------- /resources/firefox-bookmarks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # This script imports bookmarks into Firefox using the Firefox policy engine. 8 | 9 | user=$(python -c ' 10 | from SystemConfiguration import SCDynamicStoreCopyConsoleUser; 11 | import sys; 12 | username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; 13 | username = [username,""][username in [u"loginwindow", None, u""]]; 14 | sys.stdout.write(username + "\n");') 15 | 16 | mkdir /Applications/Firefox.app/Contents/Resources/distribution 17 | chown "${user}" /Applications/Firefox.app/Contents/Resources/distribution 18 | 19 | cat > /Applications/Firefox.app/Contents/Resources/distribution/policies.json <<- "EOF" 20 | { 21 | "policies": { 22 | "DisplayBookmarksToolbar": true, 23 | "NoDefaultBookmarks": true, 24 | "Bookmarks": [ 25 | { 26 | "Title": "Mozilla SSO", 27 | "URL": "https://sso.mozilla.com", 28 | "Placement": "toolbar" 29 | } 30 | ] 31 | } 32 | } 33 | EOF 34 | 35 | chown "${user}" /Applications/Firefox.app/Contents/Resources/distribution/policies.json 36 | -------------------------------------------------------------------------------- /resources/corsica/firefox-settings.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # This script disables the default browser check and sets Corsica as 8 | # the homepage using the Firefox policy engine. The homepage policy 9 | # requires Firefox ESR. 10 | 11 | mkdir /Applications/Firefox.app/Contents/Resources/distribution 12 | 13 | ( 14 | set -e 15 | cat > /Applications/Firefox.app/Contents/Resources/distribution/policies.json <<- "EOF" 16 | { 17 | "policies": { 18 | "OverrideFirstRunPage": "", 19 | "OverridePostUpdatePage": "", 20 | "DontCheckDefaultBrowser": true, 21 | "Homepage": { 22 | "URL": "https://corsica.mozilla.io/" 23 | }, 24 | "Preferences": { 25 | "browser.tabs.warnOnClose": false 26 | } 27 | } 28 | } 29 | EOF 30 | set +e 31 | ) 32 | 33 | mkdir /Users/Shared/corsica-profile 34 | 35 | ( 36 | set -e 37 | cat > /Users/Shared/corsica-profile/prefs.js <<- "EOF" 38 | user_pref("full-screen-api.allow-trusted-requests-only", false); 39 | user_pref("media.autoplay.default", 0); 40 | EOF 41 | set +e 42 | ) 43 | 44 | /bin/chmod -R 777 /Users/Shared/corsica-profile/ 45 | -------------------------------------------------------------------------------- /resources/set-computername.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # This script sets the LocalHostName and the ComputerName on a machine using 8 | # the following naming convention: [username]-[last 6 digits of the serial 9 | # number]. We also make sure the we don't use any capital letters because 10 | # Tristan hates them. 11 | 12 | lastSNdigits=$(system_profiler SPHardwareDataType | 13 | grep 'Serial Number (system)' | 14 | awk '{print $NF}' | 15 | tail -c 7) 16 | user=$(python -c ' 17 | from SystemConfiguration import SCDynamicStoreCopyConsoleUser; 18 | import sys; 19 | username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; 20 | username = [username,""][username in [u"loginwindow", None, u""]]; 21 | sys.stdout.write(username + "\n");') 22 | 23 | hostname="${user}-${lastSNdigits}" 24 | hostname=$(echo "$hostname" | tr '[:upper:]' '[:lower:]') 25 | 26 | echo "Setting LocalHostName and ComputerName to ${hostname}" 27 | 28 | scutil --set LocalHostName "$hostname" 29 | scutil --set ComputerName "$hostname" 30 | scutil --set HostName "$hostname" 31 | -------------------------------------------------------------------------------- /resources/corsica/skip-siri-setup.mobileconfig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PayloadIdentifier 6 | 1CED00D4-CDB0-4545-9C4D-903E3247351C 7 | PayloadRemovalDisallowed 8 | 9 | PayloadScope 10 | System 11 | PayloadType 12 | Configuration 13 | PayloadUUID 14 | 1CED00D4-CDB0-4545-9C4D-903E3247351C 15 | PayloadOrganization 16 | Mozilla Corporation 17 | PayloadVersion 18 | 1 19 | PayloadDescription 20 | Skips the Siri Setup pop-up window 21 | PayloadDisplayName 22 | Skip Siri Setup 23 | PayloadContent 24 | 25 | 26 | PayloadType 27 | com.apple.SetupAssistant.managed 28 | PayloadVersion 29 | 1 30 | PayloadIdentifier 31 | 8F15320D-31BC-4CF7-9965-7465AC50B39B 32 | PayloadEnabled 33 | 34 | PayloadUUID 35 | 8F15320D-31BC-4CF7-9965-7465AC50B39B 36 | PayloadDisplayName 37 | Login Window 38 | SkipSiriSetup 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /resources/corsica/skip-icloud-setup.mobileconfig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PayloadIdentifier 6 | C933F5EA-CAD4-4268-BBC0-ADB55465926B 7 | PayloadRemovalDisallowed 8 | 9 | PayloadScope 10 | System 11 | PayloadType 12 | Configuration 13 | PayloadUUID 14 | C933F5EA-CAD4-4268-BBC0-ADB55465926B 15 | PayloadOrganization 16 | Mozilla Corporation 17 | PayloadVersion 18 | 1 19 | PayloadDescription 20 | Skips the iCloud Setup pop-up window 21 | PayloadDisplayName 22 | Skip iCloud Setup 23 | PayloadContent 24 | 25 | 26 | PayloadType 27 | com.apple.SetupAssistant.managed 28 | PayloadVersion 29 | 1 30 | PayloadIdentifier 31 | 7BC6F6C1-1263-44E0-AF88-B7176383F61A 32 | PayloadEnabled 33 | 34 | PayloadUUID 35 | 7BC6F6C1-1263-44E0-AF88-B7176383F61A 36 | PayloadDisplayName 37 | Login Window 38 | SkipCloudSetup 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /resources/corsica/skip-data-privacy-setup.mobileconfig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PayloadIdentifier 6 | 2F1B7BDB-C2EC-4D07-8EB5-F83D3F1E703F 7 | PayloadRemovalDisallowed 8 | 9 | PayloadScope 10 | System 11 | PayloadType 12 | Configuration 13 | PayloadUUID 14 | 2F1B7BDB-C2EC-4D07-8EB5-F83D3F1E703F 15 | PayloadOrganization 16 | Mozilla Corporation 17 | PayloadVersion 18 | 1 19 | PayloadDescription 20 | Skips the Apple Privacy pop-up window 21 | PayloadDisplayName 22 | Skip Apple Privacy Message 23 | PayloadContent 24 | 25 | 26 | PayloadType 27 | com.apple.SetupAssistant.managed 28 | PayloadVersion 29 | 1 30 | PayloadIdentifier 31 | 4C3AFBD4-211F-46A6-81E3-168C88E443B4 32 | PayloadEnabled 33 | 34 | PayloadUUID 35 | 4C3AFBD4-211F-46A6-81E3-168C88E443B4 36 | PayloadDisplayName 37 | Login Window 38 | SkipPrivacySetup 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /resources/macos-versioncheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # this script checks for a minimum OS version and is intended to halt the build 8 | # if the machine does not meet that minimum version 9 | 10 | # minimum_os - OS family version 11 | # minimum_major - minimum major version (15 = Catalina) 12 | # minimum_minor - minimum minor version 13 | 14 | # minimum is Catalina 10.15.0 15 | 16 | minimum_os="10" 17 | minimum_major="15" 18 | minimum_minor="0" 19 | 20 | os_version=$(sw_vers -productVersion | awk -F '.' '{print $1}') 21 | major_version=$(sw_vers -productVersion | awk -F '.' '{print $2}') 22 | minor_version=$(sw_vers -productVersion | awk -F '.' '{print $3}') 23 | 24 | if [[ "$minor_version" -eq '' ]]; then 25 | minor_version=0 26 | fi 27 | 28 | if ! [[ "$os_version" -ge "$minimum_os" ]]; then 29 | if ! [[ "$os_version" -eq "$minimum_os" && "$major_version" -ge "$minimum_major" ]]; then 30 | echo "UPGRADE REQUIRED: You are running macOS ${os_version}.${major_version}.${minor_version}" 31 | echo "We are expecting at least: ${minimum_os}.${minimum_major}.${minimum_minor}" 32 | echo "The build will halt, please update macOS via the App Store and try again." 33 | exit 1 34 | fi 35 | else 36 | echo "You are running macOS ${os_version}.${major_version}.${minor_version}, which meets the minimum requirements." 37 | exit 0 38 | fi 39 | -------------------------------------------------------------------------------- /resources/corsica/skip-appearence-window.mobileconfig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PayloadContent 6 | 7 | 8 | PayloadDescription 9 | Configures Setup Assistant settings 10 | PayloadDisplayName 11 | Setup Assistant 12 | PayloadIdentifier 13 | com.mozilla.skipappearance.6DBE3179-5168-443A-A61F-5E681D492361.com.apple.SetupAssistant.managed.0665E809-53D9-4365-917C-4A601E5CC1DF 14 | PayloadOrganization 15 | 16 | PayloadType 17 | com.apple.SetupAssistant.managed 18 | PayloadUUID 19 | 0665E809-53D9-4365-917C-4A601E5CC1DF 20 | PayloadVersion 21 | 1 22 | SkipAppearance 23 | 24 | 25 | 26 | PayloadDescription 27 | This profile skips the appearence selector window in the setup assistant. 28 | PayloadDisplayName 29 | Skip Appearence Window 30 | PayloadIdentifier 31 | com.mozilla.skipappearance.6DBE3179-5168-443A-A61F-5E681D492361 32 | PayloadOrganization 33 | Mozilla Corporation 34 | PayloadScope 35 | User 36 | PayloadType 37 | Configuration 38 | PayloadUUID 39 | 6DBE3179-5168-443A-A61F-5E681D492361 40 | PayloadVersion 41 | 1 42 | 43 | 44 | -------------------------------------------------------------------------------- /resources/screensaver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # This script disables the screensaver while dinobuildr is running 8 | # and creates a LaunchAgent. The LaunchAgents runs at the next login 9 | # and re-enables the screensaver to start after five minutes and 10 | # then self-destructs. 11 | 12 | user=$(python -c ' 13 | from SystemConfiguration import SCDynamicStoreCopyConsoleUser; 14 | import sys; 15 | username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; 16 | username = [username,""][username in [u"loginwindow", None, u""]]; 17 | sys.stdout.write(username + "\n");') 18 | 19 | sudo -u "${user}" defaults -currentHost write com.apple.screensaver idleTime 0 20 | 21 | mkdir ~/Library/LaunchAgents 22 | chown "${user}" ~/Library/LaunchAgents 23 | 24 | cat > ~/Library/LaunchAgents/Enable_Screensaver.plist <<- "EOF" 25 | 26 | 27 | 28 | 29 | Label 30 | EnableScreensaver 31 | ProgramArguments 32 | 33 | bash 34 | -c 35 | defaults -currentHost write com.apple.screensaver idleTime 300; 36 | rm -rf ~/Library/LaunchAgents/Enable_Screensaver.plist 37 | 38 | RunAtLoad 39 | 40 | 41 | 42 | EOF 43 | 44 | chown "${user}" ~/Library/LaunchAgents/Enable_Screensaver.plist 45 | -------------------------------------------------------------------------------- /resources/dock-config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # shellcheck disable=SC2088 4 | 5 | # This Source Code Form is subject to the terms of the Mozilla Public 6 | # License, v. 2.0. If a copy of the MPL was not distributed with this 7 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | # dockutil is created by kcrawford and is licensed under the Apache 2.0 10 | # license. please see https://github.com/kcrawford/dockutil for more 11 | # details. 12 | 13 | # This script configured the dock in macOS using dockutil. It does not 14 | # install dockutil, it loads the script from a specfic github commit 15 | # that has been tested and hashed by Mozilla IT. NOTE: Do not change 16 | # the hash or the url without testing. 17 | 18 | hash="c49ba68db90f7ac3a50a490e597078df6de6a8feba16c24ccced7b34d84f705e" # change only after thorough testing 19 | dockutil=$(curl -fsSL https://raw.githubusercontent.com/mozilla/dockutil/26b13d380f67dc79251cf0ea6280dbaa603308be/scripts/dockutil) 20 | 21 | if [ "$(echo "$dockutil" | shasum -a 256 | awk '{print $1}')" == $hash ]; then # if the hashes match then proceed 22 | echo "Executing dockutil - hash matches expected value." 23 | python -c "$dockutil" --remove all --no-restart 24 | python -c "$dockutil" --add "/System/Applications/Launchpad.app" --position beginning --no-restart 25 | python -c "$dockutil" --add "/Applications/Firefox.app" --after Launchpad --no-restart 26 | python -c "$dockutil" --add "/Applications/Slack.app" --after Firefox --no-restart 27 | python -c "$dockutil" --add "/Applications/zoom.us.app" --after Slack --no-restart 28 | python -c "$dockutil" --add "~/Downloads" --view fan --display stack --section others --no-restart 29 | python -c "$dockutil" --add "/System/Applications/System Preferences.app" --position end 30 | else 31 | echo "Dockutil hash does not match intended value. Aborting." 32 | exit 1 33 | fi 34 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to dinobuildr 2 | 3 | Anyone may contribute issues or code to dinobuildr as long as the following guidelines are adhered to. 4 | 5 | 1) All contributors are expected to follow the Mozilla Community Participation Guidelines outlined at https://www.mozilla.org/en-US/about/governance/policies/participation/ 6 | 7 | 2) Issue naming schema (Recommended): 8 | * The following are naming conventions already in production. There may also be open issues that do not conform to the schema as well. 9 | * Bug: [short description of the bug] 10 | * Documentation: [short description of requested documentation] 11 | * Feature: [short description of the feature] 12 | * Update: [short description of the update] 13 | * Update should be used for changes to existing Documentation, Features, etc 14 | Please note: other issue types can be created as needed, per the discretion of Mozilla staff maintaining the project 15 | 16 | 3) Naming branches: 17 | * Branch names should be written in kebab-case (https://en.wikipedia.org/wiki/Kebab_case) and should and should follow the following pattern: 18 | * bug-shortbugname 19 | * doc-shortdocummentname 20 | * feat-shortfeaturename 21 | * upd8-shortupdatename 22 | * Kebab-case helps distinguish between files in the repositories, which are mostly in snake_case (and will primarly transition entirely to snake_case with future commits) 23 | 24 | 4) Commits: 25 | * Each commit should be small, complete, and isolated to a specific change. 26 | * In example: 27 | * If you're updating the link to the DMG installer of a piece of software the changes in the commit should be limited to updating the path for the installer in the appropriate *_manifest.json and the hash for the DMG. 28 | * A second commit should then be performed to update the hash of the *_manifest.json file in dino_engine.py 29 | 30 | 5) Pull Requests: 31 | * Pull requests should reflect a tightly focused group of commits that serve a singular purpose, like updating the files associated with a particular software download, or naming convention change. 32 | * Pull Request naming should reflect the changes that were made 33 | * Pull Request comments should explain the changes that were made in the commits that made up the PR 34 | * Pull Requests must be reviewed by at least one additionl project member, and can only be merged by project admins 35 | -------------------------------------------------------------------------------- /resources/userInfo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # shellcheck disable=SC2034 4 | # 5 | # Optional script by which you can control the user installation. 6 | # 7 | # Variables to set: 8 | # startDesktop: set to false if you don't want the desktop to start up immediately after installation 9 | # CP_USER_HOME: Allows the app to start scanning the user's home folder immediately after installation 10 | # user: Used to properly set file permissions 11 | # userGroup: Also used for file permissions 12 | # CP_USER_NAME: This will become the unique ID for the user in the PROe Server database. 13 | # Leave CP_USER_NAME blank to require the user to enter it. 14 | # If set this value, you'll want to set the username="" attribute of tag in default.service.xml to username="${username}" 15 | # 16 | 17 | # 18 | # Set to false if you don't want the desktop UI to start up. 19 | # 20 | startDesktop=false 21 | 22 | # 23 | # When installing from the root account (for example) you will need to populate 24 | # some or all of these variables differently than is done below. 25 | # Note: whoami *always* returns "root" for this package so we had to get creative to find the installing user. 26 | # Also: You will want to populate CP_USER_NAME with the right email address unless you don't want your users or admins receiving reports and alerts. 27 | # 28 | CP_USER_HOME="$HOME" 29 | user=$(basename "$CP_USER_HOME") 30 | userGroup=$(id -gn "$user") 31 | CP_USER_NAME="$user" 32 | 33 | 34 | # 35 | # Users have suggested alternate ways of finding the user name and email address. 36 | # The following examples may work better for your situation. 37 | # 38 | #user=`last -1 | awk '{print $1}'` 39 | 40 | # This assumes the username is the last part of the home folder name 41 | #user=`basename "$CP_USER_HOME"` 42 | 43 | # This parses the user from the computer hostname 44 | # Because the APL naming convention uses the name of the owner in the computer name we will use this 45 | # to derive the primary user name. So the primary user does not have to be logged in for this to work. 46 | #computerName=`scutil --get ComputerName` 47 | #user=${computerName%%-*} 48 | 49 | # This finds the email address from AD or LDAP 50 | #dsclEmail=`dscl /Search read /Users/$user mail` 51 | #CP_USER_NAME=${dsclEmail##*mail: } 52 | 53 | # Run As User 54 | # Uncomment the following line if you want the service to run as user instead of root. 55 | #touch "${TMPDIR}/.cpRunAsUser" 56 | -------------------------------------------------------------------------------- /resources/corsica/auto-update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # This script creates a LaunchDaemon / script pair that will check 8 | # for updates on a schedule and reboot the machine. 9 | # It has a canary service called Informant that will interrupt the 10 | # updater if certain conditions are met. 11 | 12 | cat > /usr/local/auto-update-routine.sh <<- "EOF" 13 | #!/bin/bash 14 | if [ "$(curl https://raw.githubusercontent.com/mozilla-it/informant/master/informant.txt)" = "greenlight" ]; then 15 | softwareupdate -ia 16 | fi 17 | 18 | shutdown -r now 19 | 20 | if [ "$1" = "canary" ]; then 21 | cat > /Library/LaunchDaemons/update-ff-macos.plist <<- "EOF2" 22 | 23 | 24 | 25 | 26 | Label 27 | update-firefox-macos 28 | Program 29 | /usr/local/auto-update-routine.sh 30 | StartCalendarInterval 31 | 32 | Hour 33 | 3 34 | Minute 35 | 00 36 | 37 | 38 | 39 | EOF2 40 | fi 41 | EOF 42 | 43 | chmod +x /usr/local/auto-update-routine.sh 44 | 45 | cat > /Library/LaunchDaemons/update-ff-macos.plist <<- "EOF" 46 | 47 | 48 | 49 | 50 | Label 51 | update-firefox-macos 52 | Program 53 | /usr/local/auto-update-routine.sh 54 | StartCalendarInterval 55 | 56 | 57 | Day 58 | 1 59 | Hour 60 | 3 61 | Minute 62 | 00 63 | 64 | 65 | Day 66 | 15 67 | Hour 68 | 3 69 | Minute 70 | 00 71 | 72 | 73 | 74 | 75 | EOF 76 | 77 | launchctl unload /Library/LaunchDaemons/update-ff-macos.plist 78 | launchctl load /Library/LaunchDaemons/update-ff-macos.plist 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dinobuildr 2 | 3 | ## dinobuildr at Mozilla 4 | The dinobuildr project is the current production macOS deployment and configuration tool at Mozilla. All Mozilla IT deployed Macs use this repo for initial system configuration via the following procedure: 5 | 6 | 1. Install and/or update to the latest revision of the current release of macOS using Apple sanctioned installation methods 7 | 2. Follow the macOS Setup Assistant to create a user account for the person that will be receiving the machine, and set some basic configurations 8 | 3. Utilize the `dinobuildr.sh` script to pull down a verified commit of the `dino_engine.py` configuration script and run it on behalf of the user account created in step 2 9 | 4. Enable Filevault and ensure that the password for the user account is set to something sufficiently random and complicated and hand over the machine to the person who requested it 10 | 11 | dinobuildr is intended to be a transparent, reliable, and auditable deployment solution. Anyone may inspect the automated components of our build, review what software is being deployed by default, and what configuration changes are made to a machine. Unlike most deployment and configuration management solutions dinobuildr is intended to be easy to understand, contribute to, and to audit. It does not rely on management binaries or other artifacts to work as it operates using Python 2.7 (which is built into macOS) and uses no external Python libraries. All configuration scripts exist in code that has been written and audited by Mozilla IT and all software packages come from trusted sources and are independently hashed by Mozilla IT. 12 | 13 | ## Background 14 | dinobuildr is a macOS deployment utility developed by Mozilla IT. It provides a relatively flexible framework for deploying software and shell scripts to macOS clients running relatively new versions of macOS; relying on public-facing infrastructure such as Github and official vendor binary repositories that are exposed over the internet to deliver a consistent configuration. It is intended to be straightforward, simple, and is not feature rich - instead it offers a level of simplicity and transparency that may be useful in certain environments. 15 | 16 | dinobuildr relies on a JSON manifest to specify the actions the build will take (and it what order) as well as providing URLs and SHA256 hash values for all the scripts, files, and packages in the build. Updating a package is generally as straightforward as changing the version and hash attributes in the JSON manifest. The current version of dinobuildr supports hosting arbitrary files, scripts, pkg files, and dmg files in the following locations: 17 | 18 | * **Arbitrary Files** - Github LFS 19 | * **.pkg** - Github LFS, HTTP(S)/FTP 20 | * **.dmg** - HTTP(S)/FTP 21 | * **Bash Scripts** - Github 22 | * **.mobileconfig Files** - Github 23 | -------------------------------------------------------------------------------- /resources/corsica/launch-firefox.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # This script creates a LaunchAgent and LaunchDaemon. The LaunchAgent 8 | # launches Firefox in fullscreen mode under the corsica user at login. 9 | # The LaunchDaemon prevents Firefox from automatically relaunching 10 | # after a reboot, shutdown, or power outage. The LaunchDaemon also 11 | # clears the previous Firefox session before Firefox launches again. 12 | 13 | cat > /Users/Shared/move-launch-firefox.sh <<- "EOF" 14 | #!/bin/bash 15 | 16 | if [ ! -d /Users/corsica/Library/LaunchAgents ]; then 17 | mkdir /Users/corsica/Library/LaunchAgents 18 | chown corsica /Users/corsica/Library/LaunchAgents 19 | fi 20 | 21 | mv /Users/Shared/launch-firefox.plist /Users/corsica/Library/LaunchAgents/launch-firefox.plist 22 | chown corsica /Users/corsica/Library/LaunchAgents/launch-firefox.plist 23 | 24 | chown -R corsica:staff /Applications/Firefox.app/ 25 | 26 | rm -rf /Library/LaunchDaemons/move-launch-firefox.plist 27 | 28 | rm -rf "$0" 29 | EOF 30 | 31 | chmod +x /Users/Shared/move-launch-firefox.sh 32 | 33 | cat > /Library/LaunchDaemons/move-launch-firefox.plist <<- "EOF" 34 | 35 | 36 | 37 | 38 | Label 39 | move-launch-firefox 40 | Program 41 | /Users/Shared/move-launch-firefox.sh 42 | WatchPaths 43 | 44 | /Users/corsica/Library 45 | 46 | 47 | 48 | EOF 49 | 50 | cat > /Users/Shared/launch-firefox.plist <<- "EOF" 51 | 52 | 53 | 54 | 55 | Label 56 | launch-firefox 57 | ProgramArguments 58 | 59 | /usr/bin/open 60 | -a 61 | /Applications/Firefox.app 62 | --args 63 | --foreground 64 | --profile 65 | /Users/Shared/corsica-profile/ 66 | 67 | RunAtLoad 68 | 69 | 70 | 71 | EOF 72 | 73 | cat > /Library/LaunchDaemons/reset-firefox-session.plist <<- "EOF" 74 | 75 | 76 | 77 | 78 | Label 79 | reset-firefox-session 80 | ProgramArguments 81 | 82 | bash 83 | -c 84 | rm -rf /Users/Shared/corsica-profile/sessionstore*; 85 | rm -rf /Users/corsica/Library/Preferences/ByHost/com.apple.loginwindow.* 86 | 87 | RunAtLoad 88 | 89 | 90 | 91 | EOF -------------------------------------------------------------------------------- /dinobuildr_legacy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # Written by Lucius Bono and Tristan Thomas 8 | 9 | # This script grabs the latest config.py script from the dinobuilder 10 | # Github repo (https://github.com/mozilla/dinobuildr). At this time, 11 | # we blindly trust the master branch, but later we hope to put more 12 | # elaborate checks in place to ensure that the script is legit. 13 | 14 | # Accepts optional parameters that specifies the branch and manifest 15 | # to build against. 16 | 17 | branch=master 18 | manifest=production_manifest.json 19 | repo=dinobuildr 20 | org=mozilla 21 | dino_engine=dino_engine_legacy.py 22 | 23 | while :; do 24 | case $1 in 25 | -b|--branch) branch=$2 26 | shift 27 | ;; 28 | -m|--manifest) manifest=$2 29 | shift 30 | ;; 31 | -r|--repo) repo=$2 32 | shift 33 | ;; 34 | -o|--org) org=$2 35 | shift 36 | ;; 37 | *) break 38 | esac 39 | shift 40 | done 41 | 42 | if [ "$branch" != '' ]; then 43 | if [[ "$branch" =~ [^a-zA-Z0-9{-.}] ]]; then 44 | echo "********************************************************************" 45 | echo "Branch name must be numbers, letters, - and . only." 46 | exit 1 47 | fi 48 | fi 49 | 50 | if [ "$manifest" != '' ]; then 51 | if [[ "$manifest" =~ [^a-zA-Z0-9{._}] ]]; then 52 | echo "********************************************************************" 53 | echo "Manifest name must be numbers, letters, . and _ only" 54 | exit 1 55 | fi 56 | fi 57 | 58 | if [ "$repo" != '' ]; then 59 | if [[ "$repo" =~ [^a-zA-Z0-9{._}] ]]; then 60 | echo "********************************************************************" 61 | echo "Repo name must be numbers, letters, . and _ only" 62 | exit 1 63 | fi 64 | fi 65 | 66 | if [ "$org" != '' ]; then 67 | if [[ "$org" =~ [^a-zA-Z0-9{._}] ]]; then 68 | echo "********************************************************************" 69 | echo "Org name must be numbers, letters, . and _ only" 70 | exit 1 71 | fi 72 | fi 73 | 74 | printf "\nPulling down dinobuildr from the [%s] branch from the [%s] repo in the [%s] org on github. 75 | \n\n" "$branch" "$repo" "$org" 76 | 77 | build_script=$(curl -f https://raw.githubusercontent.com/"$org"/"$repo"/"$branch"/"$dino_engine") 78 | curl_status=$? 79 | 80 | # If curl fails for some reason, we return it's non-zero exit code so that the 81 | # script can fail in a predictable way. 82 | 83 | if [ $curl_status -eq 0 ]; then 84 | printf "\nStarting the build!\n" 85 | if python -c "$build_script" -b "$branch" -m "$manifest" -r "$repo" -o "$org"; then 86 | echo "Rebooting!" 87 | osascript -e 'tell app "System Events" to restart' 88 | else 89 | echo "********************************************************************" 90 | echo "HOUSTON WE HAVE A PROBLEM: Dinobuildr did not complete successfully." 91 | echo "Please review the error and run the this build again." 92 | fi 93 | else 94 | echo "********************************************************************" 95 | echo "Uh oh, unable to download Dinobuildr from Github." 96 | if [ $curl_status -eq 6 ]; then 97 | echo "Check your internet connection and try again!" 98 | elif [ $curl_status -eq 22 ]; then 99 | echo "We can't find the branch or repo you're trying to use. Please try again!" 100 | fi 101 | exit 1 102 | fi 103 | -------------------------------------------------------------------------------- /dinobuildr.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # Written by Lucius Bono and Tristan Thomas 8 | 9 | # This script grabs the latest config.py script from the dinobuilder 10 | # Github repo (https://github.com/mozilla/dinobuildr). At this time, 11 | # we blindly trust the master branch, but later we hope to put more 12 | # elaborate checks in place to ensure that the script is legit. 13 | 14 | # Accepts optional parameters that specifies the branch and manifest 15 | # to build against. 16 | 17 | branch=master 18 | manifest=production_manifest.json 19 | repo=dinobuildr 20 | org=mozilla 21 | dino_engine=dino_engine.py 22 | 23 | while :; do 24 | case $1 in 25 | -b|--branch) branch=$2 26 | shift 27 | ;; 28 | -m|--manifest) manifest=$2 29 | shift 30 | ;; 31 | -r|--repo) repo=$2 32 | shift 33 | ;; 34 | -o|--org) org=$2 35 | shift 36 | ;; 37 | *) break 38 | esac 39 | shift 40 | done 41 | 42 | if [ "$branch" != '' ]; then 43 | if [[ "$branch" =~ [^a-zA-Z0-9{-.}] ]]; then 44 | echo "********************************************************************" 45 | echo "Branch name must be numbers, letters, - and . only." 46 | exit 1 47 | fi 48 | fi 49 | 50 | if [ "$manifest" != '' ]; then 51 | if [[ "$manifest" =~ [^a-zA-Z0-9{._}] ]]; then 52 | echo "********************************************************************" 53 | echo "Manifest name must be numbers, letters, . and _ only" 54 | exit 1 55 | fi 56 | fi 57 | 58 | if [ "$repo" != '' ]; then 59 | if [[ "$repo" =~ [^a-zA-Z0-9{._}] ]]; then 60 | echo "********************************************************************" 61 | echo "Repo name must be numbers, letters, . and _ only" 62 | exit 1 63 | fi 64 | fi 65 | 66 | if [ "$org" != '' ]; then 67 | if [[ "$org" =~ [^a-zA-Z0-9{._}] ]]; then 68 | echo "********************************************************************" 69 | echo "Org name must be numbers, letters, . and _ only" 70 | exit 1 71 | fi 72 | fi 73 | 74 | 75 | printf "\nInstalling Command Line Tools for python3 - please click through the prompt to install" 76 | # see if we can change to using applescript to install unattended 77 | # as is, the current script exits with an error while the installation is in progress, so 78 | # the tech will need to run dinobuildr again 79 | xcode-select --install 80 | 81 | printf "\nInstalling the pyobjc-framework-SystemConfiguration package for system management" 82 | pip3 install pyobjc-framework-SystemConfiguration 83 | 84 | 85 | printf "\nPulling down dinobuildr from the [%s] branch from the [%s] repo in the [%s] org on github. 86 | \n\n" "$branch" "$repo" "$org" 87 | 88 | build_script=$(curl -f https://raw.githubusercontent.com/"$org"/"$repo"/"$branch"/"$dino_engine") 89 | curl_status=$? 90 | 91 | # If curl fails for some reason, we return it's non-zero exit code so that the 92 | # script can fail in a predictable way. 93 | 94 | if [ $curl_status -eq 0 ]; then 95 | printf "\nStarting the build!\n" 96 | if python3 -c "$build_script" -b "$branch" -m "$manifest" -r "$repo" -o "$org"; then 97 | echo "Rebooting!" 98 | osascript -e 'tell app "System Events" to restart' 99 | else 100 | echo "********************************************************************" 101 | echo "HOUSTON WE HAVE A PROBLEM: Dinobuildr did not complete successfully." 102 | echo "Please review the error and run the this build again." 103 | fi 104 | else 105 | echo "********************************************************************" 106 | echo "Uh oh, unable to download Dinobuildr from Github." 107 | if [ $curl_status -eq 6 ]; then 108 | echo "Check your internet connection and try again!" 109 | elif [ $curl_status -eq 22 ]; then 110 | echo "We can't find the branch or repo you're trying to use. Please try again!" 111 | fi 112 | exit 1 113 | fi 114 | -------------------------------------------------------------------------------- /resources/wallpaper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # this script copies a wall-paper image from the temp build cache to a permanent 8 | # location and then either pulls down Steve Ward's excellent script for setting the 9 | # wallpaper in macOS Mojave, or uses the old tried and true Applescript method 10 | # of setting the wallpaper, depending on the OS version this script is being run 11 | # on. 12 | # 13 | # You can see Steve's script the follow github repo: https://github.com/tech-otaku/macos-desktop 14 | # 15 | # We fork the repo so that we can ensure that we have access to a version of 16 | # this project. 17 | # 18 | # Set the filename of the wallpaper file 19 | 20 | WALLPAPER_FILENAME=wallpaper-firefox-file.png 21 | 22 | # Determine which version of macOS we're running 23 | 24 | os_version=$(sw_vers -productVersion | awk -F '.' '{print $1}') 25 | major_version=$(sw_vers -productVersion | awk -F '.' '{print $2}') 26 | minor_version=$(sw_vers -productVersion | awk -F '.' '{print $3}') 27 | 28 | if [[ "$minor_version" -eq '' ]]; then 29 | minor_version=0 30 | fi 31 | 32 | # Copy the wallpaper file to /User/Shared. $DINOPATH is an environment variable 33 | # that dinobuildr itself actually sets in case we change the working directory 34 | # in the future. 35 | 36 | echo "Copying ${DINOPATH}/${WALLPAPER_FILENAME} to /Users/Shared/${WALLPAPER_FILENAME}" 37 | cp "${DINOPATH}/${WALLPAPER_FILENAME}" "/Users/Shared/${WALLPAPER_FILENAME}" 38 | 39 | # If we're running on 10.13 or below, we can use the old Applescript method to 40 | # set the background. 41 | # If we're on 10.14 or above, we use Steve Ward's method for setting the 42 | # wallpaper by pulling down a specific commit of their script from Github. 43 | # We also check the hash of that pinned commit just to be sure. 44 | 45 | if [[ "$os_version" -le "10" && "$major_version" -le "13" ]]; then 46 | echo "Since this is a pre-Mojave machine, we are setting the wallpaper the old-fashioned way." 47 | /usr/bin/osascript -e 'tell application "Finder" to set desktop picture to POSIX file "/Users/Shared/'"$WALLPAPER_FILENAME"'"' 48 | 49 | elif [[ "$os_version" -eq "10" && "$major_version" -eq "14" ]]; then 50 | WALLPAPER_SH=$(curl -fsSL https://raw.githubusercontent.com/mozilla/macos-desktop/810e38873c9c4d63b9d4b35cc81c008c88eac1ca/set-desktop-mojave.sh) 51 | HASH="1b6f7b016731119a83350130a7aef751f8ce5261b494a83a5ecfad3c76b39e02" # change only after thorough testing 52 | 53 | if [ "$(echo "$WALLPAPER_SH" | shasum -a 256 | awk '{print $1}')" == $HASH ]; then # if the hashes match then proceed 54 | echo "We're on Mojave so we're going to use the Mojave way to set the wallpaper." 55 | /bin/bash -c "$WALLPAPER_SH" -s "/Users/Shared/$WALLPAPER_FILENAME" 56 | fi 57 | 58 | elif [[ "$os_version" -eq "10" && "$major_version" -eq "15" ]]; then 59 | WALLPAPER_SH=$(curl -fsSL https://raw.githubusercontent.com/mozilla/macos-desktop/810e38873c9c4d63b9d4b35cc81c008c88eac1ca/set-desktop-catalina.sh) 60 | HASH="a5fd5700616730f3db1af48bf380156a1897197108be359a3c7769b7a359d7c9" # change only after thorough testing 61 | 62 | if [ "$(echo "$WALLPAPER_SH" | shasum -a 256 | awk '{print $1}')" == $HASH ]; then # if the hashes match then proceed 63 | echo "We're on Catalina so we're going to use the Catalina way to set the wallpaper." 64 | /bin/bash -c "$WALLPAPER_SH" -s "/Users/Shared/$WALLPAPER_FILENAME" 65 | fi 66 | elif [[ "$os_version" -ge "11" ]]; then 67 | # temporarily use jlin's set-desktop.sh with merged big sur fixes from upstream 68 | WALLPAPER_SH=$(curl -fsSL https://raw.githubusercontent.com/jlin/macos-desktop/81fb52052cb13480ce49258a70821a0b387cfafb/set-desktop.sh) 69 | HASH="8e425e645e2d4295c9e196e2786578f594d9829494b0084cdd2336c1fd1a2682" # change only after thorough testing 70 | echo "$WALLPAPER_SH" | shasum -a 256 | awk '{print $1}' 71 | if [ "$(echo "$WALLPAPER_SH" | shasum -a 256 | awk '{print $1}')" == $HASH ]; then # if the hashes match then proceed 72 | echo "We're on Big Sur so we're going to try to use the Big Sur way to set the wallpaper." 73 | /bin/bash -c "$WALLPAPER_SH" -s "/Users/Shared/$WALLPAPER_FILENAME" 74 | fi 75 | else 76 | echo "Wallpaper script hash does not match intended value or something else went wrong. Aborting." 77 | exit 1 78 | fi 79 | 80 | -------------------------------------------------------------------------------- /production_manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | { 4 | "item": "Check macOS Version", 5 | "version": "", 6 | "url": "resources/macos-versioncheck.sh", 7 | "filename": "", 8 | "dmg-installer": "", 9 | "dmg-advanced": "", 10 | "hash": "7355c55561dd358ea87ec9cec2fd5213d332195f7c759dcd182e63b9d75b5540", 11 | "type": "shell" 12 | }, 13 | { 14 | "item": "Download Wallpaper Image", 15 | "version": "", 16 | "url": "resources/wallpaper-firefox-file.png", 17 | "filename": "", 18 | "dmg-installer": "", 19 | "dmg-advanced": "", 20 | "hash": "8bfcd15ba06b76335e8dd54c547f3778ea064479b34f56f51a40ad75ee5d4f19", 21 | "type": "file" 22 | }, 23 | { 24 | "item": "Configure Wallpaper", 25 | "version": "", 26 | "url": "resources/wallpaper.sh", 27 | "filename": "", 28 | "dmg-installer": "", 29 | "dmg-advanced": "", 30 | "hash": "5abff4bcbe3cbb607cdd92210cc803629b06cd83cb8ee6f532d2cc17646ed853", 31 | "type": "shell" 32 | }, 33 | { 34 | "item": "Configure Screensaver", 35 | "version": "", 36 | "url": "resources/screensaver.sh", 37 | "filename": "", 38 | "dmg-installer": "", 39 | "dmg-advanced": "", 40 | "hash": "292a2522524b97b8adc8b70b6fea59a737888066c6f1b249b0c9a11c61a23844", 41 | "type": "shell" 42 | }, 43 | { 44 | "item": "Configure ComputerName", 45 | "version": "", 46 | "url": "resources/set-computername.sh", 47 | "filename": "", 48 | "dmg-installer": "", 49 | "dmg-advanced": "", 50 | "hash": "f933954b5e4e82fa3aed0c7d81d6f5659b49eac1129fe331d09ec7afae0e3ceb", 51 | "type": "shell" 52 | }, 53 | { 54 | "item": "Install Rosetta 2 if on an arm system", 55 | "version": "", 56 | "url": "resources/install-rosetta2.sh", 57 | "filename": "", 58 | "dmg-installer": "", 59 | "dmg-advanced": "", 60 | "hash": "324ee98cadce9fc66448f053d43fc7b703ff84e0aec33ab94b6193acb33aeabd", 61 | "type": "shell" 62 | }, 63 | { 64 | "item": "Install Firefox", 65 | "version": "latest", 66 | "url": "https://download.mozilla.org/?product=firefox-${version}-SSL&os=osx&lang=en-US", 67 | "filename": "firefox.dmg", 68 | "dmg-installer": "Firefox.app", 69 | "dmg-advanced": "", 70 | "hash": "autohash-firefox", 71 | "type": "dmg" 72 | }, 73 | { 74 | "item": "Configure Firefox Bookmarks", 75 | "version": "", 76 | "url": "resources/firefox-bookmarks.sh", 77 | "filename": "", 78 | "dmg-installer": "", 79 | "dmg-advanced": "", 80 | "hash": "e714487b93b01721a1a86641141d47430c6a683f5006566ad3c602f099dc8236", 81 | "type": "shell" 82 | }, 83 | { 84 | "item": "Install Slack", 85 | "version": "4.27.154", 86 | "url": "https://downloads.slack-edge.com/releases/macos/${version}/prod/universal/Slack-${version}-macOS.dmg", 87 | "filename": "slack.dmg", 88 | "dmg-installer": "Slack.app", 89 | "dmg-advanced": "", 90 | "hash": "127acf63e15cc9bbb4b3d155da70344eccf246b02fe7187526e92bd355ea1a42", 91 | "type": "dmg" 92 | }, 93 | { 94 | "item": "Configure Firewall", 95 | "version": "", 96 | "url": "resources/firewall.sh", 97 | "filename": "", 98 | "dmg-installer": "", 99 | "dmg-advanced": "", 100 | "hash": "a3476f968ac645dffcecc336a0b735a7f508b30f75c9a3222e211100e58d5c0e", 101 | "type": "shell" 102 | }, 103 | { 104 | "item": "Disable analytics settings", 105 | "version": "", 106 | "url": "resources/analytics_settings.sh", 107 | "filename": "", 108 | "dmg-installer": "", 109 | "dmg-advanced": "", 110 | "hash": "9a88799266ede381335a3ec5c23118aa6404516e6fd3ca805d238dff2eb5eefb", 111 | "type": "shell" 112 | }, 113 | { 114 | "item": "Enable automatic updates", 115 | "version": "", 116 | "url": "resources/app-store-updates.sh", 117 | "filename": "", 118 | "dmg-installer": "", 119 | "dmg-advanced": "", 120 | "hash": "5de09057abdbc563d0cc4f802ab3138d901dd1ba711f7ddcf6de99299a54affb", 121 | "type": "shell" 122 | }, 123 | { 124 | "item": "Disable Guest Login", 125 | "version": "", 126 | "url": "resources/disable-guest.sh", 127 | "filename": "", 128 | "dmg-installer": "", 129 | "dmg-advanced": "", 130 | "hash": "b4d565ca544ffa7559ccfab4d80467316f9c6531f1f7d0b97cc9010553876216", 131 | "type": "shell" 132 | }, 133 | { 134 | "item": "Enable Filevault", 135 | "version": "", 136 | "url": "resources/enable-filevault.sh", 137 | "filename": "", 138 | "dmg-installer": "", 139 | "dmg-advanced": "", 140 | "hash": "536ca494b5c1a980cfbc35ea256c8bfc48f4abfa7daa1aed9b9126e4d156980c", 141 | "type": "shell" 142 | }, 143 | { 144 | "item": "Install Zoom", 145 | "version": "5.11.6.9890", 146 | "url": "https://zoom.us/client/${version}/zoomusInstallerFull.pkg", 147 | "filename": "Zoom.pkg", 148 | "dmg-installer": "", 149 | "dmg-advanced": "", 150 | "hash": "1fbd1204a85bc65404795901bc845364fc5ef5aa70e35657521251c4ae859cfd", 151 | "type": "pkg" 152 | }, 153 | { 154 | "item": "remove pybobjc", 155 | "version": "", 156 | "url": "resources/remove-pyobjc.sh", 157 | "filename": "", 158 | "dmg-installer": "", 159 | "dmg-advanced": "", 160 | "hash": "aa5bd4450c802e76bfb4f55eb97f50713ef3fa94d3884ec33cc7813b574dc923", 161 | "type": "shell" 162 | } 163 | ] 164 | } 165 | -------------------------------------------------------------------------------- /resources/enable-filevault.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # Determine logged in user through the "usual apple way" 8 | 9 | loggedInUser=$(python -c ' 10 | from SystemConfiguration import SCDynamicStoreCopyConsoleUser; 11 | import sys; 12 | username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; 13 | username = [username,""][username in [u"loginwindow", None, u""]]; 14 | sys.stdout.write(username + "\n");') 15 | 16 | # Enable FileVault 2 using the defer option, this will attempt to turn on 17 | # FileVault 2 at the next login/logout and prompt the user for the account 18 | # password that executed this command, then drops the recovery key in a plist 19 | # owned by roo at the location we specify. 20 | # 21 | # We use this method because all other methods of programmatically enabling FV2 22 | # require the user password to be passed to fdesetup and we don't want to 23 | # encourage people to type their account passwords into random dialog boxes. 24 | 25 | echo "Enabling FileVault deffered enrollment." 26 | fdesetup enable -defer /Users/"${loggedInUser}"/Library/fvkey.plist -forceatlogin 0 -dontaskatlogout 27 | 28 | # Generate a LaunchDaemon via heredoc that will execute the chownfvkey.sh that 29 | # we will write later in this script. 30 | 31 | cat > /Library/LaunchDaemons/com.mozilla-it.chownfvkey.plist<<-"EOF" 32 | 33 | 34 | 35 | 36 | Label 37 | com.mozilla-it.chownfvkey 38 | ProgramArguments 39 | 40 | /bin/bash 41 | /usr/local/bin/chownfvkey.sh 42 | 43 | RunAtLoad 44 | 45 | 46 | 47 | EOF 48 | 49 | # Create /usr/local/bin if it doesn't exist so we can throw scripts in it. 50 | 51 | if [ ! -d /usr/local/bin ]; then 52 | mkdir /usr/local/bin 53 | chown "$loggedInUser" /usr/local/bin 54 | fi 55 | 56 | # Generate a script that will take ownership of a file called fvkey.plist. This 57 | # file is normally owned by root, and is an artifact of the FileVault 2 deffered 58 | # enrollment procedure, which we use because we don't want to capture account 59 | # passwords via any "non official" method (even though the way Apple does it 60 | # looks a little sketchy to me still, personally). 61 | 62 | # First we wait until the fvkey.plist file exists, which it should already. 63 | # Then we take ownership of the file as the user and clean up the script 64 | # artifacts and the LaunchDaemon. 65 | 66 | cat > /usr/local/bin/chownfvkey.sh <<-EOF 67 | #!/bin/bash 68 | 69 | while [ ! -f /Users/"${loggedInUser}"/Library/fvkey.plist ]; do 70 | sleep 2 71 | done 72 | 73 | chown "$loggedInUser" /Users/"${loggedInUser}"/Library/fvkey.plist 74 | 75 | rm /usr/local/bin/chownfvkey.sh 76 | rm /Library/LaunchDaemons/com.mozilla-it.chownfvkey.plist 77 | launchctl remove com.mozilla-it.chownfvkey 78 | EOF 79 | 80 | # If the user's LaunchAgents directory doesn't exist, create it so we can drop a 81 | # LaunchAgent. 82 | 83 | if [ ! -d /Users/"${loggedInUser}"/Library/LaunchAgents ]; then 84 | mkdir /Users/"${loggedInUser}"/Library/LaunchAgents 85 | chown "$loggedInUser" /Users/"${loggedInUser}"/Library/LaunchAgents 86 | fi 87 | 88 | # Generate a LaunchAgent via heredoc that will execute the fv-keyprompt.sh 89 | # script that we will write later on in this script. 90 | 91 | cat > /Users/"${loggedInUser}"/Library/LaunchAgents/com.mozilla-it.fv-keyprompt.plist <<-"EOF" 92 | 93 | 94 | 95 | 96 | Label 97 | com.mozilla-it.fv-keyprompt 98 | ProgramArguments 99 | 100 | /bin/bash 101 | /usr/local/bin/fv-keyprompt.sh 102 | 103 | RunAtLoad 104 | 105 | 106 | 107 | EOF 108 | 109 | # Generate the FileVault 2 prompt script via a heredoc that the LaunchAgent will 110 | # fire off. 111 | 112 | # First, determine the logged in user through the usual "apple way" 113 | # Then, wait until we can act on the fvkey.plist file. The file is normally 114 | # owned by root, but a LaunchDaemon we create will fix that. 115 | # Use PlistBuddy to read the recovery key to the file, then pass that key to a 116 | # simple Applescript prompt via yet another heredoc. 117 | # When finished, clean up the script artifacts, the key file and the LaunchAgent 118 | 119 | # Note: I'm not good enough at bash to know the best way to just pass the user 120 | # in to this heredoc from the main script, because we're using a command 121 | # subsitution and if I don't specify a literal interpretation of the heredoc, 122 | # the script attempts to run the command substitution. This is something that 123 | # should probably be fixed later by either avo iding the subsitution or learning 124 | # more about heredocs. 125 | 126 | cat > /usr/local/bin/fv-keyprompt.sh <<-"EOF" 127 | #!/bin/bash 128 | 129 | export loggedInUser=`python -c ' 130 | from SystemConfiguration import SCDynamicStoreCopyConsoleUser; 131 | import sys; 132 | username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; 133 | username = [username,""][username in [u"loginwindow", None, u""]]; 134 | sys.stdout.write(username + "\n");'` 135 | 136 | while [ ! -O /Users/${loggedInUser}/Library/fvkey.plist ]; do 137 | sleep 2 138 | done 139 | 140 | recovery_key=$(/usr/libexec/PlistBuddy -c "Print :RecoveryKey" /Users/"${loggedInUser}"/Library/fvkey.plist) 141 | osascript <<-EOF2 142 | display dialog "FileVault has been activated on this machine.\n\nYour FileVault recovery key is:\n\n${recovery_key}\n\nPlease escrow this key in WDE by browsing to:\nhttps://wde.mozilla.org/" buttons {"Continue"} default button 1 with title "FileVault Recovery Key" 143 | return 144 | EOF2 145 | 146 | rm /Users/${loggedInUser}/Library/fvkey.plist 147 | rm /usr/local/bin/fv-keyprompt.sh 148 | rm /Users/${loggedInUser}/Library/LaunchAgents/com.mozilla-it.fv-keyprompt.plist 149 | launchctl remove com.mozilla-it.fv-keyprompt 150 | EOF 151 | 152 | # Make the scripts we drop with the above heredocs executable for good measure. 153 | 154 | chmod +x /usr/local/bin/fv-keyprompt.sh 155 | chmod +x /usr/local/bin/chownfvkey.sh 156 | -------------------------------------------------------------------------------- /ambient_display_manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | { 4 | "item": "Check macOS Version", 5 | "version": "", 6 | "url": "resources/macos-versioncheck.sh", 7 | "filename": "", 8 | "dmg-installer": "", 9 | "dmg-advanced": "", 10 | "hash": "e844fcf24f1e4d48f1719dca1ac0d381052c9e27ad402ae181d0feeda55f459e", 11 | "type": "shell" 12 | }, 13 | { 14 | "item": "Set Hostname", 15 | "version": "", 16 | "url": "resources/corsica/prompt-computername.sh", 17 | "filename": "", 18 | "dmg-installer": "", 19 | "dmg-advanced": "", 20 | "hash": "5bd19a59b61380ec75ffe6c1e27941065bfb92aa5461c5b6bc96726ff468d15c", 21 | "type": "shell" 22 | }, 23 | { 24 | "item": "Set Do Not Disturb", 25 | "version": "", 26 | "url": "resources/corsica/set-donotdisturb.sh", 27 | "filename": "", 28 | "dmg-installer": "", 29 | "dmg-advanced": "", 30 | "hash": "caa7d4fc46dd998119fc60f14bf62667128318df364727ec6083e8f94da79f29", 31 | "type": "shell" 32 | }, 33 | { 34 | "item": "Create Corsica User", 35 | "version": "", 36 | "url": "resources/corsica/corsica-user-file.pkg", 37 | "filename": "", 38 | "dmg-installer": "", 39 | "dmg-advanced": "", 40 | "hash": "810422bed023a2812182e1870d38aa04a6cccf08d12cc1a20a94f6dd7bb8bbef", 41 | "type": "pkg" 42 | }, 43 | { 44 | "item": "Install Firefox ESR", 45 | "version": "68.0", 46 | "url": "https://download.mozilla.org/?product=firefox-${version}esr-SSL&os=osx&lang=en-US", 47 | "filename": "firefox.dmg", 48 | "dmg-installer": "Firefox.app", 49 | "dmg-advanced": "", 50 | "hash": "298a982c23db714fc7c3d4915450cc0d5ea2d37f52a68dd440a3ab31c8c53348", 51 | "type": "dmg" 52 | }, 53 | { 54 | "item": "Configure Firewall", 55 | "version": "", 56 | "url": "resources/firewall.sh", 57 | "filename": "", 58 | "dmg-installer": "", 59 | "dmg-advanced": "", 60 | "hash": "a3476f968ac645dffcecc336a0b735a7f508b30f75c9a3222e211100e58d5c0e", 61 | "type": "shell" 62 | }, 63 | { 64 | "item": "Disable analytics settings", 65 | "version": "", 66 | "url": "resources/analytics_settings.sh", 67 | "filename": "", 68 | "dmg-installer": "", 69 | "dmg-advanced": "", 70 | "hash": "9a88799266ede381335a3ec5c23118aa6404516e6fd3ca805d238dff2eb5eefb", 71 | "type": "shell" 72 | }, 73 | { 74 | "item": "Disable Screensaver", 75 | "version": "", 76 | "url": "resources/corsica/disable-screensaver.sh", 77 | "filename": "", 78 | "dmg-installer": "", 79 | "dmg-advanced": "", 80 | "hash": "04a61587519fa63b833c92aaa6190f3c9961afaf90f777f8d2510707a37cfd01", 81 | "type": "shell" 82 | }, 83 | { 84 | "item": "Configure Firefox", 85 | "version": "", 86 | "url": "resources/corsica/firefox-settings.sh", 87 | "filename": "", 88 | "dmg-installer": "", 89 | "dmg-advanced": "", 90 | "hash": "ae63aa2a891bdd327d0f8d6641e4004ec4b132c2bd19d7653443b1c661e7434d", 91 | "type": "shell" 92 | }, 93 | { 94 | "item": "Launch Firefox", 95 | "version": "", 96 | "url": "resources/corsica/launch-firefox.sh", 97 | "filename": "", 98 | "dmg-installer": "", 99 | "dmg-advanced": "", 100 | "hash": "ad5fe3c7092fbf420098037f728a7626aed362723e79fd23aafa1438fb986a3d", 101 | "type": "shell" 102 | }, 103 | { 104 | "item": "Configure Power Settings", 105 | "version": "", 106 | "url": "resources/corsica/power-settings.sh", 107 | "filename": "", 108 | "dmg-installer": "", 109 | "dmg-advanced": "", 110 | "hash": "3b6b3161094bbb38492bce682671e48e4099acb51bfc9379dba4961395a8408d", 111 | "type": "shell" 112 | }, 113 | { 114 | "item": "Enable automatic updates", 115 | "version": "", 116 | "url": "resources/corsica/auto-update.sh", 117 | "filename": "", 118 | "dmg-installer": "", 119 | "dmg-advanced": "", 120 | "hash": "5c2beeb3f17983d42ec1f0525791847745c993b1fb1bc0a5098cdd459120b9c4", 121 | "type": "shell" 122 | }, 123 | { 124 | "item": "Disable Bluetooth Setup Assistant", 125 | "version": "", 126 | "url": "resources/corsica/disable-bluetooth-setup-assistant.sh", 127 | "filename": "", 128 | "dmg-installer": "", 129 | "dmg-advanced": "", 130 | "hash": "7b92fd58cd018391fd78e77cac79f2924bc156a862d2980b4ea2505e585d420a", 131 | "type": "shell" 132 | }, 133 | { 134 | "item": "Skip iCloud Setup Assistant", 135 | "version": "", 136 | "url": "resources/corsica/skip-icloud-setup.mobileconfig", 137 | "filename": "", 138 | "dmg-installer": "", 139 | "dmg-advanced": "", 140 | "hash": "8ba15813d89d84bc7d67638f8cb0a5e7a9b493d80326f7422bf8f520d5a205ee", 141 | "type": "mobileconfig" 142 | }, 143 | { 144 | "item": "Skip Appearence Window Setup Assistant", 145 | "version": "", 146 | "url": "resources/corsica/skip-appearence-window.mobileconfig", 147 | "filename": "", 148 | "dmg-installer": "", 149 | "dmg-advanced": "", 150 | "hash": "65a5dc820588fc4324651ea04f8516e92cd17447ea79a10e00000ef0d1baeb4e", 151 | "type": "mobileconfig" 152 | }, 153 | { 154 | "item": "Skip Data and Privacy Setup Assistant", 155 | "version": "", 156 | "url": "resources/corsica/skip-data-privacy-setup.mobileconfig", 157 | "filename": "", 158 | "dmg-installer": "", 159 | "dmg-advanced": "", 160 | "hash": "54ccb4247f402f3c73a84e4f08da09e9bf63e0e763a9e94aa7659c5bd2ff25ca", 161 | "type": "mobileconfig" 162 | }, 163 | { 164 | "item": "Skip Siri Setup Assistant", 165 | "version": "", 166 | "url": "resources/corsica/skip-siri-setup.mobileconfig", 167 | "filename": "", 168 | "dmg-installer": "", 169 | "dmg-advanced": "", 170 | "hash": "9fc4d33f0bcd4eaf645baba7b55832e0c5fff61892e729c5e8b282a326c15f84", 171 | "type": "mobileconfig" 172 | }, 173 | { 174 | "item": "Disable Guest Login", 175 | "version": "", 176 | "url": "resources/disable-guest.sh", 177 | "filename": "", 178 | "dmg-installer": "", 179 | "dmg-advanced": "", 180 | "hash": "b4d565ca544ffa7559ccfab4d80467316f9c6531f1f7d0b97cc9010553876216", 181 | "type": "shell" 182 | }, 183 | { 184 | "item": "Update macOS", 185 | "version": "", 186 | "url": "resources/update-macos.sh", 187 | "filename": "", 188 | "dmg-installer": "", 189 | "dmg-advanced": "", 190 | "hash": "444591b5f64b5280935bc8b3459778a63a106399080bf4dfb854f5fad2c60562", 191 | "type": "shell" 192 | } 193 | ] 194 | } 195 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /dino_engine.py.bak: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | import subprocess 8 | import json 9 | import os 10 | import hashlib 11 | import urllib2 12 | import re 13 | import stat 14 | import shutil 15 | import shlex 16 | import pwd 17 | import grp 18 | import argparse 19 | from SystemConfiguration import SCDynamicStoreCopyConsoleUser 20 | 21 | 22 | # the downloader function accepts two arguments: the url of the file you are 23 | # downloading and the filename (path) of the file you are downloading. The 24 | # downloader reads the Content-Length portion of the header of the incoming 25 | # file and determines the expected file size then reads the incoming file in 26 | # chunks of 8192 bytes and displays the currently read bytes and percentage 27 | # complete 28 | def downloader(url, file_path): 29 | download = urllib2.urlopen(url) 30 | meta = download.info() 31 | file_size = int(meta.getheaders("Content-Length")[0]) 32 | print "%s is %s bytes." % (file_path, file_size) 33 | with open(file_path, 'wb') as code: 34 | chunk_size = 8192 35 | bytes_read = 0 36 | while True: 37 | data = download.read(chunk_size) 38 | bytes_read += len(data) 39 | code.write(data) 40 | status = r"%10d [%3.2f%%]" % (bytes_read, bytes_read * 100 / file_size) 41 | status = status + chr(8) * (len(status) + 1) 42 | print "\r", status, 43 | if len(data) < chunk_size: 44 | break 45 | 46 | 47 | # the package installer function runs the installer binary in macOS and pipes 48 | # stdout and stderr to the python console the return code of the package run 49 | # can be found in the pipes object (pipes.returncode). this is the reason we 50 | # need to run # this is the bit where we can accept an optional command with 51 | # arguments 52 | def pkg_install(package): 53 | pipes = subprocess.Popen(["sudo", "installer", "-pkg", package, "-target", "/"], 54 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 55 | stdout, stderr = pipes.communicate() 56 | if pipes.returncode == 1: 57 | print stdout 58 | print stderr 59 | exit(1) 60 | 61 | 62 | # the script executer executes any .sh file using bash and pipes stdout and 63 | # stderr to the python console. the return code of the script execution can be 64 | # found in the pipes object (pipes.returncode). 65 | def script_exec(script): 66 | pipes = subprocess.Popen(["/bin/bash", "-c", script], 67 | stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 68 | for line in iter(pipes.stdout.readline, b''): 69 | print "*** " + line.rstrip() 70 | pipes.communicate() 71 | if pipes.returncode == 1: 72 | exit(1) 73 | 74 | 75 | # the dmg installer is by far the most complicated function, because DMGs are 76 | # more complicated than a .app inside we take the appropriate action. we also 77 | # have the option to specify an optional command. since sometimes we must 78 | # execute installer .apps or pkgs buried in the .app bundle, which is annoying. 79 | def dmg_install(filename, installer, command=None): 80 | pipes = subprocess.Popen(["hdiutil", "attach", filename], stdout=subprocess.PIPE, 81 | stderr=subprocess.PIPE) 82 | stdout, stderr = pipes.communicate() 83 | if pipes.returncode == 1: 84 | print stdout 85 | print stderr 86 | exit(1) 87 | volume_path = re.search(r'(\/Volumes\/).*$', stdout).group(0) 88 | installer_path = "%s/%s" % (volume_path, installer) 89 | if command is not None and installer == '': 90 | command = command.replace('${volume}', volume_path).encode("utf-8") 91 | command = shlex.split(command) 92 | pipes = subprocess.Popen( 93 | command, 94 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 95 | stdout, stderr = pipes.communicate() 96 | if pipes.returncode == 1: 97 | print stdout 98 | print stderr 99 | exit(1) 100 | if ".pkg" in installer: 101 | pkg_install(installer_path) 102 | if ".app" in installer: 103 | applications_path = "/Applications/%s" % installer.rsplit('/', 1)[-1] 104 | if os.path.exists(applications_path): 105 | shutil.rmtree(applications_path) 106 | shutil.copytree(installer_path, applications_path) 107 | # current_user - the name of the user running the script. Apple suggests using 108 | # both methods. 109 | # uid - the UID of the user running the script 110 | # gid - the GID of the group "admin" which the user account is expected to be a member of 111 | # users in macOS 112 | current_user = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0] 113 | current_user = [current_user, ""][current_user in [u"loginwindow", None, u""]] 114 | uid = pwd.getpwnam(current_user).pw_uid 115 | gid = grp.getgrnam("admin").gr_gid 116 | os.chown(applications_path, uid, gid) 117 | for root, dirs, files in os.walk(applications_path): 118 | for d in dirs: 119 | os.chown(os.path.join(root, d), uid, gid) 120 | for f in files: 121 | os.chown(os.path.join(root, f), uid, gid) 122 | pipes = subprocess.Popen(["hdiutil", "detach", volume_path], stdout=subprocess.PIPE, 123 | stderr=subprocess.PIPE) 124 | stdout, stderr = pipes.communicate() 125 | if pipes.returncode == 1: 126 | print stdout 127 | print stderr 128 | exit(1) 129 | 130 | 131 | # the mobileconfig_install function installs configuration profiles 132 | def mobileconfig_install(mobileconfig): 133 | pipes = subprocess.Popen(["/usr/bin/profiles", "-I", "-F", mobileconfig], 134 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 135 | stdout, stderr = pipes.communicate() 136 | if pipes.returncode == 1: 137 | print stdout 138 | print stderr 139 | exit(1) 140 | 141 | 142 | # autohash-firefox helper functions 143 | # checks given hash summary page for the line that matches given locale and returns hash 144 | def autohash_firefox_find_hash(hash_summary, locale): 145 | for line in hash_summary.split("\n"): 146 | if locale in line: 147 | linesplit = line.split() 148 | # In 79.0 hashes are encoded in b'' form. 149 | # easiest way to interpret is to split it 2nd char to 66th char, since the text is 150 | # literal, and not read as an encoded string 151 | # hash = linesplit[0][2:66] 152 | # In 80.0 hashes are not in b'' form, so splitting at element 0 gets the hash 153 | hash = linesplit[0] 154 | return hash 155 | return False 156 | 157 | 158 | def autohash_firefox(): 159 | # find latest firefox version by following redirect 160 | response = urllib2.urlopen( 161 | 'https://download.mozilla.org/?product=firefox-latest-ssl&os=osx&lang=en-US') 162 | version = urllib2.urlparse.urlsplit(response.geturl()).path.split('/')[-4] 163 | # build url to fetch official SHA256SUMS page 164 | firefox_hash_url = "https://releases.mozilla.org/pub/firefox/releases/%s/SHA256SUMS" % version 165 | print ("NOTICE: Manifest file is instructing us to compare against official hash for " 166 | "Firefox %s found at %s." % (version, firefox_hash_url)) 167 | req = urllib2.Request(firefox_hash_url) 168 | response = urllib2.urlopen(req) 169 | hash_summary = response.read() 170 | # find the hash for given locale 171 | hash = autohash_firefox_find_hash(hash_summary, "mac/en-US") 172 | return hash 173 | 174 | 175 | # the hash_file function accepts two arguments: the filename that you need to 176 | # determine the SHA256 hash of and the expected hash it returns True or False. 177 | def hash_file(filename, man_hash): 178 | if man_hash == "skip": 179 | print "NOTICE: Manifest file is instructing us to SKIP hashing %s." % filename 180 | elif man_hash == "autohash-firefox": 181 | hash_check = hashlib.sha256() 182 | with open(filename, 'rb') as downloaded_file: 183 | for chunk in iter(lambda: downloaded_file.read(4096), b""): 184 | hash_check.update(chunk) 185 | if hash_check.hexdigest() == autohash_firefox(): 186 | print "\rThe hash for %s matches the official Firefox hash" % filename 187 | else: 188 | print "WARNING: The the hash for %s is unexpected." % filename 189 | exit(1) 190 | else: 191 | hash_check = hashlib.sha256() 192 | with open(filename, 'rb') as downloaded_file: 193 | for chunk in iter(lambda: downloaded_file.read(4096), b""): 194 | hash_check.update(chunk) 195 | if hash_check.hexdigest() == man_hash: 196 | print "\rThe hash for %s match the manifest file" % filename 197 | else: 198 | print "WARNING: The the hash for %s is unexpected." % filename 199 | exit(1) 200 | 201 | 202 | # the pointer_to_json function accepts the url of the file in the github repo 203 | # and the password to the repo. the pointer file is read from github then 204 | # parsed and the "oid sha256" and "size" are extracted from the pointer. an 205 | # object is returned that contains a json request for the file that the pointer 206 | # is associated with. 207 | def pointer_to_json(dl_url): 208 | content_result = urllib2.urlopen(dl_url) 209 | output = content_result.read() 210 | content_result.close() 211 | oid = re.search('(?m)^oid sha256:([a-z0-9]+)$', output) 212 | size = re.search('(?m)^size ([0-9]+)$', output) 213 | json_data = ( 214 | '{"operation": "download", ' 215 | '"transfers": ["basic"], ' 216 | '"objects": [{"oid": "%s", "size": %s}]}' % (oid.group(1), size.group(1))) 217 | return json_data 218 | 219 | 220 | # the get_lfs_url function makes a request the the lfs API of the github repo, 221 | # receives a JSON response. then gets the download URL from the JSON response 222 | # and returns it. 223 | def get_lfs_url(json_input, lfs_url): 224 | req = urllib2.Request(lfs_url, json_input) 225 | req.add_header("Accept", "application/vnd.git-lfs+json") 226 | req.add_header("Content-Type", "application/vnd.git-lfs+json") 227 | result = urllib2.urlopen(req) 228 | results_python = json.load(result) 229 | file_url = results_python['objects'][0]['actions']['download']['href'] 230 | result.close() 231 | return file_url 232 | 233 | 234 | def main(): 235 | # local_dir - the local directory the builder will use 236 | # org - the org that is hosting the build repository 237 | # repo - the rep that is hosting the build 238 | # default_branch - the default branch to build against if no --branch argument is specified 239 | # testing 240 | local_dir = "/var/tmp/dinobuildr" 241 | default_org = "mozilla" 242 | default_repo = "dinobuildr" 243 | default_branch = "master" 244 | default_manifest = "production_manifest.json" 245 | 246 | # this section parses argument(s) passed to this script 247 | # the --branch argument specified the branch that this script will build 248 | # against, which is useful for testing. the script will default to the master 249 | # branch if no argument is specified. 250 | parser = argparse.ArgumentParser() 251 | parser.add_argument("-b", "--branch", 252 | help="The branch name to build against. Defaults to %s" % default_branch) 253 | parser.add_argument("-m", "--manifest", 254 | help="The manifest to build against. Defaults to %s" % default_manifest) 255 | parser.add_argument("-r", "--repo", 256 | help="The repo to build against. Defaults to %s" % default_repo) 257 | parser.add_argument("-o", "--org", 258 | help="The org to build against. Defaults to %s" % default_org) 259 | 260 | args = parser.parse_args() 261 | 262 | if args.branch is None: 263 | branch = default_branch 264 | else: 265 | branch = args.branch 266 | 267 | if args.manifest is None: 268 | manifest = default_manifest 269 | else: 270 | manifest = args.manifest 271 | 272 | if args.repo is None: 273 | repo = default_repo 274 | else: 275 | repo = args.repo 276 | 277 | if args.org is None: 278 | org = default_org 279 | else: 280 | org = args.org 281 | 282 | # os.environ - an environment variable for the builder's local directory to be 283 | # passed on to shells scripts 284 | os.environ["DINOPATH"] = local_dir 285 | 286 | # lfs_url - the generic LFS url structure that github uses 287 | # raw_url - the generic RAW url structure that github uses 288 | # manifest_url - the url of the manifest file 289 | # manifest_hash - the hash of the manifest file 290 | # manifest_file - the expected filepath of the manifest file 291 | lfs_url = "https://github.com/%s/%s.git/info/lfs/objects/batch" % (org, repo) 292 | raw_url = "https://raw.githubusercontent.com/%s/%s/%s/" % (org, repo, branch) 293 | manifest_url = "https://raw.githubusercontent.com/%s/%s/%s/%s" % (org, repo, branch, manifest) 294 | manifest_file = "%s/%s" % (local_dir, manifest) 295 | 296 | # check to see if user ran with sudo , since it's required 297 | 298 | if os.getuid() != 0: 299 | print "This script requires root to run, please try again with sudo." 300 | exit(1) 301 | 302 | # if the local directory doesn't exist, we make it. 303 | if not os.path.exists(local_dir): 304 | os.makedirs(local_dir) 305 | 306 | # download the manifest.json file. 307 | print "\nDownloading the manifest file and hash-checking it.\n" 308 | print manifest_url 309 | print manifest_file 310 | downloader(manifest_url, manifest_file) 311 | 312 | print "\n***** DINOBUILDR IS BUILDING. RAWR. *****\n" 313 | print "Building against the [%s] branch and the %s manifest\n" % (branch, manifest) 314 | # we read the manifest file and examine each object in it. if the object is a 315 | # .pkg file, then we assemble the download url of the pointer, read the pointer 316 | # and request the file from LFS. if the file we get has a hash that matches 317 | # what's in the manifest, we Popen the installer function if the object is a 318 | # .sh file, we assemble the download url and download the file directly. if the 319 | # script we get has a hash that matches what's in the manifest, we set the 320 | # execute flag and Popen the script_exec function. same with dmgs, although 321 | # dmgs are real complicated so we may end up running an arbitrary command, 322 | # copying the installer or installing a pkg. 323 | with open(manifest_file, 'r') as manifest_data: 324 | data = json.load(manifest_data) 325 | 326 | for item in data['packages']: 327 | if item['filename'] != "": 328 | file_name = item['filename'] 329 | else: 330 | file_name = ( 331 | item['url'].replace('${version}', item['version']) 332 | ).rsplit('/', 1)[-1] 333 | # TODO: this variable name is dumb, this is the path to the file we're 334 | # working with 335 | local_path = "%s/%s" % (local_dir, file_name) 336 | 337 | if item['type'] == "pkg-lfs": 338 | dl_url = raw_url + item['url'] 339 | json_data = pointer_to_json(dl_url) 340 | lfsfile_url = get_lfs_url(json_data, lfs_url) 341 | print "Downloading:", item['item'] 342 | downloader(lfsfile_url, local_path) 343 | hash_file(local_path, item['hash']) 344 | print "Installing:", item['item'] 345 | pkg_install(local_path) 346 | print "\r" 347 | 348 | if item['type'] == "pkg": 349 | dl_url = item['url'].replace('${version}', item['version']) 350 | print "Downloading:", item['item'] 351 | downloader(dl_url, local_path) 352 | hash_file(local_path, item['hash']) 353 | print "Installing:", item['item'] 354 | pkg_install(local_path) 355 | print "\r" 356 | 357 | if item['type'] == "shell": 358 | dl_url = raw_url + item['url'] 359 | print "Downloading:", item['item'] 360 | downloader(dl_url, local_path) 361 | hash_file(local_path, item['hash']) 362 | print "Executing:", item['item'] 363 | perms = os.stat(local_path) 364 | os.chmod(local_path, perms.st_mode | stat.S_IEXEC) 365 | script_exec(local_path) 366 | print "\r" 367 | 368 | if item['type'] == "dmg": 369 | # TODO: consisitency: there should be URL checks everywhere or do this 370 | # TODO: dmg-installer / dmg-advanced are not being checked to allow 371 | # for functionality that should be in a downloader function 372 | # in the manifest generator 373 | if item['url'] == '': 374 | print "No URL specified for %s" % item['item'] 375 | break 376 | dl_url = item['url'].replace('${version}', item['version']) 377 | print "Downloading:", item['item'] 378 | downloader(dl_url, local_path) 379 | hash_file(local_path, item['hash']) 380 | if item['dmg-installer'] != '': 381 | print "Installing:", item['dmg-installer'] 382 | if item['dmg-advanced'] != '': 383 | print "Getting fancy and executing:", item['dmg-advanced'] 384 | if item['dmg-installer'] == '' and item['dmg-advanced'] == '': 385 | print(("No installer or install command specified for %s." 386 | "Assuming this is download only." % item['item'])) 387 | if item['dmg-installer'] != '': 388 | dmg_install(local_path, item['dmg-installer']) 389 | if item['dmg-advanced'] != '': 390 | dmg_install(local_path, '', item['dmg-advanced']) 391 | print "\r" 392 | 393 | if item['type'] == "file-lfs": 394 | if item['url'] == '': 395 | print "No URL specified for %s" % item['item'] 396 | break 397 | dl_url = raw_url + item['url'] 398 | json_data = pointer_to_json(dl_url) 399 | lfsfile_url = get_lfs_url(json_data, lfs_url) 400 | print "Downloading:", item['item'] 401 | downloader(lfsfile_url, local_path) 402 | hash_file(local_path, item['hash']) 403 | print "File downloaded to:", local_path 404 | print "\r" 405 | 406 | if item['type'] == "file": 407 | if item['url'] == '': 408 | print "No URL specified for %s" % item['item'] 409 | break 410 | dl_url = raw_url + item['url'] 411 | print "Downloading:", item['item'] 412 | downloader(dl_url, local_path) 413 | hash_file(local_path, item['hash']) 414 | print "File downloaded to:", local_path 415 | print "\r" 416 | 417 | if item['type'] == "mobileconfig": 418 | dl_url = raw_url + item['url'] 419 | print "Downloading:", item['item'] 420 | downloader(dl_url, local_path) 421 | hash_file(local_path, item['hash']) 422 | print "Applying Mobileconfig:", item['item'] 423 | mobileconfig_install(local_path) 424 | print "\r" 425 | 426 | # delete the temporary directory we've been downloading packages into. 427 | print "Cleanup: Deleting %s" % local_dir 428 | shutil.rmtree(local_dir) 429 | 430 | print "Build complete!" 431 | 432 | 433 | if __name__ == '__main__': 434 | main() 435 | -------------------------------------------------------------------------------- /dino_engine_legacy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | import subprocess 8 | import json 9 | import os 10 | import hashlib 11 | import urllib2 12 | import re 13 | import stat 14 | import shutil 15 | import shlex 16 | import pwd 17 | import grp 18 | import argparse 19 | from SystemConfiguration import SCDynamicStoreCopyConsoleUser 20 | 21 | 22 | # the downloader function accepts two arguments: the url of the file you are 23 | # downloading and the filename (path) of the file you are downloading. The 24 | # downloader reads the Content-Length portion of the header of the incoming 25 | # file and determines the expected file size then reads the incoming file in 26 | # chunks of 8192 bytes and displays the currently read bytes and percentage 27 | # complete 28 | def downloader(url, file_path): 29 | download = urllib2.urlopen(url) 30 | meta = download.info() 31 | file_size = int(meta.getheaders("Content-Length")[0]) 32 | print "%s is %s bytes." % (file_path, file_size) 33 | with open(file_path, 'wb') as code: 34 | chunk_size = 8192 35 | bytes_read = 0 36 | while True: 37 | data = download.read(chunk_size) 38 | bytes_read += len(data) 39 | code.write(data) 40 | status = r"%10d [%3.2f%%]" % (bytes_read, bytes_read * 100 / file_size) 41 | status = status + chr(8) * (len(status) + 1) 42 | print "\r", status, 43 | if len(data) < chunk_size: 44 | break 45 | 46 | 47 | # the package installer function runs the installer binary in macOS and pipes 48 | # stdout and stderr to the python console the return code of the package run 49 | # can be found in the pipes object (pipes.returncode). this is the reason we 50 | # need to run # this is the bit where we can accept an optional command with 51 | # arguments 52 | def pkg_install(package): 53 | pipes = subprocess.Popen(["sudo", "installer", "-pkg", package, "-target", "/"], 54 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 55 | stdout, stderr = pipes.communicate() 56 | if pipes.returncode == 1: 57 | print stdout 58 | print stderr 59 | exit(1) 60 | 61 | 62 | # the script executer executes any .sh file using bash and pipes stdout and 63 | # stderr to the python console. the return code of the script execution can be 64 | # found in the pipes object (pipes.returncode). 65 | def script_exec(script): 66 | pipes = subprocess.Popen(["/bin/bash", "-c", script], 67 | stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 68 | for line in iter(pipes.stdout.readline, b''): 69 | print "*** " + line.rstrip() 70 | pipes.communicate() 71 | if pipes.returncode == 1: 72 | exit(1) 73 | 74 | 75 | # the dmg installer is by far the most complicated function, because DMGs are 76 | # more complicated than a .app inside we take the appropriate action. we also 77 | # have the option to specify an optional command. since sometimes we must 78 | # execute installer .apps or pkgs buried in the .app bundle, which is annoying. 79 | def dmg_install(filename, installer, command=None): 80 | pipes = subprocess.Popen(["hdiutil", "attach", filename], stdout=subprocess.PIPE, 81 | stderr=subprocess.PIPE) 82 | stdout, stderr = pipes.communicate() 83 | if pipes.returncode == 1: 84 | print stdout 85 | print stderr 86 | exit(1) 87 | volume_path = re.search(r'(\/Volumes\/).*$', stdout).group(0) 88 | installer_path = "%s/%s" % (volume_path, installer) 89 | if command is not None and installer == '': 90 | command = command.replace('${volume}', volume_path).encode("utf-8") 91 | command = shlex.split(command) 92 | pipes = subprocess.Popen( 93 | command, 94 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 95 | stdout, stderr = pipes.communicate() 96 | if pipes.returncode == 1: 97 | print stdout 98 | print stderr 99 | exit(1) 100 | if ".pkg" in installer: 101 | pkg_install(installer_path) 102 | if ".app" in installer: 103 | applications_path = "/Applications/%s" % installer.rsplit('/', 1)[-1] 104 | if os.path.exists(applications_path): 105 | shutil.rmtree(applications_path) 106 | shutil.copytree(installer_path, applications_path) 107 | # current_user - the name of the user running the script. Apple suggests using 108 | # both methods. 109 | # uid - the UID of the user running the script 110 | # gid - the GID of the group "admin" which the user account is expected to be a member of 111 | # users in macOS 112 | current_user = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0] 113 | current_user = [current_user, ""][current_user in [u"loginwindow", None, u""]] 114 | uid = pwd.getpwnam(current_user).pw_uid 115 | gid = grp.getgrnam("admin").gr_gid 116 | os.chown(applications_path, uid, gid) 117 | for root, dirs, files in os.walk(applications_path): 118 | for d in dirs: 119 | os.chown(os.path.join(root, d), uid, gid) 120 | for f in files: 121 | os.chown(os.path.join(root, f), uid, gid) 122 | pipes = subprocess.Popen(["hdiutil", "detach", volume_path], stdout=subprocess.PIPE, 123 | stderr=subprocess.PIPE) 124 | stdout, stderr = pipes.communicate() 125 | if pipes.returncode == 1: 126 | print stdout 127 | print stderr 128 | exit(1) 129 | 130 | 131 | # the mobileconfig_install function installs configuration profiles 132 | def mobileconfig_install(mobileconfig): 133 | pipes = subprocess.Popen(["/usr/bin/profiles", "-I", "-F", mobileconfig], 134 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 135 | stdout, stderr = pipes.communicate() 136 | if pipes.returncode == 1: 137 | print stdout 138 | print stderr 139 | exit(1) 140 | 141 | 142 | # autohash-firefox helper functions 143 | # checks given hash summary page for the line that matches given locale and returns hash 144 | def autohash_firefox_find_hash(hash_summary, locale): 145 | for line in hash_summary.split("\n"): 146 | if locale in line: 147 | linesplit = line.split() 148 | # In 79.0 hashes are encoded in b'' form. 149 | # easiest way to interpret is to split it 2nd char to 66th char, since the text is 150 | # literal, and not read as an encoded string 151 | # hash = linesplit[0][2:66] 152 | # In 80.0 hashes are not in b'' form, so splitting at element 0 gets the hash 153 | hash = linesplit[0] 154 | return hash 155 | return False 156 | 157 | 158 | def autohash_firefox(): 159 | # find latest firefox version by following redirect 160 | response = urllib2.urlopen( 161 | 'https://download.mozilla.org/?product=firefox-latest-ssl&os=osx&lang=en-US') 162 | version = urllib2.urlparse.urlsplit(response.geturl()).path.split('/')[-4] 163 | # build url to fetch official SHA256SUMS page 164 | firefox_hash_url = "https://releases.mozilla.org/pub/firefox/releases/%s/SHA256SUMS" % version 165 | print ("NOTICE: Manifest file is instructing us to compare against official hash for " 166 | "Firefox %s found at %s." % (version, firefox_hash_url)) 167 | req = urllib2.Request(firefox_hash_url) 168 | response = urllib2.urlopen(req) 169 | hash_summary = response.read() 170 | # find the hash for given locale 171 | hash = autohash_firefox_find_hash(hash_summary, "mac/en-US") 172 | return hash 173 | 174 | 175 | # the hash_file function accepts two arguments: the filename that you need to 176 | # determine the SHA256 hash of and the expected hash it returns True or False. 177 | def hash_file(filename, man_hash): 178 | if man_hash == "skip": 179 | print "NOTICE: Manifest file is instructing us to SKIP hashing %s." % filename 180 | elif man_hash == "autohash-firefox": 181 | hash_check = hashlib.sha256() 182 | with open(filename, 'rb') as downloaded_file: 183 | for chunk in iter(lambda: downloaded_file.read(4096), b""): 184 | hash_check.update(chunk) 185 | if hash_check.hexdigest() == autohash_firefox(): 186 | print "\rThe hash for %s matches the official Firefox hash" % filename 187 | else: 188 | print "WARNING: The the hash for %s is unexpected." % filename 189 | exit(1) 190 | else: 191 | hash_check = hashlib.sha256() 192 | with open(filename, 'rb') as downloaded_file: 193 | for chunk in iter(lambda: downloaded_file.read(4096), b""): 194 | hash_check.update(chunk) 195 | if hash_check.hexdigest() == man_hash: 196 | print "\rThe hash for %s match the manifest file" % filename 197 | else: 198 | print "WARNING: The the hash for %s is unexpected." % filename 199 | exit(1) 200 | 201 | 202 | # the pointer_to_json function accepts the url of the file in the github repo 203 | # and the password to the repo. the pointer file is read from github then 204 | # parsed and the "oid sha256" and "size" are extracted from the pointer. an 205 | # object is returned that contains a json request for the file that the pointer 206 | # is associated with. 207 | def pointer_to_json(dl_url): 208 | content_result = urllib2.urlopen(dl_url) 209 | output = content_result.read() 210 | content_result.close() 211 | oid = re.search('(?m)^oid sha256:([a-z0-9]+)$', output) 212 | size = re.search('(?m)^size ([0-9]+)$', output) 213 | json_data = ( 214 | '{"operation": "download", ' 215 | '"transfers": ["basic"], ' 216 | '"objects": [{"oid": "%s", "size": %s}]}' % (oid.group(1), size.group(1))) 217 | return json_data 218 | 219 | 220 | # the get_lfs_url function makes a request the the lfs API of the github repo, 221 | # receives a JSON response. then gets the download URL from the JSON response 222 | # and returns it. 223 | def get_lfs_url(json_input, lfs_url): 224 | req = urllib2.Request(lfs_url, json_input) 225 | req.add_header("Accept", "application/vnd.git-lfs+json") 226 | req.add_header("Content-Type", "application/vnd.git-lfs+json") 227 | result = urllib2.urlopen(req) 228 | results_python = json.load(result) 229 | file_url = results_python['objects'][0]['actions']['download']['href'] 230 | result.close() 231 | return file_url 232 | 233 | 234 | def main(): 235 | # local_dir - the local directory the builder will use 236 | # org - the org that is hosting the build repository 237 | # repo - the rep that is hosting the build 238 | # default_branch - the default branch to build against if no --branch argument is specified 239 | # testing 240 | local_dir = "/var/tmp/dinobuildr" 241 | default_org = "mozilla" 242 | default_repo = "dinobuildr" 243 | default_branch = "master" 244 | default_manifest = "production_manifest.json" 245 | 246 | # this section parses argument(s) passed to this script 247 | # the --branch argument specified the branch that this script will build 248 | # against, which is useful for testing. the script will default to the master 249 | # branch if no argument is specified. 250 | parser = argparse.ArgumentParser() 251 | parser.add_argument("-b", "--branch", 252 | help="The branch name to build against. Defaults to %s" % default_branch) 253 | parser.add_argument("-m", "--manifest", 254 | help="The manifest to build against. Defaults to %s" % default_manifest) 255 | parser.add_argument("-r", "--repo", 256 | help="The repo to build against. Defaults to %s" % default_repo) 257 | parser.add_argument("-o", "--org", 258 | help="The org to build against. Defaults to %s" % default_org) 259 | 260 | args = parser.parse_args() 261 | 262 | if args.branch is None: 263 | branch = default_branch 264 | else: 265 | branch = args.branch 266 | 267 | if args.manifest is None: 268 | manifest = default_manifest 269 | else: 270 | manifest = args.manifest 271 | 272 | if args.repo is None: 273 | repo = default_repo 274 | else: 275 | repo = args.repo 276 | 277 | if args.org is None: 278 | org = default_org 279 | else: 280 | org = args.org 281 | 282 | # os.environ - an environment variable for the builder's local directory to be 283 | # passed on to shells scripts 284 | os.environ["DINOPATH"] = local_dir 285 | 286 | # lfs_url - the generic LFS url structure that github uses 287 | # raw_url - the generic RAW url structure that github uses 288 | # manifest_url - the url of the manifest file 289 | # manifest_hash - the hash of the manifest file 290 | # manifest_file - the expected filepath of the manifest file 291 | lfs_url = "https://github.com/%s/%s.git/info/lfs/objects/batch" % (org, repo) 292 | raw_url = "https://raw.githubusercontent.com/%s/%s/%s/" % (org, repo, branch) 293 | manifest_url = "https://raw.githubusercontent.com/%s/%s/%s/%s" % (org, repo, branch, manifest) 294 | manifest_file = "%s/%s" % (local_dir, manifest) 295 | 296 | # check to see if user ran with sudo , since it's required 297 | 298 | if os.getuid() != 0: 299 | print "This script requires root to run, please try again with sudo." 300 | exit(1) 301 | 302 | # if the local directory doesn't exist, we make it. 303 | if not os.path.exists(local_dir): 304 | os.makedirs(local_dir) 305 | 306 | # download the manifest.json file. 307 | print "\nDownloading the manifest file and hash-checking it.\n" 308 | print manifest_url 309 | print manifest_file 310 | downloader(manifest_url, manifest_file) 311 | 312 | print "\n***** DINOBUILDR IS BUILDING. RAWR. *****\n" 313 | print "Building against the [%s] branch and the %s manifest\n" % (branch, manifest) 314 | # we read the manifest file and examine each object in it. if the object is a 315 | # .pkg file, then we assemble the download url of the pointer, read the pointer 316 | # and request the file from LFS. if the file we get has a hash that matches 317 | # what's in the manifest, we Popen the installer function if the object is a 318 | # .sh file, we assemble the download url and download the file directly. if the 319 | # script we get has a hash that matches what's in the manifest, we set the 320 | # execute flag and Popen the script_exec function. same with dmgs, although 321 | # dmgs are real complicated so we may end up running an arbitrary command, 322 | # copying the installer or installing a pkg. 323 | with open(manifest_file, 'r') as manifest_data: 324 | data = json.load(manifest_data) 325 | 326 | for item in data['packages']: 327 | if item['filename'] != "": 328 | file_name = item['filename'] 329 | else: 330 | file_name = ( 331 | item['url'].replace('${version}', item['version']) 332 | ).rsplit('/', 1)[-1] 333 | # TODO: this variable name is dumb, this is the path to the file we're 334 | # working with 335 | local_path = "%s/%s" % (local_dir, file_name) 336 | 337 | if item['type'] == "pkg-lfs": 338 | dl_url = raw_url + item['url'] 339 | json_data = pointer_to_json(dl_url) 340 | lfsfile_url = get_lfs_url(json_data, lfs_url) 341 | print "Downloading:", item['item'] 342 | downloader(lfsfile_url, local_path) 343 | hash_file(local_path, item['hash']) 344 | print "Installing:", item['item'] 345 | pkg_install(local_path) 346 | print "\r" 347 | 348 | if item['type'] == "pkg": 349 | dl_url = item['url'].replace('${version}', item['version']) 350 | print "Downloading:", item['item'] 351 | downloader(dl_url, local_path) 352 | hash_file(local_path, item['hash']) 353 | print "Installing:", item['item'] 354 | pkg_install(local_path) 355 | print "\r" 356 | 357 | if item['type'] == "shell": 358 | dl_url = raw_url + item['url'] 359 | print "Downloading:", item['item'] 360 | downloader(dl_url, local_path) 361 | hash_file(local_path, item['hash']) 362 | print "Executing:", item['item'] 363 | perms = os.stat(local_path) 364 | os.chmod(local_path, perms.st_mode | stat.S_IEXEC) 365 | script_exec(local_path) 366 | print "\r" 367 | 368 | if item['type'] == "dmg": 369 | # TODO: consisitency: there should be URL checks everywhere or do this 370 | # TODO: dmg-installer / dmg-advanced are not being checked to allow 371 | # for functionality that should be in a downloader function 372 | # in the manifest generator 373 | if item['url'] == '': 374 | print "No URL specified for %s" % item['item'] 375 | break 376 | dl_url = item['url'].replace('${version}', item['version']) 377 | print "Downloading:", item['item'] 378 | downloader(dl_url, local_path) 379 | hash_file(local_path, item['hash']) 380 | if item['dmg-installer'] != '': 381 | print "Installing:", item['dmg-installer'] 382 | if item['dmg-advanced'] != '': 383 | print "Getting fancy and executing:", item['dmg-advanced'] 384 | if item['dmg-installer'] == '' and item['dmg-advanced'] == '': 385 | print(("No installer or install command specified for %s." 386 | "Assuming this is download only." % item['item'])) 387 | if item['dmg-installer'] != '': 388 | dmg_install(local_path, item['dmg-installer']) 389 | if item['dmg-advanced'] != '': 390 | dmg_install(local_path, '', item['dmg-advanced']) 391 | print "\r" 392 | 393 | if item['type'] == "file-lfs": 394 | if item['url'] == '': 395 | print "No URL specified for %s" % item['item'] 396 | break 397 | dl_url = raw_url + item['url'] 398 | json_data = pointer_to_json(dl_url) 399 | lfsfile_url = get_lfs_url(json_data, lfs_url) 400 | print "Downloading:", item['item'] 401 | downloader(lfsfile_url, local_path) 402 | hash_file(local_path, item['hash']) 403 | print "File downloaded to:", local_path 404 | print "\r" 405 | 406 | if item['type'] == "file": 407 | if item['url'] == '': 408 | print "No URL specified for %s" % item['item'] 409 | break 410 | dl_url = raw_url + item['url'] 411 | print "Downloading:", item['item'] 412 | downloader(dl_url, local_path) 413 | hash_file(local_path, item['hash']) 414 | print "File downloaded to:", local_path 415 | print "\r" 416 | 417 | if item['type'] == "mobileconfig": 418 | dl_url = raw_url + item['url'] 419 | print "Downloading:", item['item'] 420 | downloader(dl_url, local_path) 421 | hash_file(local_path, item['hash']) 422 | print "Applying Mobileconfig:", item['item'] 423 | mobileconfig_install(local_path) 424 | print "\r" 425 | 426 | # delete the temporary directory we've been downloading packages into. 427 | print "Cleanup: Deleting %s" % local_dir 428 | shutil.rmtree(local_dir) 429 | 430 | print "Build complete!" 431 | 432 | 433 | if __name__ == '__main__': 434 | main() 435 | -------------------------------------------------------------------------------- /dino_engine.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | from http.client import HTTPMessage 8 | import subprocess 9 | import json 10 | import os 11 | import hashlib 12 | import urllib.request, urllib.error, urllib.parse 13 | import re 14 | import stat 15 | import shutil 16 | import shlex 17 | import pwd 18 | import grp 19 | import argparse 20 | from SystemConfiguration import SCDynamicStoreCopyConsoleUser 21 | 22 | 23 | # the downloader function accepts two arguments: the url of the file you are 24 | # downloading and the filename (path) of the file you are downloading. The 25 | # downloader reads the Content-Length portion of the header of the incoming 26 | # file and determines the expected file size then reads the incoming file in 27 | # chunks of 8192 bytes and displays the currently read bytes and percentage 28 | # complete 29 | def downloader(url, file_path): 30 | download = urllib.request.urlopen(url) 31 | meta = download.info() 32 | file_size = int(meta.get("Content-Length")[0]) 33 | print("%s is %s bytes." % (file_path, file_size)) 34 | with open(file_path, 'wb') as code: 35 | chunk_size = 8192 36 | bytes_read = 0 37 | while True: 38 | data = download.read(chunk_size) 39 | bytes_read += len(data) 40 | code.write(data) 41 | status = r"%10d [%3.2f%%]" % (bytes_read, bytes_read * 100 / file_size) 42 | status = status + chr(8) * (len(status) + 1) 43 | print("\r", status, end=' ') 44 | if len(data) < chunk_size: 45 | break 46 | 47 | 48 | # the package installer function runs the installer binary in macOS and pipes 49 | # stdout and stderr to the python console the return code of the package run 50 | # can be found in the pipes object (pipes.returncode). this is the reason we 51 | # need to run # this is the bit where we can accept an optional command with 52 | # arguments 53 | def pkg_install(package): 54 | pipes = subprocess.Popen(["sudo", "installer", "-pkg", package, "-target", "/"], 55 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 56 | stdout, stderr = pipes.communicate() 57 | if pipes.returncode == 1: 58 | print(stdout) 59 | print(stderr) 60 | exit(1) 61 | 62 | 63 | # the script executer executes any .sh file using bash and pipes stdout and 64 | # stderr to the python console. the return code of the script execution can be 65 | # found in the pipes object (pipes.returncode). 66 | def script_exec(script): 67 | pipes = subprocess.Popen(["/bin/bash", "-c", script], 68 | stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 69 | # the following function is for printing errors back up to the termina 70 | # I commented it out because I couldn't figure out how to update it for py3 71 | # re-write this when there is extra cycles 72 | #for line in iter(pipes.stdout.readline, b''): 73 | # print("*** " + line.rstrip()) 74 | pipes.communicate() 75 | if pipes.returncode == 1: 76 | exit(1) 77 | 78 | 79 | # the dmg installer is by far the most complicated function, because DMGs are 80 | # more complicated than a .app inside we take the appropriate action. we also 81 | # have the option to specify an optional command. since sometimes we must 82 | # execute installer .apps or pkgs buried in the .app bundle, which is annoying. 83 | def dmg_install(filename, installer, command=None): 84 | pipes = subprocess.Popen(["hdiutil", "attach", filename], stdout=subprocess.PIPE, 85 | stderr=subprocess.PIPE) 86 | stdout, stderr = pipes.communicate() 87 | if pipes.returncode == 1: 88 | print(stdout) 89 | print(stderr) 90 | exit(1) 91 | volume_path = re.search(r'(\/Volumes\/).*$', stdout.decode('utf-8')).group(0) 92 | installer_path = "%s/%s" % (volume_path, installer) 93 | if command is not None and installer == '': 94 | command = command.replace('${volume}', volume_path).encode("utf-8") 95 | command = shlex.split(command) 96 | pipes = subprocess.Popen( 97 | command, 98 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 99 | stdout, stderr = pipes.communicate() 100 | if pipes.returncode == 1: 101 | print(stdout) 102 | print(stderr) 103 | exit(1) 104 | if ".pkg" in installer: 105 | pkg_install(installer_path) 106 | if ".app" in installer: 107 | applications_path = "/Applications/%s" % installer.rsplit('/', 1)[-1] 108 | if os.path.exists(applications_path): 109 | shutil.rmtree(applications_path) 110 | shutil.copytree(installer_path, applications_path) 111 | # current_user - the name of the user running the script. Apple suggests using 112 | # both methods. 113 | # uid - the UID of the user running the script 114 | # gid - the GID of the group "admin" which the user account is expected to be a member of 115 | # users in macOS 116 | current_user = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0] 117 | current_user = [current_user, ""][current_user in ["loginwindow", None, ""]] 118 | uid = pwd.getpwnam(current_user).pw_uid 119 | gid = grp.getgrnam("admin").gr_gid 120 | os.chown(applications_path, uid, gid) 121 | for root, dirs, files in os.walk(applications_path): 122 | for d in dirs: 123 | os.chown(os.path.join(root, d), uid, gid) 124 | for f in files: 125 | os.chown(os.path.join(root, f), uid, gid) 126 | pipes = subprocess.Popen(["hdiutil", "detach", volume_path], stdout=subprocess.PIPE, 127 | stderr=subprocess.PIPE) 128 | stdout, stderr = pipes.communicate() 129 | if pipes.returncode == 1: 130 | print(stdout) 131 | print(stderr) 132 | exit(1) 133 | 134 | 135 | # the mobileconfig_install function installs configuration profiles 136 | def mobileconfig_install(mobileconfig): 137 | pipes = subprocess.Popen(["/usr/bin/profiles", "-I", "-F", mobileconfig], 138 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 139 | stdout, stderr = pipes.communicate() 140 | if pipes.returncode == 1: 141 | print(stdout) 142 | print(stderr) 143 | exit(1) 144 | 145 | 146 | # autohash-firefox helper functions 147 | # checks given hash summary page for the line that matches given locale and returns hash 148 | def autohash_firefox_find_hash(hash_summary, locale): 149 | for line in hash_summary.split(b'\n'): 150 | if locale in line: 151 | linesplit = line.split() 152 | # In 79.0 hashes are encoded in b'' form. 153 | # easiest way to interpret is to split it 2nd char to 66th char, since the text is 154 | # literal, and not read as an encoded string 155 | # hash = linesplit[0][2:66] 156 | # In 80.0 hashes are not in b'' form, so splitting at element 0 gets the hash 157 | hash = linesplit[0] 158 | return hash 159 | return False 160 | 161 | 162 | def autohash_firefox(): 163 | # find latest firefox version by following redirect 164 | response = urllib.request.urlopen( 165 | 'https://download.mozilla.org/?product=firefox-latest-ssl&os=osx&lang=en-US') 166 | version = urllib.parse.urlsplit(response.geturl()).path.split('/')[-4] 167 | # build url to fetch official SHA256SUMS page 168 | firefox_hash_url = "https://releases.mozilla.org/pub/firefox/releases/%s/SHA256SUMS" % version 169 | print(("NOTICE: Manifest file is instructing us to compare against official hash for " 170 | "Firefox %s found at %s." % (version, firefox_hash_url))) 171 | req = urllib.request.Request(firefox_hash_url) 172 | response = urllib.request.urlopen(req) 173 | hash_summary = response.read() 174 | # find the hash for given locale 175 | hash = autohash_firefox_find_hash(hash_summary, b'mac/en-US') 176 | return hash 177 | 178 | 179 | # the hash_file function accepts two arguments: the filename that you need to 180 | # determine the SHA256 hash of and the expected hash it returns True or False. 181 | def hash_file(filename, man_hash): 182 | if man_hash == "skip": 183 | print("NOTICE: Manifest file is instructing us to SKIP hashing %s." % filename) 184 | elif man_hash == "autohash-firefox": 185 | hash_check = hashlib.sha256() 186 | with open(filename, 'rb') as downloaded_file: 187 | for chunk in iter(lambda: downloaded_file.read(4096), b""): 188 | hash_check.update(chunk) 189 | if hash_check.hexdigest() == autohash_firefox().decode(): 190 | print("\rThe hash for %s matches the official Firefox hash" % filename) 191 | else: 192 | print(hash_check.hexdigest()) 193 | print(autohash_firefox().decode()) 194 | print("WARNING: The the hash for %s is unexpected." % filename) 195 | exit(1) 196 | else: 197 | hash_check = hashlib.sha256() 198 | with open(filename, 'rb') as downloaded_file: 199 | for chunk in iter(lambda: downloaded_file.read(4096), b""): 200 | hash_check.update(chunk) 201 | if hash_check.hexdigest() == man_hash: 202 | print("\rThe hash for %s match the manifest file" % filename) 203 | else: 204 | print("WARNING: The the hash for %s is unexpected." % filename) 205 | exit(1) 206 | 207 | 208 | # the pointer_to_json function accepts the url of the file in the github repo 209 | # and the password to the repo. the pointer file is read from github then 210 | # parsed and the "oid sha256" and "size" are extracted from the pointer. an 211 | # object is returned that contains a json request for the file that the pointer 212 | # is associated with. 213 | def pointer_to_json(dl_url): 214 | content_result = urllib.request.urlopen(dl_url) 215 | output = content_result.read() 216 | content_result.close() 217 | oid = re.search('(?m)^oid sha256:([a-z0-9]+)$', output) 218 | size = re.search('(?m)^size ([0-9]+)$', output) 219 | json_data = ( 220 | '{"operation": "download", ' 221 | '"transfers": ["basic"], ' 222 | '"objects": [{"oid": "%s", "size": %s}]}' % (oid.group(1), size.group(1))) 223 | return json_data 224 | 225 | 226 | # the get_lfs_url function makes a request the the lfs API of the github repo, 227 | # receives a JSON response. then gets the download URL from the JSON response 228 | # and returns it. 229 | def get_lfs_url(json_input, lfs_url): 230 | req = urllib.request.Request(lfs_url, json_input) 231 | req.add_header("Accept", "application/vnd.git-lfs+json") 232 | req.add_header("Content-Type", "application/vnd.git-lfs+json") 233 | result = urllib.request.urlopen(req) 234 | results_python = json.load(result) 235 | file_url = results_python['objects'][0]['actions']['download']['href'] 236 | result.close() 237 | return file_url 238 | 239 | 240 | def main(): 241 | # local_dir - the local directory the builder will use 242 | # org - the org that is hosting the build repository 243 | # repo - the rep that is hosting the build 244 | # default_branch - the default branch to build against if no --branch argument is specified 245 | # testing 246 | local_dir = "/var/tmp/dinobuildr" 247 | default_org = "mozilla" 248 | default_repo = "dinobuildr" 249 | default_branch = "master" 250 | default_manifest = "production_manifest.json" 251 | 252 | # this section parses argument(s) passed to this script 253 | # the --branch argument specified the branch that this script will build 254 | # against, which is useful for testing. the script will default to the master 255 | # branch if no argument is specified. 256 | parser = argparse.ArgumentParser() 257 | parser.add_argument("-b", "--branch", 258 | help="The branch name to build against. Defaults to %s" % default_branch) 259 | parser.add_argument("-m", "--manifest", 260 | help="The manifest to build against. Defaults to %s" % default_manifest) 261 | parser.add_argument("-r", "--repo", 262 | help="The repo to build against. Defaults to %s" % default_repo) 263 | parser.add_argument("-o", "--org", 264 | help="The org to build against. Defaults to %s" % default_org) 265 | 266 | args = parser.parse_args() 267 | 268 | if args.branch is None: 269 | branch = default_branch 270 | else: 271 | branch = args.branch 272 | 273 | if args.manifest is None: 274 | manifest = default_manifest 275 | else: 276 | manifest = args.manifest 277 | 278 | if args.repo is None: 279 | repo = default_repo 280 | else: 281 | repo = args.repo 282 | 283 | if args.org is None: 284 | org = default_org 285 | else: 286 | org = args.org 287 | 288 | # os.environ - an environment variable for the builder's local directory to be 289 | # passed on to shells scripts 290 | os.environ["DINOPATH"] = local_dir 291 | 292 | # lfs_url - the generic LFS url structure that github uses 293 | # raw_url - the generic RAW url structure that github uses 294 | # manifest_url - the url of the manifest file 295 | # manifest_hash - the hash of the manifest file 296 | # manifest_file - the expected filepath of the manifest file 297 | lfs_url = "https://github.com/%s/%s.git/info/lfs/objects/batch" % (org, repo) 298 | raw_url = "https://raw.githubusercontent.com/%s/%s/%s/" % (org, repo, branch) 299 | manifest_url = "https://raw.githubusercontent.com/%s/%s/%s/%s" % (org, repo, branch, manifest) 300 | manifest_file = "%s/%s" % (local_dir, manifest) 301 | 302 | # check to see if user ran with sudo , since it's required 303 | 304 | if os.getuid() != 0: 305 | print("This script requires root to run, please try again with sudo.") 306 | exit(1) 307 | 308 | # if the local directory doesn't exist, we make it. 309 | if not os.path.exists(local_dir): 310 | os.makedirs(local_dir) 311 | 312 | # download the manifest.json file. 313 | print("\nDownloading the manifest file and hash-checking it.\n") 314 | print(manifest_url) 315 | print(manifest_file) 316 | downloader(manifest_url, manifest_file) 317 | 318 | print("\n***** DINOBUILDR IS BUILDING. RAWR. *****\n") 319 | print("Building against the [%s] branch and the %s manifest\n" % (branch, manifest)) 320 | # we read the manifest file and examine each object in it. if the object is a 321 | # .pkg file, then we assemble the download url of the pointer, read the pointer 322 | # and request the file from LFS. if the file we get has a hash that matches 323 | # what's in the manifest, we Popen the installer function if the object is a 324 | # .sh file, we assemble the download url and download the file directly. if the 325 | # script we get has a hash that matches what's in the manifest, we set the 326 | # execute flag and Popen the script_exec function. same with dmgs, although 327 | # dmgs are real complicated so we may end up running an arbitrary command, 328 | # copying the installer or installing a pkg. 329 | with open(manifest_file, 'r') as manifest_data: 330 | data = json.load(manifest_data) 331 | 332 | for item in data['packages']: 333 | if item['filename'] != "": 334 | file_name = item['filename'] 335 | else: 336 | file_name = ( 337 | item['url'].replace('${version}', item['version']) 338 | ).rsplit('/', 1)[-1] 339 | # TODO: this variable name is dumb, this is the path to the file we're 340 | # working with 341 | local_path = "%s/%s" % (local_dir, file_name) 342 | 343 | if item['type'] == "pkg-lfs": 344 | dl_url = raw_url + item['url'] 345 | json_data = pointer_to_json(dl_url) 346 | lfsfile_url = get_lfs_url(json_data, lfs_url) 347 | print("Downloading:", item['item']) 348 | downloader(lfsfile_url, local_path) 349 | hash_file(local_path, item['hash']) 350 | print("Installing:", item['item']) 351 | pkg_install(local_path) 352 | print("\r") 353 | 354 | if item['type'] == "pkg": 355 | dl_url = item['url'].replace('${version}', item['version']) 356 | print("Downloading:", item['item']) 357 | downloader(dl_url, local_path) 358 | hash_file(local_path, item['hash']) 359 | print("Installing:", item['item']) 360 | pkg_install(local_path) 361 | print("\r") 362 | 363 | if item['type'] == "shell": 364 | dl_url = raw_url + item['url'] 365 | print("Downloading:", item['item']) 366 | downloader(dl_url, local_path) 367 | hash_file(local_path, item['hash']) 368 | print("Executing:", item['item']) 369 | perms = os.stat(local_path) 370 | os.chmod(local_path, perms.st_mode | stat.S_IEXEC) 371 | script_exec(local_path) 372 | print("\r") 373 | 374 | if item['type'] == "dmg": 375 | # TODO: consisitency: there should be URL checks everywhere or do this 376 | # TODO: dmg-installer / dmg-advanced are not being checked to allow 377 | # for functionality that should be in a downloader function 378 | # in the manifest generator 379 | if item['url'] == '': 380 | print("No URL specified for %s" % item['item']) 381 | break 382 | dl_url = item['url'].replace('${version}', item['version']) 383 | print("Downloading:", item['item']) 384 | downloader(dl_url, local_path) 385 | hash_file(local_path, item['hash']) 386 | if item['dmg-installer'] != '': 387 | print("Installing:", item['dmg-installer']) 388 | if item['dmg-advanced'] != '': 389 | print("Getting fancy and executing:", item['dmg-advanced']) 390 | if item['dmg-installer'] == '' and item['dmg-advanced'] == '': 391 | print(("No installer or install command specified for %s." 392 | "Assuming this is download only." % item['item'])) 393 | if item['dmg-installer'] != '': 394 | dmg_install(local_path, item['dmg-installer']) 395 | if item['dmg-advanced'] != '': 396 | dmg_install(local_path, '', item['dmg-advanced']) 397 | print("\r") 398 | 399 | if item['type'] == "file-lfs": 400 | if item['url'] == '': 401 | print("No URL specified for %s" % item['item']) 402 | break 403 | dl_url = raw_url + item['url'] 404 | json_data = pointer_to_json(dl_url) 405 | lfsfile_url = get_lfs_url(json_data, lfs_url) 406 | print("Downloading:", item['item']) 407 | downloader(lfsfile_url, local_path) 408 | hash_file(local_path, item['hash']) 409 | print("File downloaded to:", local_path) 410 | print("\r") 411 | 412 | if item['type'] == "file": 413 | if item['url'] == '': 414 | print("No URL specified for %s" % item['item']) 415 | break 416 | dl_url = raw_url + item['url'] 417 | print("Downloading:", item['item']) 418 | downloader(dl_url, local_path) 419 | hash_file(local_path, item['hash']) 420 | print("File downloaded to:", local_path) 421 | print("\r") 422 | 423 | if item['type'] == "mobileconfig": 424 | dl_url = raw_url + item['url'] 425 | print("Downloading:", item['item']) 426 | downloader(dl_url, local_path) 427 | hash_file(local_path, item['hash']) 428 | print("Applying Mobileconfig:", item['item']) 429 | mobileconfig_install(local_path) 430 | print("\r") 431 | 432 | # delete the temporary directory we've been downloading packages into. 433 | print("Cleanup: Deleting %s" % local_dir) 434 | shutil.rmtree(local_dir) 435 | 436 | print("Build complete!") 437 | 438 | 439 | if __name__ == '__main__': 440 | main() 441 | --------------------------------------------------------------------------------