Skip to content

ptb/mac-setup

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

76 Commits
 
 
 
 
 
 

Repository files navigation

Mac Setup

From zero to fully installed and configured, in an hour.

Overview

Quick Start

case "${SHELL}" in
  (*zsh) ;;
  (*) chsh -s "$(which zsh)"; exit 1 ;;
esac
curl --location --silent \
  "https://github.com/ptb/mac-setup/raw/develop/mac-setup.command" | \
  source /dev/stdin 0
init && install && config
custom && personalize_all

init

  • 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

install

  • 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

config

  • 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

custom

  • 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

Features

  • macOS High Sierra: Tested with macOS High Sierra 10.13 (17A365).
  • Completely Automated: Homebrew, Cask, and mas-cli install 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.

Walkthrough

Clone Internal Hard Disk to External Disk

Select the External Disk as Startup Disk

Download macOS Install from the App Store

macappstores://itunes.apple.com/app/id1209167288

Open /Applications/Utilities/Terminal.app

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"

Select the Install Disk as Startup Disk

License

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.

Initialize

Initialize New Terminal

if test -z "${1}"; then
  osascript - "${0}" << EOF > /dev/null 2>&1
<<new_term.applescript>>
EOF
fi

new_term.applescript

on run { _this }
  tell app "Terminal" to do script "source " & quoted form of _this & " 0"
end run

Define Function ask

ask () {
  osascript - "${1}" "${2}" "${3}" << EOF 2> /dev/null
<<ask.applescript>>
EOF
}

ask.applescript

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 run

Define Function ask2

ask2 () {
  osascript - "$1" "$2" "$3" "$4" "$5" "$6" << EOF 2> /dev/null
<<ask2.applescript>>
EOF
}

ask2.applescript

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 run

Define Function p

p () {
  printf "\n\033[1m\033[34m%s\033[0m\n\n" "${1}"
}

Define Function run

run () {
  osascript - "${1}" "${2}" "${3}" << EOF 2> /dev/null
<<run.applescript>>
EOF
}

run.applescript

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 run

Define Function init

init () {
  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"
fi

Define Function init_paths

init_paths () {
  test -x "/usr/libexec/path_helper" && \
    eval $(/usr/libexec/path_helper -s)
}

Eliminate Prompts for Password

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)"
}

Select Installation Cache Location

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
}

init_cache.applescript

on run
  return text 1 through -2 of POSIX path of (choose folder with prompt "Select Installation Cache Location")
end run

Set Defaults for Sleep

init_no_sleep () {
  sudo pmset -a sleep 0
  sudo pmset -a disksleep 0
}

Set Hostname from DNS

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
}

Set Permissions on Install Destinations

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
}

_dest

LocationInstall 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

Install Developer Tools

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
}

Install Xcode

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
}

Install macOS Updates

init_updates () {
  sudo softwareupdate --install --all
}

Save Mac App Store Packages

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" ""
}

_maskeep_launchd

CommandEntryTypeValue
add:KeepAliveboolfalse
add:Labelstringcom.github.ptb.maskeep
add:ProcessTypestringBackground
add:Programstring/usr/local/bin/maskeep
add:RunAtLoadbooltrue
add:StandardErrorPathstring/dev/stderr
add:StandardOutPathstring/dev/stdout
add:UserNamestringroot
add:WatchPathsarray
add:WatchPaths:0string$(sudo find ‘/private/var/folders’ -name ‘com.apple.SoftwareUpdate’ -type d -user _softwareupdate -print -quit 2> /dev/null)
add:WatchPaths:1string$(sudo -u \#501 – sh -c ‘getconf DARWIN_USER_CACHE_DIR’ 2> /dev/null)com.apple.appstore
add:WatchPaths:2string$(sudo -u \#502 – sh -c ‘getconf DARWIN_USER_CACHE_DIR’ 2> /dev/null)com.apple.appstore
add:WatchPaths:3string$(sudo -u \#503 – sh -c ‘getconf DARWIN_USER_CACHE_DIR’ 2> /dev/null)com.apple.appstore
add:WatchPaths:4string/Library/Updates

/usr/local/bin/maskeep

#!/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 0

Install

Define Function install

install () {
  install_macos_sw
  install_node_sw
  install_perl_sw
  install_python_sw
  install_ruby_sw

  which config
}

Install macOS Software with brew

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
}

Add /usr/local/bin/sbin to Default Path

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 Homebrew Package Manager

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"
}

Add Homebrew Taps to Brewfile

install_brewfile_taps () {
  printf "%s\n" "${_taps}" | \
  while IFS="$(printf '\t')" read tap; do
    printf 'tap "%s"\n' "${tap}" >> "${BREWFILE}"
  done
  printf "\n" >> "${BREWFILE}"
}

_taps

Homebrew Tap NameReference URL
caskroom/caskhttps://github.com/caskroom/homebrew-cask
caskroom/fontshttps://github.com/caskroom/homebrew-fonts
caskroom/versionshttps://github.com/caskroom/homebrew-versions
homebrew/bundlehttps://github.com/Homebrew/homebrew-bundle
homebrew/command-not-foundhttps://github.com/Homebrew/homebrew-command-not-found
homebrew/nginxhttps://github.com/Homebrew/homebrew-nginx
homebrew/phphttps://github.com/Homebrew/homebrew-php
homebrew/serviceshttps://github.com/Homebrew/homebrew-services
ptb/customhttps://github.com/ptb/homebrew-custom
railwaycat/emacsmacporthttps://github.com/railwaycat/homebrew-emacsmacport

Add Homebrew Packages to Brewfile

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}"
}

_pkgs

Homebrew Package NameReference URL
aspellhttp://aspell.net/
bashhttps://www.gnu.org/software/bash/
certbothttps://certbot.eff.org/
chromedriverhttps://sites.google.com/a/chromium.org/chromedriver/
coreutilshttps://www.gnu.org/software/coreutils/
dashhttp://gondor.apana.org.au/~herbert/dash/
dutihttps://github.com/moretension/duti
e2fsprogshttps://e2fsprogs.sourceforge.io/
fasdhttps://github.com/clvv/fasd
fdupeshttps://github.com/adrianlopezroche/fdupes
gawkhttps://www.gnu.org/software/gawk/
getmailhttp://pyropus.ca/software/getmail/
githttps://git-scm.com/
git-flowhttp://nvie.com/posts/a-successful-git-branching-model/
git-lfshttps://git-lfs.github.com/
gnu-sedhttps://www.gnu.org/software/sed/
gnupghttps://www.gnupg.org/
gpachttps://gpac.wp.imt.fr/
httpiehttps://httpie.org/
hubhttps://hub.github.com/
ievmshttps://xdissent.github.io/ievms/
imagemagickhttps://www.imagemagick.org/
mashttps://github.com/argon/mas
mercurial