Skip to content

Instantly share code, notes, and snippets.

@toonetown
Last active August 26, 2021 16:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save toonetown/48101686e509fda81335 to your computer and use it in GitHub Desktop.
Save toonetown/48101686e509fda81335 to your computer and use it in GitHub Desktop.
Bootstraps a Homebrew development environment, with optional packages (taps, brews, casks, and services)
###########
# Installs and/or updates homebrew and various components
#
#!/bin/bash
: ${HOMEBREW_INST_URL:="https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh"}
: ${HB_BOOTSTRAP_GIST_URL:="https://gist.githubusercontent.com/toonetown/48101686e509fda81335/raw"}
if [ $# -eq 0 ]; then
echo "Usage: ${0} [--fetch-only] <PACKAGES...>"
echo ""
echo "The format for package is one of:"
echo " t:user/repository (for taps - prefix tap with an asterisk for pinned tap, i.e. t:*user/repository)"
echo " b:brew-name (for brew formulas)"
echo " c:cask-name (for casks)"
echo " s:service-name (for services - prefix with an asterisk to run at boot, i.e. s:*service-name)"
echo " n:npm-package (for node packages)"
echo ""
echo "Packages will be installed (or updated if they are already installed and are outdated) one at a time in the"
echo "order listed above (taps first, then brews, then casks, then services, then node packages)."
echo ""
echo "If you specify a cask, then the tap for homebrew-cask will be added. If you specify a service, then the"
echo "tap for homebrew/services will be added. If you specify a node package, then the node brew will be added."
echo ""
echo "To run this directly from curl (i.e. to bootstrap a system), you can execute:"
echo " curl -sSL ${HB_BOOTSTRAP_GIST_URL} | bash -s -- <PACKAGES...>"
echo ""
exit 1
fi
install_xcode() {
xcode-select -p >/dev/null 2>&1 || {
echo ""
echo "==========================="
echo "Installing Developer Tools..."
echo "==========================="
INPROGRESS_FLAG="/tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress"
touch "${INPROGRESS_FLAG}"
trap 'rm -f "${INPROGRESS_FLAG}"' EXIT
while ! ( xcode-select -p &>/dev/null ); do softwareupdate -i -a || exit $?; done
rm -f "${INPROGRESS_FLAG}"
}
}
check_homebrew() {
echo ""
echo "==========================="
echo "Checking Homebrew installation..."
echo "==========================="
(mkdir -p "$(brew --cache)" &>/dev/null && chmod 700 "$(brew --cache)") || return $?
brew doctor || return $?
for d in cache cellar; do
[ -d "$(brew --${d})" ] || mkdir -p "$(brew --${d})" || return $?
done
}
install_homebrew() {
install_xcode || return $?
which brew >/dev/null && {
echo ""
echo "==========================="
echo "Updating Homebrew..."
echo "==========================="
brew update || return $?
} || {
echo ""
echo "==========================="
echo "Installing Homebrew..."
echo "==========================="
if [ "$(id -u)" == "0" ]; then
if [ "${FORCE_HOMEBREW_ROOT_INSTALL}" == "YES" ]; then
/bin/bash -c "$(curl -fsSL "${HOMEBREW_INST_URL}" \
| grep -v 'Process.uid == 0' \
| sed -e 's/EOABORT unless `dsmemberutil/EOABORT if `dsmemberutil/g')" || return $?
else
echo "Not installing homebrew as root - set FORCE_HOMEBREW_ROOT_INSTALL"
echo "to \"YES\" (case-sensitive) to override this behavior"
exit 1
fi
else
r/bin/bash -c "$(curl -fsSL "${HOMEBREW_INST_URL}")" || return $?
fi
}
}
add_tap() {
TAP_NAME="${1}"
[ -n "${TAP_NAME}" ] || { echo "Missing tap name"; return 1; }
brew tap | grep -q "^${TAP_NAME}$" || {
echo ""
echo "==========================="
echo "Tapping ${TAP_NAME}..."
echo "==========================="
brew tap ${TAP_NAME}
}
return $?
}
pin_tap() {
TAP_NAME="${1}"
[ -n "${TAP_NAME}" ] || { echo "Missing tap name"; return 1; }
add_tap "${TAP_NAME}" || return $?
brew tap-info "${TAP_NAME}" | grep -q "^${TAP_NAME}: pinned" || {
echo ""
echo "==========================="
echo "Pinning tap ${TAP_NAME}..."
echo "==========================="
brew tap-pin ${TAP_NAME}
}
return $?
}
fetch_brews() {
if [ $# -gt 0 ]; then
echo ""
echo "==========================="
echo "Fetching brews and dependencies..."
echo "==========================="
brew fetch --formula --deps "$@" || return $?
fi
return 0
}
fetch_casks() {
if [ $# -gt 0 ]; then
echo ""
echo "==========================="
echo "Fetching casks..."
echo "==========================="
brew fetch --cask "$@" || return $?
fi
return 0
}
install_brew() {
BREW_NAME="${1}"
[ -n "${BREW_NAME}" ] || { echo "Missing brew name"; return 1; }
if echo "${BREW_NAME}" | grep -q "\/"; then
LIST_OPT="--full-name"
else
LIST_OPT=""
fi
brew list --formula ${LIST_OPT} 2>&1 | grep -q "^${BREW_NAME}$" && {
brew outdated --formula "${BREW_NAME}" &>/dev/null || {
echo ""
echo "==========================="
echo "Upgrading brew ${BREW_NAME}..."
echo "==========================="
brew upgrade --cleanup ${BREW_NAME}
}
} || {
echo ""
echo "==========================="
echo "Installing brew ${BREW_NAME}..."
echo "==========================="
brew install --formula ${BREW_NAME}
}
return $?
}
install_cask() {
CASK_NAME="${1}"
[ -n "${CASK_NAME}" ] || { echo "Missing cask name"; return 1; }
brew list --cask 2>&1 | grep -q "^${CASK_NAME}$" && {
if brew outdated --cask 2>&1 | grep -q "^${CASK_NAME}$"; then
echo ""
echo "==========================="
echo "Upgrading cask ${CASK_NAME}..."
echo "==========================="
brew upgrade --cask ${CASK_NAME}
fi
} || {
echo ""
echo "==========================="
echo "Installing cask ${CASK_NAME}..."
echo "==========================="
brew install --cask ${CASK_NAME}
}
return $?
}
start_boot_service() {
SERVICE_NAME="${1}"
[ -n "${SERVICE_NAME}" ] || { echo "Missing service name"; return 1; }
sudo -p "Password for managing boot services:" brew services list | grep -q "^${SERVICE_NAME} \+started" && {
echo ""
echo "==========================="
echo "Restarting boot service ${SERVICE_NAME}..."
echo "==========================="
sudo brew services restart ${SERVICE_NAME}
} || {
echo ""
echo "==========================="
echo "Starting boot service ${SERVICE_NAME}..."
echo "==========================="
sudo brew services start ${SERVICE_NAME}
}
if [ $? -ne 0 ]; then
echo "Boot service ${SERVICE_NAME} failed to start" >&2
fi
return 0
}
start_service() {
SERVICE_NAME="${1}"
[ -n "${SERVICE_NAME}" ] || { echo "Missing service name"; return 1; }
brew services list | grep -q "^${SERVICE_NAME} \+started" && {
echo ""
echo "==========================="
echo "Restarting service ${SERVICE_NAME}..."
echo "==========================="
brew services restart ${SERVICE_NAME}
} || {
echo ""
echo "==========================="
echo "Starting service ${SERVICE_NAME}..."
echo "==========================="
brew services start ${SERVICE_NAME}
}
if [ $? -ne 0 ]; then
echo "Service ${SERVICE_NAME} failed to start" >&2
fi
return 0
}
install_npm_pkg() {
NPM_PACKAGE_NAME="${1}"
[ -n "${NPM_PACKAGE_NAME}" ] || { echo "Missing node package name"; return 1; }
npm -g ls "${NPM_PACKAGE_NAME}" &>/dev/null && {
if npm -g outdated 2>&1 | grep -q "^${NPM_PACKAGE_NAME} "; then
echo ""
echo "==========================="
echo "Upgrading npm ${NPM_PACKAGE_NAME}..."
echo "==========================="
npm -g -s install ${NPM_PACKAGE_NAME}@latest
fi
} || {
echo ""
echo "==========================="
echo "Installing npm ${NPM_PACKAGE_NAME}..."
echo "==========================="
npm -g -s install ${NPM_PACKAGE_NAME}
}
return $?
}
TAP_LIST=""
PRE_CASK_LIST=""
BREW_LIST=""
CASK_LIST=""
SERVICE_LIST=""
NPM_LIST=""
if [ "${1}" == "--fetch-only" ]; then DO_FETCH_ONLY="yes"; shift; else DO_FETCH_ONLY="no"; fi
for i in $@; do
case "${i:0:2}" in
"t:")
TAP_LIST="${TAP_LIST} ${i:2}"
;;
"b:")
if [ "${i:2}" == "brew-cask" ]; then
TAP_LIST="${TAP_LIST} homebrew/cask"
elif [ "${i:2}" != "brew" ]; then
BREW_LIST="${BREW_LIST} ${i:2}"
fi
;;
"c:")
if [ "${i:2}" == "xcode" -a -d /Applications/Xcode.app ]; then
brew list --cask 2>&1 | grep -q "^xcode$" || {
echo "Appstore Xcode already installed"
}
else
TAP_LIST="${TAP_LIST} homebrew/cask"
if [ "${i:2:4}" == "java" ]; then
PRE_CASK_LIST="${PRE_CASK_LIST} ${i:2}"
else
CASK_LIST="${CASK_LIST} ${i:2}"
fi
fi
;;
"s:")
SERVICE_LIST="${SERVICE_LIST} ${i:2}"
TAP_LIST="${TAP_LIST} homebrew/services"
;;
"n:")
BREW_LIST="${BREW_LIST} node"
NPM_LIST="${NPM_LIST} ${i:2}"
;;
*)
echo "Invalid package '${i}'"
exit 1
;;
esac
done
install_homebrew || exit $?
for i in $(ruby -e "puts \"${TAP_LIST}\".split(/\s+/).sort.uniq"); do
if [ "${i:0:1}" == "*" ]; then
pin_tap ${i:1} || exit $?
else
add_tap ${i} || exit $?
fi
done
check_homebrew || exit $?
if [ "${DO_FETCH_ONLY}" == "yes" ]; then
fetch_brews $(ruby -e "puts \"${BREW_LIST}\".split(/[\s\*]+/).sort.uniq") || exit $?
fetch_casks $(ruby -e "puts \"${PRE_CASK_LIST} ${CASK_LIST}\".split(/[\s\*]+/).sort.uniq") || exit $?
else
for i in $(ruby -e "puts \"${PRE_CASK_LIST}\".split(/\s+/).sort.uniq"); do
install_cask ${i} || exit $?
done
for i in $(ruby -e "puts \"${BREW_LIST}\".split(/\s+/).sort.uniq"); do
for j in $(brew deps --formula --skip-optional --skip-build ${i} 2>/dev/null | grep -v '^ruby$'); do
install_brew ${j} || exit $?
done
install_brew ${i} || exit $?
done
for i in $(ruby -e "puts \"${CASK_LIST}\".split(/\s+/).sort.uniq"); do
install_cask ${i} || exit $?
done
for i in $(ruby -e "puts \"${SERVICE_LIST}\".split(/\s+/).sort.uniq"); do
if [ "${i:0:1}" == "*" ]; then
start_boot_service ${i:1} || exit $?
else
start_service ${i} || exit $?
fi
done
if [ -n "${NPM_LIST}" ]; then
install_npm_pkg npm || exit $?
for i in $(ruby -e "puts \"${NPM_LIST}\".split(/\s+/).sort.uniq"); do
install_npm_pkg ${i} || exit $?
done
fi
fi
@toonetown
Copy link
Author

toonetown commented Sep 10, 2016

A helpful set of defaults are:

curl -sSL https://gist.githubusercontent.com/toonetown/48101686e509fda81335/raw | bash -s -- \
        b:brew-bootstrap \
        t:toonetown/extras b:toonetown-extras s:toonetown-extras \
        b:rmate b:pretty-prompt b:scm-prompt \
        b:bash-completion b:bash-completion-env \
        b:mas b:swup

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment