diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3bf5c33..e0a3e45 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,6 +53,7 @@ jobs: needs: [check] runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: os: [macos-14, ubuntu-latest] timeout-minutes: 100 @@ -78,12 +79,64 @@ jobs: run: | sudo rm -rf /usr/local/Caskroom /usr/local/Homebrew /usr/local/bin/brew \ /usr/local/.??* /Applications/Xcode.app /Library/Developer/CommandLineTools - - name: Run bootstrap.sh + - name: Enable passwordless sudo commands on macOS runners + if: runner.os == 'macOS' + run: | + # mitigate https://github.com/actions/runner-images/issues/10484 + sudo sed -i '' 's/%admin ALL = (ALL) ALL/%admin ALL = (ALL) NOPASSWD: ALL/g' /etc/sudoers + sudo -v + - name: Create a non-admin user account + run: | + username=standard-user + if ${{ runner.os == 'Linux' }}; then + sudo adduser --disabled-password --gecos "" "$username" + home_prefix=/home + elif ${{ runner.os == 'macOS' }}; then + sudo sysadminctl -addUser "$username" + sudo passwd -u "$username" + sudo dseditgroup -o edit -d "$username" -t user admin + home_prefix=/Users + sudo dscl . -create "$home_prefix/$username" NFSHomeDirectory "$home_prefix/$username" + fi + echo "NON_ADMIN_USER=$username" >>"$GITHUB_ENV" + echo "NON_ADMIN_USER_HOME=$home_prefix/$username" >>"$GITHUB_ENV" + - name: Set bootstrap script URL run: | - bootstrap_script_url="https://raw.githubusercontent.com/$STRAP_GITHUB_USER/dotfiles/$STRAP_DOTFILES_BRANCH/bootstrap.sh" - /usr/bin/env bash -c "$(curl -fsSL $bootstrap_script_url)" + STRAP_SCRIPT_URL="https://raw.githubusercontent.com/$STRAP_GITHUB_USER/dotfiles/$STRAP_DOTFILES_BRANCH/bootstrap.sh" + echo "STRAP_SCRIPT_URL=$STRAP_SCRIPT_URL" + echo "STRAP_SCRIPT_URL=$STRAP_SCRIPT_URL" >>"$GITHUB_ENV" + - name: > + Run bootstrap.sh with a non-admin non-sudo user without Homebrew installed + (Homebrew installation requires sudo) + id: bootstrap-non-admin-non-sudo + run: | + sudo \ + --preserve-env=STRAP_CI,STRAP_DEBUG,STRAP_DOTFILES_BRANCH,STRAP_GIT_EMAIL,STRAP_GIT_NAME,STRAP_GITHUB_USER,STRAP_SCRIPT_URL \ + -u "$NON_ADMIN_USER" bash -c '/usr/bin/env bash -c "$(curl -fsSL ${{ env.STRAP_SCRIPT_URL }})"' + working-directory: ${{ env.NON_ADMIN_USER_HOME }} + - name: Update non-admin user account with sudo permissions + run: | + SUDOERS_FILE="/etc/sudoers.d/$NON_ADMIN_USER" + echo "$NON_ADMIN_USER ALL=(ALL) NOPASSWD:ALL" | sudo tee "$SUDOERS_FILE" + sudo chmod 0440 "$SUDOERS_FILE" + - name: Run bootstrap.sh with a non-admin sudo user without Homebrew installed + run: | + sudo \ + --preserve-env=STRAP_CI,STRAP_DEBUG,STRAP_DOTFILES_BRANCH,STRAP_GIT_EMAIL,STRAP_GIT_NAME,STRAP_GITHUB_USER,STRAP_SCRIPT_URL \ + -u "$NON_ADMIN_USER" bash -c '/usr/bin/env bash -c "$(curl -fsSL ${{ env.STRAP_SCRIPT_URL }})"' + working-directory: ${{ env.NON_ADMIN_USER_HOME }} + - name: Rerun bootstrap.sh with a non-admin sudo user after Homebrew has been installed + run: | + sudo \ + --preserve-env=STRAP_CI,STRAP_DEBUG,STRAP_DOTFILES_BRANCH,STRAP_GIT_EMAIL,STRAP_GIT_NAME,STRAP_GITHUB_USER,STRAP_SCRIPT_URL \ + -u "$NON_ADMIN_USER" bash -c '/usr/bin/env bash "${{ env.NON_ADMIN_USER_HOME }}/.dotfiles/bootstrap.sh"' + working-directory: ${{ env.NON_ADMIN_USER_HOME }} + - name: Run bootstrap.sh + run: /usr/bin/env bash -c "$(curl -fsSL $STRAP_SCRIPT_URL)" - name: Rerun bootstrap.sh to test idempotence run: bash "$HOME/.dotfiles/bootstrap.sh" + - name: Check Homebrew formulae + run: brew list | grep -qE "\b(bash|grep|sed)\b" - name: Check Homebrew configuration run: brew config - name: Check for potential problems with brew doctor diff --git a/README.md b/README.md index 719ef7c..c276cb7 100644 --- a/README.md +++ b/README.md @@ -64,9 +64,16 @@ The following environment variables can be used to configure _bootstrap.sh_, and - `STRAP_DOTFILES_URL`: URL from which the dotfiles repo will be cloned. Defaults to `https://github.com/$STRAP_GITHUB_USER/dotfiles`, but any [Git-compatible URL](https://www.git-scm.com/docs/git-clone#_git_urls) can be used, so long as it is accessible at the time the script runs. - `STRAP_DOTFILES_BRANCH`: Git branch to check out after cloning dotfiles repo. Defaults to `main`. -_bootstrap.sh_ will set up macOS and Homebrew, run scripts in the _scripts/_ directory, and install Homebrew packages and casks from the _[Brewfile](Brewfile)_. +There are some additional variables for advanced usage. Consult the _[bootstrap.sh](bootstrap.sh)_ script to see all supported variables. -A Brewfile is a list of [Homebrew](https://brew.sh/) packages and casks (applications) that can be installed in a batch by [Homebrew Bundle](https://github.com/Homebrew/homebrew-bundle). The Brewfile can even be used to install Mac App Store apps with the `mas` CLI. Note that you must sign in to the App Store ahead of time for `mas` to work. +_bootstrap.sh_ will set up macOS and Homebrew, run scripts in the _scripts/_ directory, and install Homebrew packages and casks from the _[Brewfile](Brewfile)_. A Brewfile is a list of [Homebrew](https://brew.sh/) packages and casks (applications) that can be installed in a batch by [Homebrew Bundle](https://github.com/Homebrew/homebrew-bundle). The Brewfile can even be used to install Mac App Store apps with the `mas` CLI. Note that you must sign in to the App Store ahead of time for `mas` to work. + +The following list is a brief summary of permissions related to _bootstrap.sh_. + +- Initial setup of Homebrew itself does not require an admin user account, but does require `sudo`. See the [Homebrew installation docs](https://docs.brew.sh/Installation), [Homebrew/install#312](https://github.com/Homebrew/install/issues/312), and [Homebrew/install#315](https://github.com/Homebrew/install/pull/315/files). +- [After Homebrew setup, use of `sudo` with `brew` commands is discouraged](https://docs.brew.sh/FAQ#why-does-homebrew-say-sudo-is-bad). +- After Homebrew setup, commands such as `brew bundle install --global` should be run from the same user account used for setup. Attempts to run `brew` commands from another user account will result in errors, because directories that need to be updated are owned by the setup account. If access to the setup account is not routinely available, an alternative approach could be to change ownership of Homebrew directories to a group that includes the user account used for Homebrew setup as well as other users that need to run Homebrew commands. +- _bootstrap.sh_ can run with limited functionality on non-admin and non-`sudo` user accounts. A plausible use case could exist in which an admin runs `bootstrap.sh` to configure the system initially, then a non-admin runs `bootstrap.sh` to configure their own account. In this use case, the non-admin user should not need admin or `sudo` privileges, because all the pertinent setup (FileVault disk encryption, XCode developer tools, Homebrew, etc) is already complete. Users with more complex needs for multi-environment dotfiles management might consider a tool like [`chezmoi`](https://www.chezmoi.io/). diff --git a/bootstrap.sh b/bootstrap.sh index c1d1e59..89e9e38 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -27,6 +27,8 @@ Linux) esac [[ -z $HOMEBREW_PREFIX ]] && HOMEBREW_PREFIX="$DEFAULT_HOMEBREW_PREFIX" +STRAP_ADMIN=${STRAP_ADMIN:-0} +if groups | grep -qE "\b(admin)\b"; then STRAP_ADMIN=1; else STRAP_ADMIN=0; fi STRAP_CI=${STRAP_CI:=0} STRAP_DEBUG=${STRAP_DEBUG:-0} [[ $1 = "--debug" || -o xtrace ]] && STRAP_DEBUG=1 @@ -40,6 +42,7 @@ DEFAULT_DOTFILES_URL="https://github.com/$STRAP_GITHUB_USER/dotfiles" STRAP_DOTFILES_URL=${STRAP_DOTFILES_URL:="$DEFAULT_DOTFILES_URL"} STRAP_DOTFILES_BRANCH=${STRAP_DOTFILES_BRANCH:="main"} STRAP_SUCCESS="" +STRAP_SUDO=0 sudo_askpass() { if [ -n "$SUDO_ASKPASS" ]; then @@ -51,8 +54,10 @@ sudo_askpass() { cleanup() { set +e - sudo_askpass rm -rf "$CLT_PLACEHOLDER" "$SUDO_ASKPASS" "$SUDO_ASKPASS_DIR" - sudo --reset-timestamp + if [ -n "$SUDO_ASKPASS" ]; then + sudo_askpass rm -rf "$CLT_PLACEHOLDER" "$SUDO_ASKPASS" "$SUDO_ASKPASS_DIR" + sudo --reset-timestamp + fi if [ -z "$STRAP_SUCCESS" ]; then if [ -n "$STRAP_STEP" ]; then echo "!!! $STRAP_STEP FAILED" >&2 @@ -73,18 +78,41 @@ else Q="$STRAP_QUIET_FLAG" fi -# Prompt for sudo password and initialize (or reinitialize) sudo -sudo --reset-timestamp - clear_debug() { set +x } + reset_debug() { if [ "$STRAP_DEBUG" -gt 0 ]; then set -x fi } +abort() { + STRAP_STEP="" + echo "!!! $*" >&2 + exit 1 +} + +escape() { + printf '%s' "${1//\'/\'}" +} + +log_no_sudo() { + STRAP_STEP="$*" + echo "--> $*" +} + +logk() { + STRAP_STEP="" + echo "OK" +} + +logn_no_sudo() { + STRAP_STEP="$*" + printf -- "--> %s " "$*" +} + logskip() { STRAP_STEP="" echo "SKIPPED" @@ -92,13 +120,15 @@ logskip() { } sudo_init() { - if [ "$STRAP_INTERACTIVE" -eq 0 ]; then return; fi + if [ "$STRAP_INTERACTIVE" -eq 0 ]; then + sudo -n -l mkdir &>/dev/null && export STRAP_SUDO=1 + return + fi # Check and, if necessary, enable sudo authentication using TouchID. # Don't care about non-alphanumeric filenames when doing a specific match # shellcheck disable=SC2010,SC2086 if ls /usr/lib/pam | grep $Q "pam_tid.so"; then - STRAP_STEP="Configuring sudo authentication using TouchID:" - echo "--> $STRAP_STEP" + logn_no_sudo "Configuring sudo authentication using TouchID:" if [[ -f /etc/pam.d/sudo_local || -f /etc/pam.d/sudo_local.template ]]; then # New in macOS Sonoma, survives updates. PAM_FILE="/etc/pam.d/sudo_local" @@ -147,8 +177,9 @@ sudo_init() { chmod 700 "$SUDO_ASKPASS_DIR" "$SUDO_ASKPASS" bash -c "cat > '$SUDO_ASKPASS'" <<<"$SUDO_PASSWORD_SCRIPT" unset SUDO_PASSWORD_SCRIPT + STRAP_SUDO=1 reset_debug - export SUDO_ASKPASS + export STRAP_SUDO SUDO_ASKPASS fi } @@ -159,40 +190,30 @@ sudo_refresh() { else sudo_init fi + echo "STRAP_SUDO=$STRAP_SUDO" reset_debug } -abort() { - STRAP_STEP="" - echo "!!! $*" >&2 - exit 1 -} log() { STRAP_STEP="$*" sudo_refresh echo "--> $*" } + logn() { STRAP_STEP="$*" sudo_refresh printf -- "--> %s " "$*" } -logk() { - STRAP_STEP="" - echo "OK" -} -escape() { - printf '%s' "${1//\'/\'}" -} # Given a list of scripts in the dotfiles repo, run the first one that exists run_dotfile_scripts() { - if [ -d ~/.dotfiles ]; then + if [ -d "$HOME/.dotfiles" ]; then ( - cd ~/.dotfiles + cd "$HOME/.dotfiles" for i in "$@"; do if [ -f "$i" ] && [ -x "$i" ]; then - log "Running dotfiles $i:" + log_no_sudo "Running dotfiles script $i:" if [ "$STRAP_DEBUG" -eq 0 ]; then "$i" 2>/dev/null else @@ -208,7 +229,7 @@ run_dotfile_scripts() { [ "$USER" = "root" ] && abort "Run bootstrap.sh as yourself, not root." # shellcheck disable=SC2086 -if [ "$MACOS" -gt 0 ]; then +if [ "$MACOS" -gt 0 ] && [ "$STRAP_ADMIN" -gt 0 ]; then [ "$STRAP_CI" -eq 0 ] && caffeinate -s -w $$ & groups | grep $Q -E "\b(admin)\b" || abort "Add $USER to admin." logn "Configuring security settings:" @@ -235,23 +256,21 @@ if [ "$MACOS" -gt 0 ]; then fi # Check for and enable full-disk encryption -logn "Checking full-disk encryption status:" -VAULT_MSG="FileVault is (On|Off, but will be enabled after the next restart)." -# shellcheck disable=SC2086 -if fdesetup status | grep $Q -E "$VAULT_MSG"; then - logk -elif [ "$MACOS" -eq 0 ] || [ "$STRAP_CI" -gt 0 ]; then - echo - logn "Skipping full-disk encryption." -elif [ "$STRAP_INTERACTIVE" -gt 0 ]; then - echo - log "Enabling full-disk encryption on next reboot:" - sudo_askpass fdesetup enable -user "$USER" | - tee ~/Desktop/"FileVault Recovery Key.txt" - logk -else - echo - abort "Run 'sudo fdesetup enable -user \"$USER\"' for full-disk encryption." +if [ "$MACOS" -eq 0 ] || [ "$STRAP_ADMIN" -eq 0 ] || [ "$STRAP_CI" -gt 0 ]; then + logskip "Skipping full-disk encryption." +elif [ "$MACOS" -gt 0 ] && [ "$STRAP_ADMIN" -gt 0 ]; then + logn "Checking full-disk encryption status:" + VAULT_MSG="FileVault is (On|Off, but will be enabled after the next restart)." + # shellcheck disable=SC2086 + if fdesetup status | grep $Q -E "$VAULT_MSG"; then + logk + elif sudo_askpass fdesetup enable -user "$USER" | + tee ~/Desktop/"FileVault Recovery Key.txt"; then + log "Full-disk encryption will be enabled after next reboot:" + logk + else + abort "Run 'sudo fdesetup enable -user \"$USER\"' for full-disk encryption." + fi fi # Set up Xcode Command Line Tools @@ -296,23 +315,60 @@ check_xcode_license() { fi } -if [ "$MACOS" -gt 0 ]; then +check_software_updates() { + logn "Checking for software updates:" + # shellcheck disable=SC2086 + if softwareupdate -l 2>&1 | grep $Q "No new software available."; then + logk + else + if [ "$MACOS" -gt 0 ] && [ "$STRAP_CI" -eq 0 ]; then + echo + log "Installing software updates:" + sudo_askpass softwareupdate --install --all + check_xcode_license + else + logskip "Skipping software updates." + fi + logk + fi +} + +if [ "$MACOS" -gt 0 ] && [ "$STRAP_ADMIN" -gt 0 ]; then install_xcode_clt check_xcode_license + check_software_updates else - log "Not macOS. Xcode CLT install and license check skipped." + logskip "Xcode Command-Line Tools install and license check skipped." fi configure_git() { - logn "Configuring Git:" + logn_no_sudo "Configuring Git:" + # These calls to `git config` are needed for CI use cases in which certain + # aspects of the `.gitconfig` cannot be used (like signing commits with SSH). + # See commit c0542397e817fc1bd711984619eb73a6fdc937b2. + # Permission denied errors may occur when Git attempts to read + # [`$XDG_CONFIG_HOME/git/attributes`](https://git-scm.com/docs/gitattributes) + # or [`$XDG_CONFIG_HOME/git/ignore`](https://git-scm.com/docs/gitignore). + # These files may be located in `/home/runner/.config` on GitHub Actions + # runners and inaccessible if a non-root user is running this script. if [ "$STRAP_CI" -gt 0 ]; then + export XDG_CONFIG_HOME="$HOME/.config" + mkdir -p "$XDG_CONFIG_HOME" && chmod -R 775 "$XDG_CONFIG_HOME" git config --global commit.gpgsign false git config --global gpg.format openpgp + if ! git config --global core.attributesfile >/dev/null; then + touch "$HOME/.gitattributes" + git config --global core.attributesfile "$HOME/.gitattributes" + fi + if ! git config --global core.excludesfile >/dev/null; then + touch "$HOME/.gitignore_global" + git config --global core.excludesfile "$HOME/.gitignore_global" + fi fi - if [ -n "$STRAP_GIT_NAME" ] && ! git config user.name >/dev/null; then + if [ -n "$STRAP_GIT_NAME" ] && ! git config --global user.name >/dev/null; then git config --global user.name "$STRAP_GIT_NAME" fi - if [ -n "$STRAP_GIT_EMAIL" ] && ! git config user.email >/dev/null; then + if [ -n "$STRAP_GIT_EMAIL" ] && ! git config --global user.email >/dev/null; then git config --global user.email "$STRAP_GIT_EMAIL" fi if [ -n "$STRAP_GITHUB_USER" ] && @@ -334,7 +390,7 @@ configure_git() { "$PROTOCOL" "$STRAP_GITHUB_USER" "$STRAP_GITHUB_TOKEN" | git credential approve else - log "Skipping Git credential setup." + logskip "Skipping Git credential setup." fi logk fi @@ -343,49 +399,30 @@ configure_git() { # The first call to `configure_git` is needed for cloning the dotfiles repo. configure_git -# Check for and install any remaining software updates -logn "Checking for software updates:" -# shellcheck disable=SC2086 -if softwareupdate -l 2>&1 | grep $Q "No new software available."; then - logk -else - if [ "$MACOS" -gt 0 ] && [ "$STRAP_CI" -eq 0 ]; then - echo - log "Installing software updates:" - sudo_askpass softwareupdate --install --all - check_xcode_license - else - log "Skipping software updates." - fi - logk -fi - # Set up dotfiles # shellcheck disable=SC2086 if [ ! -d "$HOME/.dotfiles" ]; then if [ -z "$STRAP_DOTFILES_URL" ] || [ -z "$STRAP_DOTFILES_BRANCH" ]; then abort "Please set STRAP_DOTFILES_URL and STRAP_DOTFILES_BRANCH." fi - log "Cloning $STRAP_DOTFILES_URL to ~/.dotfiles." - git clone $Q "$STRAP_DOTFILES_URL" ~/.dotfiles + log_no_sudo "Cloning $STRAP_DOTFILES_URL to $HOME/.dotfiles." + git clone $Q "$STRAP_DOTFILES_URL" "$HOME/.dotfiles" fi strap_dotfiles_branch_name="${STRAP_DOTFILES_BRANCH##*/}" -log "Checking out $strap_dotfiles_branch_name in ~/.dotfiles." +log_no_sudo "Checking out $strap_dotfiles_branch_name in $HOME/.dotfiles." # shellcheck disable=SC2086 ( - cd ~/.dotfiles + cd "$HOME/.dotfiles" git stash git fetch $Q git checkout "$strap_dotfiles_branch_name" git pull $Q --rebase --autostash ) run_dotfile_scripts scripts/symlink.sh -logk - -# The second call to `configure_git` is needed for CI use cases when certain -# aspects of the `.gitconfig` cannot be used (like signing commits with SSH). -# See commit c0542397e817fc1bd711984619eb73a6fdc937b2. +# The second call to `configure_git` is needed for CI use cases in which some +# aspects of the `.gitconfig` cannot be used after cloning the dotfiles repo. configure_git +logk # shellcheck disable=SC2086 install_homebrew() { @@ -393,7 +430,7 @@ install_homebrew() { HOMEBREW_PREFIX="$(brew --prefix 2>/dev/null || true)" [ -n "$HOMEBREW_PREFIX" ] || HOMEBREW_PREFIX="$DEFAULT_HOMEBREW_PREFIX" [ -d "$HOMEBREW_PREFIX" ] || sudo_askpass mkdir -p "$HOMEBREW_PREFIX" - sudo_askpass chown "root:wheel" "$HOMEBREW_PREFIX" 2>/dev/null || true + sudo_askpass chown -R "root:wheel" "$HOMEBREW_PREFIX" 2>/dev/null || true ( cd "$HOMEBREW_PREFIX" sudo_askpass mkdir -p \ @@ -404,7 +441,7 @@ install_homebrew() { HOMEBREW_REPOSITORY="$(brew --repository 2>/dev/null || true)" [ -n "$HOMEBREW_REPOSITORY" ] || HOMEBREW_REPOSITORY="$HOMEBREW_PREFIX/Homebrew" [ -d "$HOMEBREW_REPOSITORY" ] || sudo_askpass mkdir -p "$HOMEBREW_REPOSITORY" - sudo_askpass chown -R "$USER:admin" "$HOMEBREW_REPOSITORY" + sudo_askpass chown -R "root:wheel" "$HOMEBREW_REPOSITORY" 2>/dev/null || true if [ "$HOMEBREW_PREFIX" != "$HOMEBREW_REPOSITORY" ]; then ln -sf "$HOMEBREW_REPOSITORY/bin/brew" "$HOMEBREW_PREFIX/bin/brew" fi @@ -424,7 +461,7 @@ install_homebrew() { set_up_brew_skips() { local brewfile_path casks ci_skips mas_ids mas_prefix - log "Setting up Homebrew Bundle formula installs to skip." + log_no_sudo "Setting up Homebrew Bundle formula installs to skip." ci_skips="awscli black jupyterlab mkvtoolnix zsh-completions" [ "$STRAP_CI" -gt 0 ] && HOMEBREW_BUNDLE_BREW_SKIP="$ci_skips" if [ -f "$HOME/.Brewfile" ]; then @@ -434,16 +471,16 @@ set_up_brew_skips() { else abort "No Brewfile found" fi - log "Setting up Homebrew Bundle cask installs to skip." + log_no_sudo "Setting up Homebrew Bundle cask installs to skip." if [ "$MACOS" -gt 0 ] && [ "$brewfile_path" == "$HOME/.Brewfile" ]; then casks="$(brew bundle list --global --cask --quiet | tr '\n' ' ')" elif [ "$MACOS" -gt 0 ] && [ "$brewfile_path" == "Brewfile" ]; then casks="$(brew bundle list --cask --quiet | tr '\n' ' ')" else - log "Cask commands are only supported on macOS." + log_no_sudo "Cask commands are only supported on macOS." fi HOMEBREW_BUNDLE_CASK_SKIP="${casks%% }" - log "Setting up Homebrew Bundle Mac App Store (mas) installs to skip." + log_no_sudo "Setting up Homebrew Bundle Mac App Store (mas) installs to skip." mas_ids="" mas_prefix='*mas*, id: ' while read -r brewfile_line; do @@ -451,9 +488,9 @@ set_up_brew_skips() { [[ $brewfile_line == *$mas_prefix* ]] && mas_ids+="${brewfile_line##$mas_prefix} " done <"$brewfile_path" HOMEBREW_BUNDLE_MAS_SKIP="${mas_ids%% }" - log "HOMEBREW_BUNDLE_BREW_SKIP='$HOMEBREW_BUNDLE_BREW_SKIP'" - log "HOMEBREW_BUNDLE_CASK_SKIP='$HOMEBREW_BUNDLE_CASK_SKIP'" - log "HOMEBREW_BUNDLE_MAS_SKIP='$HOMEBREW_BUNDLE_MAS_SKIP'" + log_no_sudo "HOMEBREW_BUNDLE_BREW_SKIP='$HOMEBREW_BUNDLE_BREW_SKIP'" + log_no_sudo "HOMEBREW_BUNDLE_CASK_SKIP='$HOMEBREW_BUNDLE_CASK_SKIP'" + log_no_sudo "HOMEBREW_BUNDLE_MAS_SKIP='$HOMEBREW_BUNDLE_MAS_SKIP'" export HOMEBREW_BUNDLE_BREW_SKIP="$HOMEBREW_BUNDLE_BREW_SKIP" export HOMEBREW_BUNDLE_CASK_SKIP="$HOMEBREW_BUNDLE_CASK_SKIP" export HOMEBREW_BUNDLE_MAS_SKIP="$HOMEBREW_BUNDLE_MAS_SKIP" @@ -506,17 +543,41 @@ run_brew_installs() { fi } -# Install Homebrew: https://docs.brew.sh/Installation -script_url="https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh" -NONINTERACTIVE=$STRAP_CI \ - /usr/bin/env bash -c "$(curl -fsSL $script_url)" || install_homebrew - -# Set up Homebrew on Linux: https://docs.brew.sh/Homebrew-on-Linux -[ "$LINUX" -gt 0 ] && run_dotfile_scripts scripts/linuxbrew.sh - -run_brew_installs || abort "Homebrew installs were not successful." +# Install Homebrew +# https://docs.brew.sh/Installation +# https://docs.brew.sh/Homebrew-on-Linux +# Homebrew installs require `sudo`, but not necessarily admin +# https://docs.brew.sh/FAQ#why-does-homebrew-say-sudo-is-bad +# https://github.com/Homebrew/install/issues/312 +# https://github.com/Homebrew/install/pull/315/files +if [ "$STRAP_SUDO" -eq 0 ]; then + sudo_init || logskip "Skipping Homebrew installation (requires sudo)." +fi +if [ "$STRAP_SUDO" -gt 0 ]; then + # Prevent "Permission denied" errors on Homebrew directories + log "Updating permissions on Homebrew directories" + sudo_askpass mkdir -p "$HOMEBREW_PREFIX/"{Caskroom,Cellar,Frameworks} + sudo_askpass chmod -R 775 "$HOMEBREW_PREFIX/"{Caskroom,Cellar,Frameworks} + sudo_askpass chown -R "$USER" "$HOMEBREW_PREFIX" 2>/dev/null || true + logk + if [ "$MACOS" -gt 0 ]; then + log "Installing Homebrew on macOS" + script_url="https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh" + NONINTERACTIVE=$STRAP_CI \ + /usr/bin/env bash -c "$(curl -fsSL $script_url)" || install_homebrew + logk + elif [ "$LINUX" -gt 0 ]; then + # https://docs.brew.sh/Homebrew-on-Linux + log "Installing Homebrew on Linux" + run_dotfile_scripts scripts/linuxbrew.sh + logk + else + abort "Unsupported operating system $OS" + fi + run_brew_installs || abort "Homebrew installs were not successful." +fi run_dotfile_scripts scripts/strap-after-setup.sh STRAP_SUCCESS=1 -log "Your system is now bootstrapped!" +log_no_sudo "Your system is now bootstrapped!"