Skip to content

Instantly share code, notes, and snippets.

@dzogrim
Last active July 23, 2025 15:15
Show Gist options
  • Save dzogrim/00e3d5dd9e2b14680204fe4d5d58f8d3 to your computer and use it in GitHub Desktop.
Save dzogrim/00e3d5dd9e2b14680204fe4d5d58f8d3 to your computer and use it in GitHub Desktop.
This script automates Homebrew maintenance on macOS
#!/usr/bin/env bash
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2019–2025 dzogrim <[email protected]>
#
# ------------------------------------------------------------------------------
# brew-update.sh — Homebrew Maintenance Tool for macOS
#
# This script automates Homebrew maintenance on macOS:
# - Updates the package index (brew update)
# - Lists and upgrades outdated packages (brew upgrade)
# - Fixes permissions on "/opt/homebrew/Cellar" if needed
# - Cleans up outdated and unlinked versions (brew cleanup -s)
# - Optionally runs 'brew doctor' (with HELP argument)
#
# Features:
# - macOS-only safeguard
# - Avoids root execution
# - Lock mechanism to prevent concurrent runs
# - Minimal, reliable output
#
# Usage:
# ./brew-update.sh # Run normal update & cleanup
# ./brew-update.sh HELP # Run and also perform 'brew doctor'
#
# Requirements:
# - Homebrew installed
# - 'lockfile' utility available
# Optional:
# - brew_conf_export.sh # To run and also perform a brew bundle in Dropbox
#
# License: MIT
VERSION="0.6 (2025-07-23)"
# Author: dzogrim <[email protected]>
# ------------------------------------------------------------------------------
set -euo pipefail
# ------------------------------------------------------------------------------
# Configuration
# ------------------------------------------------------------------------------
NAME="$(basename "$0")"
LOCK="/tmp/${NAME}.lock"
DOCTOR=0
BREW_BIN="$(command -v brew)"
CELLAR_DIR="$("${BREW_BIN}" --cellar)"
# ------------------------------------------------------------------------------
# Colors
# ------------------------------------------------------------------------------
cRST='\033[0m'
cRed='\033[31;1m'
cGreen='\033[32;1m'
cYellow='\033[33;1m'
# Ensure the script is running on macOS only.
if [[ "$(uname)" != "Darwin" ]]; then
# shellcheck disable=SC2059
printf "\n${cRed}Error:${cRST} This program is intended for macOS only. Exiting.\n"
exit 1
fi
log() { printf " ${cGreen}*${cRST} %s\n" "$@"; }
warn() { printf " ${cYellow}!${cRST} %s\n" "$@" >&2; }
error() { printf " ${cRed}✘ [ERROR]${cRST} %s\n" "$@" >&2; exit 1; }
version() { echo "$NAME v$VERSION"; exit 0; }
# ------------------------------------------------------------------------------
# Lock + Checks
# ------------------------------------------------------------------------------
check_requirements() {
command -v lockfile >/dev/null || error "lockfile is required."
command -v brew >/dev/null || error "Homebrew is not installed."
[[ "$(id -u)" -ne 0 ]] || error "Do not run as root."
}
acquire_lock() {
lockfile -r 0 "$LOCK" || error "Another instance is running."
trap 'rm -f "$LOCK"' EXIT
}
# ------------------------------------------------------------------------------
# Operations
# ------------------------------------------------------------------------------
fix_cellar_permissions() {
if [ ! -w "$CELLAR_DIR" ]; then
warn "Cellar is not writable. Attempting fix..."
sudo chown -R "$(whoami)" "$CELLAR_DIR" || error "Failed to fix permissions."
fi
}
update_brew() {
log "Updating Homebrew..."
"$BREW_BIN" update
}
upgrade_brew() {
log "Listing outdated formulas..."
"$BREW_BIN" outdated || true
log "Upgrading formulas..."
"$BREW_BIN" upgrade
}
cleanup_brew() {
log "Cleaning up old versions..."
fix_cellar_permissions
"$BREW_BIN" cleanup -s
}
doctor_report() {
if [[ "$DOCTOR" -eq 1 ]]; then
log "Running brew doctor..."
"$BREW_BIN" doctor || warn "brew doctor reported issues."
fi
}
ask_backup_config() {
if command -v brew_conf_export.sh >/dev/null; then
echo ""
read -r -t 30 -p "Do you want to backup the configuration with brew_conf_export.sh? [y/N] " choice || choice="n"
case "$choice" in
[yY][eE][sS]|[yY])
log "Launching brew_conf_export.sh..."
brew_conf_export.sh
;;
*)
echo ""
warn "Skipping configuration backup."
;;
esac
else
warn "brew_conf_export.sh not found in PATH — skipping backup."
fi
}
# ------------------------------------------------------------------------------
# Main
# ------------------------------------------------------------------------------
usage() {
echo "Usage: $NAME [APPLY|HELP|-h|-v]"
echo " -h → display usage"
echo " -v → display version"
echo " APPLY → simply runs the program (default)"
echo " HELP → also run 'brew doctor' at the end"
exit 1
}
main() {
[[ "${1:-}" == "-v" ]] && version
[[ "${1:-}" == "-h" || $# -gt 1 ]] && usage
[[ "${1:-}" == "APPLY" ]] && DOCTOR=0
[[ "${1:-}" == "HELP" ]] && DOCTOR=1
check_requirements
acquire_lock
update_brew
upgrade_brew
cleanup_brew
ask_backup_config
doctor_report
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment