#!/bin/bash # We don't need return codes for "$(command)", only stdout is needed. # Allow `[[ -n "$(command)" ]]`, `func "$(command)"`, pipes, etc. # shellcheck disable=SC2312 set -u abort() { printf "%s\n" "$@" >&2 exit 1 } # Fail fast with a concise message when not using bash # Single brackets are needed here for POSIX compatibility # shellcheck disable=SC2292 if [ -z "${BASH_VERSION:-}" ] then abort "Bash is required to interpret this script." fi # Check if script is run with force-interactive mode in CI if [[ -n "${CI-}" && -n "${INTERACTIVE-}" ]] then abort "Cannot run force-interactive mode in CI." fi # Check if both `INTERACTIVE` and `NONINTERACTIVE` are set # Always use single-quoted strings with `exp` expressions # shellcheck disable=SC2016 if [[ -n "${INTERACTIVE-}" && -n "${NONINTERACTIVE-}" ]] then abort 'Both `$INTERACTIVE` and `$NONINTERACTIVE` are set. Please unset at least one variable and try again.' fi # Check if script is run in POSIX mode if [[ -n "${POSIXLY_CORRECT+1}" ]] then abort 'Bash must not run in POSIX mode. Please unset POSIXLY_CORRECT and try again.' fi usage() { cat <${tty_bold} %s${tty_reset}\n" "$(shell_join "$@")" } warn() { printf "${tty_red}Warning${tty_reset}: %s\n" "$(chomp "$1")" >&2 } # Check if script is run non-interactively (e.g. CI) # If it is run non-interactively we should not prompt for passwords. # Always use single-quoted strings with `exp` expressions # shellcheck disable=SC2016 if [[ -z "${NONINTERACTIVE-}" ]] then if [[ -n "${CI-}" ]] then warn 'Running in non-interactive mode because `$CI` is set.' NONINTERACTIVE=1 elif [[ ! -t 0 ]] then if [[ -z "${INTERACTIVE-}" ]] then warn 'Running in non-interactive mode because `stdin` is not a TTY.' NONINTERACTIVE=1 else warn 'Running in interactive mode despite `stdin` not being a TTY because `$INTERACTIVE` is set.' fi fi else ohai 'Running in non-interactive mode because `$NONINTERACTIVE` is set.' fi # USER isn't always set so provide a fall back for the installer and subprocesses. if [[ -z "${USER-}" ]] then USER="$(chomp "$(id -un)")" export USER fi # First check OS. OS="$(uname)" if [[ "${OS}" == "Linux" ]] then HOMEBREW_ON_LINUX=1 elif [[ "${OS}" == "Darwin" ]] then HOMEBREW_ON_MACOS=1 else abort "Homebrew is only supported on macOS and Linux." fi # Required installation paths. To install elsewhere (which is unsupported) # you can untar https://github.com/Homebrew/brew/tarball/master # anywhere you like. if [[ -n "${HOMEBREW_ON_MACOS-}" ]] then UNAME_MACHINE="$(/usr/bin/uname -m)" if [[ "${UNAME_MACHINE}" == "arm64" ]] then # On ARM macOS, this script installs to /opt/homebrew only HOMEBREW_PREFIX="/opt/homebrew" HOMEBREW_REPOSITORY="${HOMEBREW_PREFIX}" else # On Intel macOS, this script installs to /usr/local only HOMEBREW_PREFIX="/usr/local" HOMEBREW_REPOSITORY="${HOMEBREW_PREFIX}/Homebrew" fi HOMEBREW_CACHE="${HOME}/Library/Caches/Homebrew" STAT_PRINTF=("stat" "-f") PERMISSION_FORMAT="%A" CHOWN=("/usr/sbin/chown") CHGRP=("/usr/bin/chgrp") GROUP="admin" TOUCH=("/usr/bin/touch") INSTALL=("/usr/bin/install" -d -o "root" -g "wheel" -m "0755") else UNAME_MACHINE="$(uname -m)" # On Linux, this script installs to /home/linuxbrew/.linuxbrew only HOMEBREW_PREFIX="/home/linuxbrew/.linuxbrew" HOMEBREW_REPOSITORY="${HOMEBREW_PREFIX}/Homebrew" HOMEBREW_CACHE="${HOME}/.cache/Homebrew" STAT_PRINTF=("stat" "--printf") PERMISSION_FORMAT="%a" CHOWN=("/bin/chown") CHGRP=("/bin/chgrp") GROUP="$(id -gn)" TOUCH=("/bin/touch") INSTALL=("/usr/bin/install" -d -o "${USER}" -g "${GROUP}" -m "0755") fi CHMOD=("/bin/chmod") MKDIR=("/bin/mkdir" "-p") HOMEBREW_BREW_DEFAULT_GIT_REMOTE="https://github.com/Homebrew/brew" HOMEBREW_CORE_DEFAULT_GIT_REMOTE="https://github.com/Homebrew/homebrew-core" # Use remote URLs of Homebrew repositories from environment if set. HOMEBREW_BREW_GIT_REMOTE="${HOMEBREW_BREW_GIT_REMOTE:-"${HOMEBREW_BREW_DEFAULT_GIT_REMOTE}"}" HOMEBREW_CORE_GIT_REMOTE="${HOMEBREW_CORE_GIT_REMOTE:-"${HOMEBREW_CORE_DEFAULT_GIT_REMOTE}"}" # The URLs with and without the '.git' suffix are the same Git remote. Do not prompt. if [[ "${HOMEBREW_BREW_GIT_REMOTE}" == "${HOMEBREW_BREW_DEFAULT_GIT_REMOTE}.git" ]] then HOMEBREW_BREW_GIT_REMOTE="${HOMEBREW_BREW_DEFAULT_GIT_REMOTE}" fi if [[ "${HOMEBREW_CORE_GIT_REMOTE}" == "${HOMEBREW_CORE_DEFAULT_GIT_REMOTE}.git" ]] then HOMEBREW_CORE_GIT_REMOTE="${HOMEBREW_CORE_DEFAULT_GIT_REMOTE}" fi export HOMEBREW_{BREW,CORE}_GIT_REMOTE # TODO: bump version when new macOS is released or announced MACOS_NEWEST_UNSUPPORTED="15.0" # TODO: bump version when new macOS is released MACOS_OLDEST_SUPPORTED="12.0" # For Homebrew on Linux REQUIRED_RUBY_VERSION=2.6 # https://github.com/Homebrew/brew/pull/6556 REQUIRED_GLIBC_VERSION=2.13 # https://docs.brew.sh/Homebrew-on-Linux#requirements REQUIRED_CURL_VERSION=7.41.0 # HOMEBREW_MINIMUM_CURL_VERSION in brew.sh in Homebrew/brew REQUIRED_GIT_VERSION=2.7.0 # HOMEBREW_MINIMUM_GIT_VERSION in brew.sh in Homebrew/brew # no analytics during installation export HOMEBREW_NO_ANALYTICS_THIS_RUN=1 export HOMEBREW_NO_ANALYTICS_MESSAGE_OUTPUT=1 unset HAVE_SUDO_ACCESS # unset this from the environment have_sudo_access() { if [[ ! -x "/usr/bin/sudo" ]] then return 1 fi local -a SUDO=("/usr/bin/sudo") if [[ -n "${SUDO_ASKPASS-}" ]] then SUDO+=("-A") elif [[ -n "${NONINTERACTIVE-}" ]] then SUDO+=("-n") fi if [[ -z "${HAVE_SUDO_ACCESS-}" ]] then if [[ -n "${NONINTERACTIVE-}" ]] then "${SUDO[@]}" -l mkdir &>/dev/null else "${SUDO[@]}" -v && "${SUDO[@]}" -l mkdir &>/dev/null fi HAVE_SUDO_ACCESS="$?" fi if [[ -n "${HOMEBREW_ON_MACOS-}" ]] && [[ "${HAVE_SUDO_ACCESS}" -ne 0 ]] then abort "Need sudo access on macOS (e.g. the user ${USER} needs to be an Administrator)!" fi return "${HAVE_SUDO_ACCESS}" } execute() { if ! "$@" then abort "$(printf "Failed during: %s" "$(shell_join "$@")")" fi } execute_sudo() { local -a args=("$@") if [[ "${EUID:-${UID}}" != "0" ]] && have_sudo_access then if [[ -n "${SUDO_ASKPASS-}" ]] then args=("-A" "${args[@]}") fi ohai "/usr/bin/sudo" "${args[@]}" execute "/usr/bin/sudo" "${args[@]}" else ohai "${args[@]}" execute "${args[@]}" fi } getc() { local save_state save_state="$(/bin/stty -g)" /bin/stty raw -echo IFS='' read -r -n 1 -d '' "$@" /bin/stty "${save_state}" } ring_bell() { # Use the shell's audible bell. if [[ -t 1 ]] then printf "\a" fi } wait_for_user() { local c echo echo "Press ${tty_bold}RETURN${tty_reset}/${tty_bold}ENTER${tty_reset} to continue or any other key to abort:" getc c # we test for \r and \n because some stuff does \r instead if ! [[ "${c}" == $'\r' || "${c}" == $'\n' ]] then exit 1 fi } major_minor() { echo "${1%%.*}.$( x="${1#*.}" echo "${x%%.*}" )" } version_gt() { [[ "${1%.*}" -gt "${2%.*}" ]] || [[ "${1%.*}" -eq "${2%.*}" && "${1#*.}" -gt "${2#*.}" ]] } version_ge() { [[ "${1%.*}" -gt "${2%.*}" ]] || [[ "${1%.*}" -eq "${2%.*}" && "${1#*.}" -ge "${2#*.}" ]] } version_lt() { [[ "${1%.*}" -lt "${2%.*}" ]] || [[ "${1%.*}" -eq "${2%.*}" && "${1#*.}" -lt "${2#*.}" ]] } check_run_command_as_root() { [[ "${EUID:-${UID}}" == "0" ]] || return # Allow Azure Pipelines/GitHub Actions/Docker/Concourse/Kubernetes to do everything as root (as it's normal there) [[ -f /.dockerenv ]] && return [[ -f /run/.containerenv ]] && return [[ -f /proc/1/cgroup ]] && grep -E "azpl_job|actions_job|docker|garden|kubepods" -q /proc/1/cgroup && return abort "Don't run this as root!" } should_install_command_line_tools() { if [[ -n "${HOMEBREW_ON_LINUX-}" ]] then return 1 fi if version_gt "${macos_version}" "10.13" then ! [[ -e "/Library/Developer/CommandLineTools/usr/bin/git" ]] else ! [[ -e "/Library/Developer/CommandLineTools/usr/bin/git" ]] || ! [[ -e "/usr/include/iconv.h" ]] fi } get_permission() { "${STAT_PRINTF[@]}" "${PERMISSION_FORMAT}" "$1" } user_only_chmod() { [[ -d "$1" ]] && [[ "$(get_permission "$1")" != 75[0145] ]] } exists_but_not_writable() { [[ -e "$1" ]] && ! [[ -r "$1" && -w "$1" && -x "$1" ]] } get_owner() { "${STAT_PRINTF[@]}" "%u" "$1" } file_not_owned() { [[ "$(get_owner "$1")" != "$(id -u)" ]] } get_group() { "${STAT_PRINTF[@]}" "%g" "$1" } file_not_grpowned() { [[ " $(id -G "${USER}") " != *" $(get_group "$1") "* ]] } # Please sync with 'test_ruby()' in 'Library/Homebrew/utils/ruby.sh' from the Homebrew/brew repository. test_ruby() { if [[ ! -x "$1" ]] then return 1 fi "$1" --enable-frozen-string-literal --disable=gems,did_you_mean,rubyopt -rrubygems -e \ "abort if Gem::Version.new(RUBY_VERSION.to_s.dup).to_s.split('.').first(2) != \ Gem::Version.new('${REQUIRED_RUBY_VERSION}').to_s.split('.').first(2)" 2>/dev/null } test_curl() { if [[ ! -x "$1" ]] then return 1 fi local curl_version_output curl_name_and_version curl_version_output="$("$1" --version 2>/dev/null)" curl_name_and_version="${curl_version_output%% (*}" version_ge "$(major_minor "${curl_name_and_version##* }")" "$(major_minor "${REQUIRED_CURL_VERSION}")" } test_git() { if [[ ! -x "$1" ]] then return 1 fi local git_version_output git_version_output="$("$1" --version 2>/dev/null)" if [[ "${git_version_output}" =~ "git version "([^ ]*).* ]] then version_ge "$(major_minor "${BASH_REMATCH[1]}")" "$(major_minor "${REQUIRED_GIT_VERSION}")" else abort "Unexpected Git version: '${git_version_output}'!" fi } # Search for the given executable in PATH (avoids a dependency on the `which` command) which() { # Alias to Bash built-in command `type -P` type -P "$@" } # Search PATH for the specified program that satisfies Homebrew requirements # function which is set above # shellcheck disable=SC2230 find_tool() { if [[ $# -ne 1 ]] then return 1 fi local executable while read -r executable do if [[ "${executable}" != /* ]] then warn "Ignoring ${executable} (relative paths don't work)" elif "test_$1" "${executable}" then echo "${executable}" break fi done < <(which -a "$1") } no_usable_ruby() { [[ -z "$(find_tool ruby)" ]] } outdated_glibc() { local glibc_version glibc_version="$(ldd --version | head -n1 | grep -o '[0-9.]*$' | grep -o '^[0-9]\+\.[0-9]\+')" version_lt "${glibc_version}" "${REQUIRED_GLIBC_VERSION}" } if [[ -n "${HOMEBREW_ON_LINUX-}" ]] && no_usable_ruby && outdated_glibc then abort "$( cat </dev/null then trap '/usr/bin/sudo -k' EXIT fi # Things can fail later if `pwd` doesn't exist. # Also sudo prints a warning message for no good reason cd "/usr" || exit 1 ####################################################################### script # shellcheck disable=SC2016 ohai 'Checking for `sudo` access (which may request your password)...' if [[ -n "${HOMEBREW_ON_MACOS-}" ]] then [[ "${EUID:-${UID}}" == "0" ]] || have_sudo_access elif ! [[ -w "${HOMEBREW_PREFIX}" ]] && ! [[ -w "/home/linuxbrew" ]] && ! [[ -w "/home" ]] && ! have_sudo_access then abort "$( cat </dev/null then abort "$( cat </dev/null || return # we do it in four steps to avoid merge errors when reinstalling execute "${USABLE_GIT}" "-c" "init.defaultBranch=master" "init" "--quiet" # "git remote add" will fail if the remote is defined in the global config execute "${USABLE_GIT}" "config" "remote.origin.url" "${HOMEBREW_BREW_GIT_REMOTE}" execute "${USABLE_GIT}" "config" "remote.origin.fetch" "+refs/heads/*:refs/remotes/origin/*" # ensure we don't munge line endings on checkout execute "${USABLE_GIT}" "config" "--bool" "core.autocrlf" "false" # make sure symlinks are saved as-is execute "${USABLE_GIT}" "config" "--bool" "core.symlinks" "true" execute "${USABLE_GIT}" "fetch" "--force" "origin" execute "${USABLE_GIT}" "fetch" "--force" "--tags" "origin" execute "${USABLE_GIT}" "remote" "set-head" "origin" "--auto" >/dev/null LATEST_GIT_TAG="$("${USABLE_GIT}" tag --list --sort="-version:refname" | head -n1)" if [[ -z "${LATEST_GIT_TAG}" ]] then abort "Failed to query latest Homebrew/brew Git tag." fi execute "${USABLE_GIT}" "checkout" "--force" "-B" "stable" "${LATEST_GIT_TAG}" if [[ "${HOMEBREW_REPOSITORY}" != "${HOMEBREW_PREFIX}" ]] then if [[ "${HOMEBREW_REPOSITORY}" == "${HOMEBREW_PREFIX}/Homebrew" ]] then execute "ln" "-sf" "../Homebrew/bin/brew" "${HOMEBREW_PREFIX}/bin/brew" else abort "The Homebrew/brew repository should be placed in the Homebrew prefix directory." fi fi if [[ -n "${HOMEBREW_NO_INSTALL_FROM_API-}" && ! -d "${HOMEBREW_CORE}" ]] then # Always use single-quoted strings with `exp` expressions # shellcheck disable=SC2016 ohai 'Tapping homebrew/core because `$HOMEBREW_NO_INSTALL_FROM_API` is set.' ( execute "${MKDIR[@]}" "${HOMEBREW_CORE}" cd "${HOMEBREW_CORE}" >/dev/null || return execute "${USABLE_GIT}" "-c" "init.defaultBranch=master" "init" "--quiet" execute "${USABLE_GIT}" "config" "remote.origin.url" "${HOMEBREW_CORE_GIT_REMOTE}" execute "${USABLE_GIT}" "config" "remote.origin.fetch" "+refs/heads/*:refs/remotes/origin/*" execute "${USABLE_GIT}" "config" "--bool" "core.autocrlf" "false" execute "${USABLE_GIT}" "config" "--bool" "core.symlinks" "true" execute "${USABLE_GIT}" "fetch" "--force" "origin" "refs/heads/master:refs/remotes/origin/master" execute "${USABLE_GIT}" "remote" "set-head" "origin" "--auto" >/dev/null execute "${USABLE_GIT}" "reset" "--hard" "origin/master" cd "${HOMEBREW_REPOSITORY}" >/dev/null || return ) || exit 1 fi execute "${HOMEBREW_PREFIX}/bin/brew" "update" "--force" "--quiet" ) || exit 1 if [[ ":${PATH}:" != *":${HOMEBREW_PREFIX}/bin:"* ]] then warn "${HOMEBREW_PREFIX}/bin is not in your PATH. Instructions on how to configure your shell for Homebrew can be found in the 'Next steps' section below." fi ohai "Installation successful!" echo ring_bell # Use an extra newline and bold to avoid this being missed. ohai "Homebrew has enabled anonymous aggregate formulae and cask analytics." echo "$( cat </dev/null || return execute "${USABLE_GIT}" "config" "--replace-all" "homebrew.analyticsmessage" "true" execute "${USABLE_GIT}" "config" "--replace-all" "homebrew.caskanalyticsmessage" "true" ) || exit 1 ohai "Next steps:" case "${SHELL}" in */bash*) if [[ -n "${HOMEBREW_ON_LINUX-}" ]] then shell_rcfile="${HOME}/.bashrc" else shell_rcfile="${HOME}/.bash_profile" fi ;; */zsh*) if [[ -n "${HOMEBREW_ON_LINUX-}" ]] then shell_rcfile="${ZDOTDIR:-"${HOME}"}/.zshrc" else shell_rcfile="${ZDOTDIR:-"${HOME}"}/.zprofile" fi ;; */fish*) shell_rcfile="${HOME}/.config/fish/config.fish" ;; *) shell_rcfile="${ENV:-"${HOME}/.profile"}" ;; esac if grep -qs "eval \"\$(${HOMEBREW_PREFIX}/bin/brew shellenv)\"" "${shell_rcfile}" then if ! [[ -x "$(command -v brew)" ]] then cat <> ${shell_rcfile} eval "\$(${HOMEBREW_PREFIX}/bin/brew shellenv)" EOS fi if [[ -n "${non_default_repos}" ]] then plural="" if [[ "${#additional_shellenv_commands[@]}" -gt 1 ]] then plural="s" fi printf -- "- Run these commands in your terminal to add the non-default Git remote%s for %s:\n" "${plural}" "${non_default_repos}" printf " echo '# Set PATH, MANPATH, etc., for Homebrew.' >> %s\n" "${shell_rcfile}" printf " echo '%s' >> ${shell_rcfile}\n" "${additional_shellenv_commands[@]}" printf " %s\n" "${additional_shellenv_commands[@]}" fi if [[ -n "${HOMEBREW_ON_LINUX-}" ]] then echo "- Install Homebrew's dependencies if you have sudo access:" if [[ -x "$(command -v apt-get)" ]] then echo " sudo apt-get install build-essential" elif [[ -x "$(command -v yum)" ]] then echo " sudo yum groupinstall 'Development Tools'" elif [[ -x "$(command -v pacman)" ]] then echo " sudo pacman -S base-devel" elif [[ -x "$(command -v apk)" ]] then echo " sudo apk add build-base" fi cat <