├── .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 |
--------------------------------------------------------------------------------