Files
script/homebrew/README.md
T
orion ff54bffc53 ♻️ refactor(core): 优化 Homebrew 智能升级管理脚本
* 优化 Shell 环境设置,采用 set -euo pipefail 提升脚本鲁棒性
* 改进路径处理逻辑,动态按需向 PATH 注入 Homebrew 常用路径
* 增强失败隔离机制,实现 Cask 批量失败后仅对过期项精准重试
* 优化临时文件管理,改用临时目录并在退出时自动清理相关资源
* 完善网络容错能力,在 bootstrap 阶段增加 curl 重试与超时限制
* 更新文档说明,同步最新的升级策略、并发控制及错误处理逻辑
* 引入终端动态宽度支持,优化非交互式环境下的输出展示效果
2026-07-17 00:30:07 +08:00

10 KiB
Raw Blame History

Homebrew Upgrade Manager

brew-upgrade-manager.sh 是一个 macOS Homebrew 升级脚本,用于按固定流程更新 Homebrew、检查环境、升级 Formula 和 Cask,并清理旧版本与缓存。

仓库中同时提供 brew-upgrade-manager-bootstrap.sh。它是启动器:先从远端下载最新版 brew-upgrade-manager.sh 到临时文件,准备 sudo 凭据,执行后自动删除临时文件。适合在本机配置成 brewup 命令长期使用。

文件说明

文件 作用
brew-upgrade-manager.sh 真正执行 Homebrew 升级流程的主脚本
brew-upgrade-manager-bootstrap.sh 远程启动器,下载主脚本、执行、清理临时文件

功能

  • 执行 brew update -v 更新 Homebrew 仓库。
  • 执行 brew doctor 做健康检查;发现问题时给出警告,但不中断后续流程。
  • 批量升级 Formula 和 Cask;批量命令失败后,仅对仍然过期的项目逐个重试。
  • Formula 与 Cask 阶段相互隔离,一个包失败不会阻止另一类包继续升级。
  • 默认使用 brew upgrade --cask --greedy,不强制覆盖已有 App;需要时可显式开启 --force
  • 网络、下载、HTTP 429/5xx 等瞬时错误会自动退避重试;权限、root、证书、checksum 和磁盘空间错误不会盲目重试。
  • 同一用户只能运行一个主脚本实例,避免并发升级争用 Homebrew 锁和 /Applications
  • 升级全部成功后执行常规 brew cleanup;存在失败时默认保留缓存,方便下次恢复。
  • 支持 --width、环境变量宽度和终端动态宽度。
  • 启动器支持通过 macOS Keychain 保存并读取 sudo 密码,用于 sudo -A -v 预刷新 sudo 凭据。

依赖

  • macOS
  • Homebrew
  • Bash
  • curl
  • macOS Keychain 工具 /usr/bin/security,仅启动器需要

可先检查:

brew --version
curl --version

推荐用法:配置 brewup

把下面函数加入 ~/.zshrc

brewup() {
  curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 15 \
    https://git.orionc.me/orion/script/raw/branch/main/homebrew/brew-upgrade-manager-bootstrap.sh \
    | bash -s -- "$@"
}

重新加载 shell 配置:

source ~/.zshrc

之后直接运行:

brewup

传递参数时也可以正常转发给主脚本:

brewup --width 160

如果更偏好 alias,也可以使用:

alias brewup='curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 15 https://git.orionc.me/orion/script/raw/branch/main/homebrew/brew-upgrade-manager-bootstrap.sh | bash -s --'

函数版对参数转发更直观,推荐优先使用函数。

本地运行主脚本

如果已经 clone 了本仓库,也可以直接运行主脚本:

cd homebrew
chmod +x brew-upgrade-manager.sh
./brew-upgrade-manager.sh

指定固定终端宽度:

./brew-upgrade-manager.sh --width 130
./brew-upgrade-manager.sh --width=130

也可以通过环境变量指定:

HB_TERMINAL_WIDTH=130 ./brew-upgrade-manager.sh

优先级为:命令行 --width 高于 HB_TERMINAL_WIDTH。两者都不设置时,脚本会读取当前终端宽度;无法读取时默认使用 130

启动器行为

brew-upgrade-manager-bootstrap.sh 会执行以下操作:

  1. 创建权限隔离的临时目录。
  2. 使用连接超时、低速超时和 curl 重试下载远端主脚本。
  3. 检查下载结果非空,可选校验 SHA256,并执行 bash -n 语法检查。
  4. 生成临时 SUDO_ASKPASS 脚本。
  5. 从 macOS Keychain 读取 sudo 密码;首次使用时提示输入一次并保存到 Keychain。
  6. 执行 sudo -A -v 刷新 sudo 凭据。
  7. 执行主脚本并转发参数。
  8. 正常退出、Ctrl-C 或 TERM 时删除临时目录中的文件。

默认 Keychain service 名称为:

brewup-sudo-password

如需删除已保存的 sudo 密码:

security delete-generic-password -a "$USER" -s brewup-sudo-password

如需使用自定义 Keychain service

BREWUP_KEYCHAIN_SERVICE=my-brewup-password brewup

SHA256 校验

启动器支持通过 BREWUP_SHA256 校验下载到的主脚本。先计算远端脚本当前哈希:

curl -fsSL https://git.orionc.me/orion/script/raw/branch/main/homebrew/brew-upgrade-manager.sh | shasum -a 256

运行时指定:

BREWUP_SHA256=<sha256> brewup

如果哈希不匹配,启动器会停止执行。

调试

查看启动器下载到的主脚本首行:

BREWUP_DEBUG=1 brewup

重试设置

主脚本默认对可识别的瞬时错误最多尝试 3 次,等待时间从 5 秒开始并按倍数增加:

HB_RETRY_ATTEMPTS=4 HB_RETRY_DELAY_SECONDS=3 brewup

启动器下载远程主脚本时默认最多重试 3 次,每次间隔 2 秒:

BREWUP_DOWNLOAD_RETRIES=5 BREWUP_DOWNLOAD_RETRY_DELAY_SECONDS=3 brewup

下载连接和低速超时也可以调整:

BREWUP_CONNECT_TIMEOUT_SECONDS=20 BREWUP_LOW_SPEED_TIME_SECONDS=45 brewup

权限不足、以 root 运行 Homebrew、证书校验、checksum、磁盘空间不足等确定性错误不会盲目重复重试。批量升级失败后,脚本仍会对剩余项目逐个执行一次,以隔离真实失败项。

为了兼顾性能,Formula 和 Cask 的批量阶段都只执行一次,只有仍然过期的失败项进入单项重试。单项升级保持串行,因为并行运行多个 Homebrew 写操作会争用 Homebrew 锁和 /Applications。用于错误分类的临时日志会在命令结束或收到信号后删除。

Cask 与清理策略

默认不使用 --force。确实需要覆盖已有 Cask 文件时:

HB_CASK_FORCE=1 brewup

升级成功后默认执行常规 brew cleanup。可指定缓存保留天数,或明确清空全部缓存:

HB_CLEANUP_DAYS=30 brewup
HB_CLEANUP_ALL=1 brewup
HB_SKIP_CLEANUP=1 brewup

存在升级失败时默认跳过 cleanup,以保留下载缓存。仍希望清理时:

HB_CLEANUP_ON_FAILURE=1 brewup

如果 brew doctor 明显影响执行时间,也可以跳过:

HB_SKIP_DOCTOR=1 brewup

执行流程

主脚本执行顺序:

  1. brew update -v
  2. brew doctor
  3. brew upgrade --formula;失败时仅重试仍然过期的 Formula
  4. brew upgrade --cask --greedy;失败时仅重试仍然过期的 Cask
  5. 全部成功时执行 brew cleanup,否则默认保留缓存
  6. 汇总失败的 Formula/Cask 并返回最终状态码

常见问题

首次运行为什么要输入 sudo 密码?

启动器会把 sudo 密码保存到当前用户的 macOS Keychain,后续通过临时 SUDO_ASKPASS 脚本读取,用于刷新 sudo 凭据。密码不会写入仓库,也不会写入主脚本。

Keychain 中的 sudo 密码不可用

通常是系统密码已变更,或 Keychain 条目内容不再正确。删除后重新运行即可:

security delete-generic-password -a "$USER" -s brewup-sudo-password
brewup

表格或输出宽度异常

指定固定宽度:

brewup --width 130

或:

HB_TERMINAL_WIDTH=130 brewup

Error: can't modify frozen Array

这是 buo/cask-upgrade tap 与新版 Homebrew 的兼容问题。先移除该 tap:

brew untap buo/cask-upgrade

GUI 应用升级现在使用 Homebrew 官方的 brew upgrade --cask --greedy,脚本不再需要 buo/cask-upgrade

brew doctor 提示 warning

brew doctor 的 warning 不一定代表脚本失败。脚本会继续执行,并打印:

Warning: 'brew doctor' detected issues. Manual review and resolution are recommended.

常见 warning 处理方式:

  • Some installed casks are deprecated or disabled:说明某些 Cask 已废弃或被禁用,例如 ayugram。可以自行寻找替代应用,或不再需要时卸载:

    brew uninstall --cask ayugram
    
  • Homebrew's "sbin" was not found in your PATH:说明 shell 的 PATH 缺少 Homebrew 的 sbin 目录。Apple Silicon Mac 通常可加入:

    echo 'export PATH="/opt/homebrew/sbin:$PATH"' >> ~/.zshrc
    source ~/.zshrc
    

    Intel Mac 或 /usr/local 安装的 Homebrew 可加入:

    echo 'export PATH="/usr/local/sbin:$PATH"' >> ~/.zshrc
    source ~/.zshrc
    

Cask 下载曾经报错但最终升级完成

brew upgrade --cask --greedy 有时会在下载阶段出现 curl: (18) Transferred a partial file 之类的瞬时错误,随后 Homebrew 又完成安装。此时批量命令仍可能返回非零退出码。

脚本会在批量 Cask 升级命令失败后执行:

brew outdated --cask --greedy

如果没有剩余过期 Cask,脚本会把前一次错误视为已恢复。如果仍有过期 Cask,脚本会逐个重试;网络类错误按配置退避重试,其他错误执行一次。最终仍有过期项时,脚本返回失败状态并默认保留缓存。

Cask 报 Running Homebrew as root

某些 macOS/Homebrew 组合在覆盖现有 App、复制扩展属性时,会从 Cask 内部通过 sudo 调用 brew ruby,随后被 Homebrew 自身的 root 安全检查拒绝。这不代表整个 brewup 是通过 sudo brew 启动的。

新版脚本默认不再使用 --force,并在批量升级失败后对仍然过期的 Cask 单独重试。例如:

brew upgrade --cask --greedy visual-studio-code

如果单独重试仍失败,请到“系统设置 → 隐私与安全性 → App 管理”中允许当前终端管理应用,然后再次运行。不要使用 sudo brew upgrade

注意事项

  • 脚本启用了 set -euo pipefail,但 Formula/Cask 升级阶段会捕获错误、继续隔离其他失败项,并在最后统一返回状态。
  • brew upgrade --cask --greedy 可能退出正在运行的 GUI 应用,建议先保存重要工作。
  • 不建议日常启用 HB_CASK_FORCE=1;它会允许 Homebrew 覆盖已有 Cask 文件。
  • 远程启动器属于“下载后执行”模式,只应从可信仓库使用。
  • 在公司设备或受管 macOS 上运行前,先确认 Homebrew、Cask、Keychain 和 sudo 策略允许自动升级。