488a9a2140
Homebrew cask installers call sudo internally for each package that needs admin privileges. Without pre-authentication, macOS prompts for a password on every such package across a long brew bundle run. _ssr_sudo_keepalive_start: Calls 'sudo -v' once (prompts user if not already cached), then spawns a background loop that calls 'sudo -n true' every 60s to prevent the default 5-minute credential cache from expiring mid-run. _ssr_sudo_keepalive_stop: Kills the keepalive loop after brew bundle completes. If sudo is unavailable (non-admin user), a warning is shown and the run continues — individual cask installers that need sudo will prompt as before. Co-authored-by: Cursor <cursoragent@cursor.com>
130 lines
4.6 KiB
Bash
130 lines
4.6 KiB
Bash
#!/usr/bin/env bash
|
|
# lib/brew.sh — Homebrew install + Brewfile dump/restore.
|
|
|
|
ssr::brew::ensure_installed() {
|
|
if command -v brew >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
|
|
# Homebrew's install script requires sudo on macOS to write to /opt/homebrew
|
|
# (Apple Silicon) or /usr/local (Intel). Check before running it so we can
|
|
# give a clear error instead of a cryptic failure.
|
|
if ! sudo -n true 2>/dev/null; then
|
|
ssr::warn "Homebrew is not installed and sudo access is required to install it."
|
|
ssr::warn "Please run the following as an admin user and re-run ssr restore:"
|
|
ssr::warn " /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""
|
|
ssr::die "Cannot install Homebrew without sudo access."
|
|
fi
|
|
|
|
ssr::log "Homebrew not found — installing"
|
|
# NONINTERACTIVE=1 skips the confirmation prompt; sudo is available (checked above).
|
|
NONINTERACTIVE=1 /bin/bash -c \
|
|
"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
|
|
|
# Ensure brew is on PATH for both Apple Silicon and Intel layouts.
|
|
if [[ -x /opt/homebrew/bin/brew ]]; then
|
|
eval "$(/opt/homebrew/bin/brew shellenv)"
|
|
elif [[ -x /usr/local/bin/brew ]]; then
|
|
eval "$(/usr/local/bin/brew shellenv)"
|
|
fi
|
|
command -v brew >/dev/null 2>&1 || ssr::die "brew still not on PATH after install"
|
|
}
|
|
|
|
ssr::brew::update() {
|
|
ssr::log "brew update"
|
|
brew update --quiet
|
|
}
|
|
|
|
ssr::brew::upgrade() {
|
|
[[ "${SSR_BREW_UPGRADE:-true}" == "true" ]] || return 0
|
|
ssr::log "brew upgrade"
|
|
brew upgrade --quiet || ssr::warn "brew upgrade reported errors (continuing)"
|
|
}
|
|
|
|
ssr::brew::cleanup() {
|
|
[[ "${SSR_BREW_CLEANUP:-true}" == "true" ]] || return 0
|
|
ssr::log "brew cleanup"
|
|
brew cleanup --quiet || true
|
|
}
|
|
|
|
# Dumps Brewfile to the cloud backup dir with a timestamped rotation copy.
|
|
ssr::brew::dump() {
|
|
local backup_dir="$1" brewfile="$1/Brewfile"
|
|
ssr::ensure_dir "$backup_dir"
|
|
if [[ -f "$brewfile" ]]; then
|
|
cp -f "$brewfile" "$brewfile.bak"
|
|
fi
|
|
ssr::log "brew bundle dump → $brewfile"
|
|
(
|
|
cd "$backup_dir"
|
|
brew bundle dump --describe --quiet --force --file=Brewfile
|
|
)
|
|
ssr::ok "Brewfile written ($(wc -l < "$brewfile" | tr -d ' ') lines)"
|
|
}
|
|
|
|
# Pre-authenticate sudo once and keep credentials alive in the background.
|
|
# macOS caches sudo credentials for 5 minutes (300s); the keepalive loop
|
|
# refreshes every 60s so Homebrew cask installers never hit a timeout mid-run.
|
|
#
|
|
# Usage:
|
|
# _ssr_sudo_keepalive_start # prompts once, starts background loop
|
|
# … long-running commands …
|
|
# _ssr_sudo_keepalive_stop # kills the loop
|
|
|
|
_SSR_SUDO_KEEPALIVE_PID=""
|
|
|
|
_ssr_sudo_keepalive_start() {
|
|
# Check whether sudo is available at all (may already be cached).
|
|
if ! sudo -v 2>/dev/null; then
|
|
ssr::warn "sudo authentication failed — cask installs may prompt per-package."
|
|
return 0
|
|
fi
|
|
ssr::ok "sudo credentials cached — Homebrew casks will not prompt during install."
|
|
|
|
# Background loop: re-validate every 60s to prevent 5-minute timeout.
|
|
(
|
|
while true; do
|
|
sleep 60
|
|
sudo -n true 2>/dev/null || break
|
|
done
|
|
) &
|
|
_SSR_SUDO_KEEPALIVE_PID=$!
|
|
}
|
|
|
|
_ssr_sudo_keepalive_stop() {
|
|
if [[ -n "$_SSR_SUDO_KEEPALIVE_PID" ]]; then
|
|
kill "$_SSR_SUDO_KEEPALIVE_PID" 2>/dev/null || true
|
|
wait "$_SSR_SUDO_KEEPALIVE_PID" 2>/dev/null || true
|
|
_SSR_SUDO_KEEPALIVE_PID=""
|
|
fi
|
|
}
|
|
|
|
ssr::brew::restore() {
|
|
local brewfile="$1"
|
|
[[ -f "$brewfile" ]] || ssr::die "Brewfile not found: $brewfile"
|
|
ssr::log "brew bundle install ← $brewfile"
|
|
|
|
# Authenticate sudo once up front so Homebrew cask installers don't
|
|
# prompt individually for each package that needs admin privileges.
|
|
_ssr_sudo_keepalive_start
|
|
|
|
# Suppress per-app Spotlight indexing: mas would otherwise run mdimport
|
|
# after every App Store installation. One batch pass runs below instead.
|
|
local rc=0
|
|
MAS_NO_AUTO_INDEX=1 brew bundle install --file="$brewfile" --verbose || rc=$?
|
|
|
|
_ssr_sudo_keepalive_stop
|
|
|
|
# Single batch Spotlight index of /Applications after all installs.
|
|
ssr::log "Indexing /Applications in Spotlight (one-time batch)"
|
|
mdimport /Applications 2>/dev/null || true
|
|
[[ -d "$HOME/Applications" ]] && mdimport "$HOME/Applications" 2>/dev/null || true
|
|
|
|
if [[ $rc -ne 0 ]]; then
|
|
ssr::warn "brew bundle install finished with errors (rc=$rc) — some packages were not installed."
|
|
ssr::warn "Rerun manually: brew bundle install --file=\"$brewfile\""
|
|
else
|
|
ssr::ok "brew bundle install completed successfully."
|
|
fi
|
|
}
|