From zero to fully installed and configured, in an hour.
case "${SHELL}" in
(*zsh) ;;
(*) chsh -s "$(which zsh)"; exit 1 ;;
esaccurl --location --silent \ "https://github.com/ptb/mac-setup/raw/develop/mac-setup.command" | \ source /dev/stdin 0
init && install && config
custom && personalize_all
- Enter administrator account password only once
- Select the cache folder for repeat installations
- Turn off sleep and set computer name/hostname
- Set write permission on destination folders
- Cache software updates and App Store software
- Install developer tools and macOS updates
- Homebrew: The missing package mananger for macOS
- Homebrew-Cask: “To install, drag this icon…” no more!
- mas-cli/mas: Mac App Store command line interface
- homebrew-bundle: List all Homebrew packages in
Brewfile - Node.js: Cross-platform JavaScript run-time environment
- Perl 5: Highly capable, feature-rich programming language
- Python: Programming language that lets you work quickly
- Ruby: Language with a focus on simplicity and productivity
- Configure software requiring an administrator account
- Optionally configure local Dovecot secure IMAP server
- Create your primary non-administrator account
- Remove password-less administrator account permission
- Recommended that you log out of administrator account
- Log in with your new non-administrator account
- Create or clone a git repository into your home folder
- Install Atom packages and customize preferences
- Set the desktop picture to a solid black color
- Customize the dock with new default applications
- Customize Emacs with Spacemacs: Emacs plus Vim!
- Set all preferences automatically and consistently
- macOS High Sierra: Tested with macOS High Sierra 10.13 (17A365).
- Completely Automated: Homebrew, Cask, and
mas-cliinstall everything. - Latest Versions: Includes Node, Perl, Python, and Ruby separate from macOS.
- Customized Terminal: Colors and fonts decyphered into editable preferences.
- Idempotent: This script is intended to be safe to run more than once.
macappstores://itunes.apple.com/app/id1209167288
diskx="$(diskutil list internal physical | sed '/^\//!d;s/^\(.*\)\ (.*):/\1/')"
diskutil zeroDisk $diskx diskutil partitionDisk $diskx 2 GPT \ jhfs+ "Install" 6G \ apfs $(ruby -e "print '$(hostname -s)'.capitalize") R
sudo "/Applications/Install macOS High Sierra.app/Contents/Resources/createinstallmedia" \ --applicationpath "/Applications/Install macOS High Sierra.app" --nointeraction \ --volume "/Volumes/Install"
Copyright 2017 Peter T Bosse II
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
if test -z "${1}"; then
osascript - "${0}" << EOF > /dev/null 2>&1
<<new_term.applescript>>
EOF
fion run { _this }
tell app "Terminal" to do script "source " & quoted form of _this & " 0"
end runask () {
osascript - "${1}" "${2}" "${3}" << EOF 2> /dev/null
<<ask.applescript>>
EOF
}on run { _title, _action, _default }
tell app "System Events" to return text returned of (display dialog _title with title _title buttons { "Cancel", _action } default answer _default)
end runask2 () {
osascript - "$1" "$2" "$3" "$4" "$5" "$6" << EOF 2> /dev/null
<<ask2.applescript>>
EOF
}on run { _text, _title, _cancel, _action, _default, _hidden }
tell app "Terminal" to return text returned of (display dialog _text with title _title buttons { _cancel, _action } cancel button _cancel default button _action default answer _default hidden answer _hidden)
end runp () {
printf "\n\033[1m\033[34m%s\033[0m\n\n" "${1}"
}run () {
osascript - "${1}" "${2}" "${3}" << EOF 2> /dev/null
<<run.applescript>>
EOF
}on run { _title, _cancel, _action }
tell app "Terminal" to return button returned of (display dialog _title with title _title buttons { _cancel, _action } cancel button 1 default button 2 giving up after 5)
end runinit () {
init_sudo
init_cache
init_no_sleep
init_hostname
init_perms
init_maskeep
init_updates
config_new_account
config_rm_sudoers
}
if test "${1}" = 0; then
printf "\n$(which init)\n"
fiinit_paths () {
test -x "/usr/libexec/path_helper" && \
eval $(/usr/libexec/path_helper -s)
}init_sudo () {
printf "%s\n" "%wheel ALL=(ALL) NOPASSWD: ALL" | \
sudo tee "/etc/sudoers.d/wheel" > /dev/null && \
sudo dscl /Local/Default append /Groups/wheel GroupMembership "$(whoami)"
}init_cache () {
grep -q "CACHES" "/etc/zshenv" 2> /dev/null || \
a=$(osascript << EOF 2> /dev/null
<<init_cache.applescript>>
EOF
) && \
test -d "${a}" || \
a="${HOME}/Library/Caches/"
grep -q "CACHES" "/etc/zshenv" 2> /dev/null || \
printf "%s\n" \
"export CACHES=\"${a}\"" \
"export HOMEBREW_CACHE=\"${a}/brew\"" \
"export BREWFILE=\"${a}/brew/Brewfile\"" | \
sudo tee -a "/etc/zshenv" > /dev/null
. "/etc/zshenv"
if test -d "${CACHES}/upd"; then
sudo chown -R "$(whoami)" "/Library/Updates"
rsync -a --delay-updates \
"${CACHES}/upd/" "/Library/Updates/"
fi
}on run
return text 1 through -2 of POSIX path of (choose folder with prompt "Select Installation Cache Location")
end runinit_no_sleep () {
sudo pmset -a sleep 0
sudo pmset -a disksleep 0
}init_hostname () {
a=$(ask2 "Set Computer Name and Hostname" "Set Hostname" "Cancel" "Set Hostname" $(ruby -e "print '$(hostname -s)'.capitalize") "false")
if test -n $a; then
sudo scutil --set ComputerName $(ruby -e "print '$a'.capitalize")
sudo scutil --set HostName $(ruby -e "print '$a'.downcase")
fi
}init_perms () {
printf "%s\n" "${_dest}" | \
while IFS="$(printf '\t')" read d; do
test -d "${d}" || sudo mkdir -p "${d}"
sudo chgrp -R admin "${d}"
sudo chmod -R g+w "${d}"
done
}| Location | Install Path |
|---|---|
| /usr/local/bin | |
| /Library/Desktop Pictures | |
| colorpickerdir | /Library/ColorPickers |
| fontdir | /Library/Fonts |
| input_methoddir | /Library/Input Methods |
| prefpanedir | /Library/PreferencePanes |
| qlplugindir | /Library/QuickLook |
| screen_saverdir | /Library/Screen Savers |
| /Library/User Pictures |
init_devtools () {
p="${HOMEBREW_CACHE}/Cask/Command Line Tools (macOS High Sierra version 10.13).pkg"
i="com.apple.pkg.CLTools_SDK_macOS1013"
if test -f "${p}"; then
if ! pkgutil --pkg-info "${i}" > /dev/null 2>&1; then
sudo installer -pkg "${p}" -target /
fi
else
xcode-select --install
fi
}init_xcode () {
if test -f ${HOMEBREW_CACHE}/Cask/xcode*.xip; then
p "Installing Xcode"
dest="${HOMEBREW_CACHE}/Cask/xcode"
if ! test -d "$dest"; then
pkgutil --expand ${HOMEBREW_CACHE}/Cask/xcode*.xip "$dest"
curl --location --silent \
"https://gist.githubusercontent.com/pudquick/ff412bcb29c9c1fa4b8d/raw/24b25538ea8df8d0634a2a6189aa581ccc6a5b4b/parse_pbzx2.py" | \
python - "${dest}/Content"
find "${dest}" -empty -name "*.xz" -type f -print0 | \
xargs -0 -l 1 rm
find "${dest}" -name "*.xz" -print0 | \
xargs -0 -L 1 gunzip
cat ${dest}/Content.part* > \
${dest}/Content.cpio
fi
cd /Applications && \
sudo cpio -dimu --file=${dest}/Content.cpio
for pkg in /Applications/Xcode*.app/Contents/Resources/Packages/*.pkg; do
sudo installer -pkg "$pkg" -target /
done
x="$(find '/Applications' -maxdepth 1 -regex '.*/Xcode[^ ]*.app' -print -quit)"
if test -n "${x}"; then
sudo xcode-select -s "${x}"
sudo xcodebuild -license accept
fi
fi
}init_updates () {
sudo softwareupdate --install --all
}sudo lsof -c softwareupdated -F -r 2 | sed '/^n\//!d;/com.apple.SoftwareUpdate/!d;s/^n//' sudo lsof -c storedownloadd -F -r 2 | sed '/^n\//!d;/com.apple.appstore/!d;s/^n//'
init_maskeep () {
sudo softwareupdate --reset-ignored > /dev/null
cat << EOF > "/usr/local/bin/maskeep"
<<maskeep.sh>>
EOF
chmod a+x "/usr/local/bin/maskeep"
rehash
config_launchd "/Library/LaunchDaemons/com.github.ptb.maskeep.plist" "$_maskeep_launchd" "sudo" ""
}| Command | Entry | Type | Value |
|---|---|---|---|
| add | :KeepAlive | bool | false |
| add | :Label | string | com.github.ptb.maskeep |
| add | :ProcessType | string | Background |
| add | :Program | string | /usr/local/bin/maskeep |
| add | :RunAtLoad | bool | true |
| add | :StandardErrorPath | string | /dev/stderr |
| add | :StandardOutPath | string | /dev/stdout |
| add | :UserName | string | root |
| add | :WatchPaths | array | |
| add | :WatchPaths:0 | string | $(sudo find ‘/private/var/folders’ -name ‘com.apple.SoftwareUpdate’ -type d -user _softwareupdate -print -quit 2> /dev/null) |
| add | :WatchPaths:1 | string | $(sudo -u \#501 – sh -c ‘getconf DARWIN_USER_CACHE_DIR’ 2> /dev/null)com.apple.appstore |
| add | :WatchPaths:2 | string | $(sudo -u \#502 – sh -c ‘getconf DARWIN_USER_CACHE_DIR’ 2> /dev/null)com.apple.appstore |
| add | :WatchPaths:3 | string | $(sudo -u \#503 – sh -c ‘getconf DARWIN_USER_CACHE_DIR’ 2> /dev/null)com.apple.appstore |
| add | :WatchPaths:4 | string | /Library/Updates |
#!/bin/sh
asdir="/Library/Caches/storedownloadd"
as1="\$(sudo -u \\#501 -- sh -c 'getconf DARWIN_USER_CACHE_DIR' 2> /dev/null)com.apple.appstore"
as2="\$(sudo -u \\#502 -- sh -c 'getconf DARWIN_USER_CACHE_DIR' 2> /dev/null)com.apple.appstore"
as3="\$(sudo -u \\#503 -- sh -c 'getconf DARWIN_USER_CACHE_DIR' 2> /dev/null)com.apple.appstore"
upd="/Library/Updates"
sudir="/Library/Caches/softwareupdated"
su="\$(sudo find '/private/var/folders' -name 'com.apple.SoftwareUpdate' -type d -user _softwareupdate 2> /dev/null)"
for i in 1 2 3 4 5; do
mkdir -m a=rwxt -p "\$asdir"
for as in "\$as1" "\$as2" "\$as3" "\$upd"; do
test -d "\$as" && \
find "\${as}" -type d -print | \\
while read a; do
b="\${asdir}/\$(basename \$a)"
mkdir -p "\${b}"
find "\${a}" -type f -print | \\
while read c; do
d="\$(basename \$c)"
test -e "\${b}/\${d}" || \\
ln "\${c}" "\${b}/\${d}" && \\
chmod 666 "\${b}/\${d}"
done
done
done
mkdir -m a=rwxt -p "\${sudir}"
find "\${su}" -name "*.tmp" -type f -print | \\
while read a; do
d="\$(basename \$a)"
test -e "\${sudir}/\${d}.xar" ||
ln "\${a}" "\${sudir}/\${d}.xar" && \\
chmod 666 "\${sudir}/\${d}.xar"
done
sleep 1
done
exit 0install () {
install_macos_sw
install_node_sw
install_perl_sw
install_python_sw
install_ruby_sw
which config
}install_macos_sw () {
p "Installing macOS Software"
install_paths
install_brew
install_brewfile_taps
install_brewfile_brew_pkgs
install_brewfile_cask_args
install_brewfile_cask_pkgs
install_brewfile_mas_apps
x=$(find '/Applications' -maxdepth 1 -regex '.*/Xcode[^ ]*.app' -print -quit)
if test -n "$x"; then
sudo xcode-select -s "$x"
sudo xcodebuild -license accept
fi
brew bundle --file="${BREWFILE}"
x=$(find '/Applications' -maxdepth 1 -regex '.*/Xcode[^ ]*.app' -print -quit)
if test -n "$x"; then
sudo xcode-select -s "$x"
sudo xcodebuild -license accept
fi
install_links
sudo xattr -rd "com.apple.quarantine" "/Applications" > /dev/null 2>&1
sudo chmod -R go=u-w "/Applications" > /dev/null 2>&1
}install_paths () {
if ! grep -Fq "/usr/local/sbin" /etc/paths; then
sudo sed -i "" -e "/\/usr\/sbin/{x;s/$/\/usr\/local\/sbin/;G;}" /etc/paths
fi
}install_brew () {
if ! which brew > /dev/null; then
ruby -e \
"$(curl -Ls 'https://github.com/Homebrew/install/raw/master/install')" \
< /dev/null > /dev/null 2>&1
fi
printf "" > "${BREWFILE}"
brew analytics off
brew update
brew doctor
brew tap "homebrew/bundle"
}install_brewfile_taps () {
printf "%s\n" "${_taps}" | \
while IFS="$(printf '\t')" read tap; do
printf 'tap "%s"\n' "${tap}" >> "${BREWFILE}"
done
printf "\n" >> "${BREWFILE}"
}| Homebrew Tap Name | Reference URL |
|---|---|
| caskroom/cask | https://github.com/caskroom/homebrew-cask |
| caskroom/fonts | https://github.com/caskroom/homebrew-fonts |
| caskroom/versions | https://github.com/caskroom/homebrew-versions |
| homebrew/bundle | https://github.com/Homebrew/homebrew-bundle |
| homebrew/command-not-found | https://github.com/Homebrew/homebrew-command-not-found |
| homebrew/nginx | https://github.com/Homebrew/homebrew-nginx |
| homebrew/php | https://github.com/Homebrew/homebrew-php |
| homebrew/services | https://github.com/Homebrew/homebrew-services |
| ptb/custom | https://github.com/ptb/homebrew-custom |
| railwaycat/emacsmacport | https://github.com/railwaycat/homebrew-emacsmacport |
install_brewfile_brew_pkgs () {
printf "%s\n" "${_pkgs}" | \
while IFS="$(printf '\t')" read pkg; do
# printf 'brew "%s", args: [ "force-bottle" ]\n' "${pkg}" >> "${BREWFILE}"
printf 'brew "%s"\n' "${pkg}" >> "${BREWFILE}"
done
printf "\n" >> "${BREWFILE}"
}install_brewfile_cask_args () {
printf 'cask_args \' >> "${BREWFILE}"
printf "%s\n" "${_args}" | \
while IFS="$(printf '\t')" read arg dir; do
printf '\n %s: "%s",' "${arg}" "${dir}" >> "${BREWFILE}"
done
sed -i "" -e '$ s/,/\
/' "${BREWFILE}"
}install_brewfile_cask_pkgs () {
printf "%s\n" "${_casks}" | \
while IFS="$(printf '\t')" read cask; do
printf 'cask "%s"\n' "${cask}" >> "${BREWFILE}"
done
printf "\n" >> "${BREWFILE}"
}install_brewfile_mas_apps () {
open "/Applications/App Store.app"
run "Sign in to the App Store with your Apple ID" "Cancel" "OK"
MASDIR="$(getconf DARWIN_USER_CACHE_DIR)com.apple.appstore"
sudo chown -R "$(whoami)" "${MASDIR}"
rsync -a --delay-updates \
"${CACHES}/mas/" "${MASDIR}/"
printf "%s\n" "${_mas}" | \
while IFS="$(printf '\t')" read app id; do
printf 'mas "%s", id: %s\n' "${app}" "${id}" >> "${BREWFILE}"
done
}| App Name | App ID | App Store URL |
|---|---|---|
| 1Password | 443987910 | https://itunes.apple.com/app/id443987910 |
| Affinity Photo | 824183456 | https://itunes.apple.com/app/id824183456 |
| Coffitivity | 659901392 | https://itunes.apple.com/app/id659901392 |
| Duplicate Photos Fixer Pro | 963642514 | https://itunes.apple.com/app/id963642514 |
| Growl | 467939042 | https://itunes.apple.com/app/id467939042 |
| HardwareGrowler | 475260933 | https://itunes.apple.com/app/id475260933 |
| I Love Stars | 402642760 | https://itunes.apple.com/app/id402642760 |
| Icon Slate | 439697913 | https://itunes.apple.com/app/id439697913 |
| Justnotes | 511230166 | https://itunes.apple.com/app/id511230166 |
| Keynote | 409183694 | https://itunes.apple.com/app/id409183694 |
| Metanota Pro | 515250764 | https://itunes.apple.com/app/id515250764 |
| Numbers | 409203825 | https://itunes.apple.com/app/id409203825 |
| Pages | 409201541 | https://itunes.apple.com/app/id409201541 |
| WiFi Explorer | 494803304 | https://itunes.apple.com/app/id494803304 |
| Xcode | 497799835 | https://itunes.apple.com/app/id497799835 |
| macOS High Sierra | 1209167288 |