Last active
July 23, 2025 15:15
-
-
Save dzogrim/00e3d5dd9e2b14680204fe4d5d58f8d3 to your computer and use it in GitHub Desktop.
This script automates Homebrew maintenance on macOS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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