Files
script/homebrew/README.md
T
orion 3e7063b30a ♻️ refactor(core): 优化 Homebrew 升级管理器的错误重试与隔离机制
* 重构 Cask 升级逻辑,引入失败隔离与针对性重试机制
* 新增通过环境变量自定义重试次数与延迟间隔的功能
* 优化启动器下载流程,集成 curl 自动退避重试策略
* 改进 Cask 失败处理,仅对未成功项进行二次重试并移除强制标志
* 更新文档,详细说明网络瞬时错误处理与新版本升级逻辑
* 增强脚本健壮性,完善针对数值参数的合法性校验机制
2026-07-17 00:10:37 +08:00

285 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 做健康检查;发现问题时给出警告,但不中断后续流程。
- 使用 `brew upgrade --formula` 升级命令行工具。
- 使用 `brew upgrade --cask --greedy --force` 批量升级 GUI 应用。
- Homebrew 更新、Formula 升级和 Cask 升级遇到网络、下载、HTTP 429/5xx 等瞬时错误时,会自动退避重试。
- 批量 Cask 升级只执行一次,以利用 Homebrew 的批量下载能力;失败后只对仍然过期的 Cask 逐个重试,并在重试时去掉 `--force`,避免重复处理已成功的应用。
- 单个 Cask 的失败不会掩盖其他 Cask 的升级结果;最终会再次核对过期列表,仅在仍有失败项时返回非零状态。
- 执行 `brew cleanup --prune=all` 清理旧版本和缓存。
- 支持固定终端宽度,避免非交互环境下输出宽度异常。
- 启动器支持通过 macOS Keychain 保存并读取 sudo 密码,用于 `sudo -A -v` 预刷新 sudo 凭据。
## 依赖
- macOS
- Homebrew
- Bash
- `curl`
- macOS Keychain 工具 `/usr/bin/security`,仅启动器需要
可先检查:
```bash
brew --version
curl --version
```
## 推荐用法:配置 `brewup`
把下面函数加入 `~/.zshrc`
```bash
brewup() {
curl -fsSL https://git.orionc.me/orion/script/raw/branch/main/homebrew/brew-upgrade-manager-bootstrap.sh | bash -s -- "$@"
}
```
重新加载 shell 配置:
```bash
source ~/.zshrc
```
之后直接运行:
```bash
brewup
```
传递参数时也可以正常转发给主脚本:
```bash
brewup --width 160
```
如果更偏好 alias,也可以使用:
```bash
alias brewup='curl -fsSL https://git.orionc.me/orion/script/raw/branch/main/homebrew/brew-upgrade-manager-bootstrap.sh | bash -s --'
```
函数版对参数转发更直观,推荐优先使用函数。
## 本地运行主脚本
如果已经 clone 了本仓库,也可以直接运行主脚本:
```bash
cd homebrew
chmod +x brew-upgrade-manager.sh
./brew-upgrade-manager.sh
```
指定固定终端宽度:
```bash
./brew-upgrade-manager.sh --width 130
./brew-upgrade-manager.sh --width=130
```
也可以通过环境变量指定:
```bash
HB_TERMINAL_WIDTH=130 ./brew-upgrade-manager.sh
```
优先级为:命令行 `--width` 高于 `HB_TERMINAL_WIDTH`。两者都不设置时,脚本会读取当前终端宽度;无法读取时默认使用 `130`
## 启动器行为
`brew-upgrade-manager-bootstrap.sh` 会执行以下操作:
1. 创建临时文件。
2. 生成临时 `SUDO_ASKPASS` 脚本。
3. 从 macOS Keychain 读取 sudo 密码;首次使用时提示输入一次并保存到 Keychain。
4. 执行 `sudo -A -v` 刷新 sudo 凭据。
5. 下载远端 `brew-upgrade-manager.sh`
6. 可选校验 SHA256。
7. 使用 `bash "$TEMP" "$@"` 执行主脚本并转发参数。
8. 退出时删除临时脚本文件。
默认 Keychain service 名称为:
```bash
brewup-sudo-password
```
如需删除已保存的 sudo 密码:
```bash
security delete-generic-password -a "$USER" -s brewup-sudo-password
```
如需使用自定义 Keychain service
```bash
BREWUP_KEYCHAIN_SERVICE=my-brewup-password brewup
```
## SHA256 校验
启动器支持通过 `BREWUP_SHA256` 校验下载到的主脚本。先计算远端脚本当前哈希:
```bash
curl -fsSL https://git.orionc.me/orion/script/raw/branch/main/homebrew/brew-upgrade-manager.sh | shasum -a 256
```
运行时指定:
```bash
BREWUP_SHA256=<sha256> brewup
```
如果哈希不匹配,启动器会停止执行。
## 调试
查看启动器下载到的主脚本首行:
```bash
BREWUP_DEBUG=1 brewup
```
## 重试设置
主脚本默认对可识别的瞬时错误最多尝试 3 次,等待时间从 5 秒开始并按倍数增加:
```bash
HB_RETRY_ATTEMPTS=4 HB_RETRY_DELAY_SECONDS=3 brewup
```
启动器下载远程主脚本时默认最多重试 3 次,每次间隔 2 秒:
```bash
BREWUP_DOWNLOAD_RETRIES=5 BREWUP_DOWNLOAD_RETRY_DELAY_SECONDS=3 brewup
```
权限不足、以 root 运行 Homebrew、代码签名失败等确定性错误不会盲目重复重试。批量 Cask 升级遇到这类错误后,脚本仍会尝试对剩余 Cask 逐个执行一次,以隔离失败项并避开 `--force`
为了兼顾性能,正常命令的 stdout 会直接输出,不写入重试日志;脚本只临时记录通常包含错误信息的 stderr。Cask 批量阶段不会整体重试,只有仍然过期的失败项会进入单项重试。单项升级保持串行,因为并行运行多个 Homebrew 写操作会争用 Homebrew 锁和 `/Applications`,通常不会更快,也更容易产生安装冲突。
## 执行流程
主脚本执行顺序:
1. `brew update -v`
2. `brew doctor`
3. `brew upgrade --formula`
4. `brew upgrade --cask --greedy --force`;失败时对剩余 Cask 逐个执行 `brew upgrade --cask --greedy <cask>`
5. `brew cleanup --prune=all`
## 常见问题
### 首次运行为什么要输入 sudo 密码?
启动器会把 sudo 密码保存到当前用户的 macOS Keychain,后续通过临时 `SUDO_ASKPASS` 脚本读取,用于刷新 sudo 凭据。密码不会写入仓库,也不会写入主脚本。
### Keychain 中的 sudo 密码不可用
通常是系统密码已变更,或 Keychain 条目内容不再正确。删除后重新运行即可:
```bash
security delete-generic-password -a "$USER" -s brewup-sudo-password
brewup
```
### 表格或输出宽度异常
指定固定宽度:
```bash
brewup --width 130
```
或:
```bash
HB_TERMINAL_WIDTH=130 brewup
```
### `Error: can't modify frozen Array`
这是 `buo/cask-upgrade` tap 与新版 Homebrew 的兼容问题。先移除该 tap:
```bash
brew untap buo/cask-upgrade
```
GUI 应用升级现在使用 Homebrew 官方的 `brew upgrade --cask --greedy --force`,脚本不再需要 `buo/cask-upgrade`
### `brew doctor` 提示 warning
`brew doctor` 的 warning 不一定代表脚本失败。脚本会继续执行,并打印:
```bash
Warning: 'brew doctor' detected issues. Manual review and resolution are recommended.
```
常见 warning 处理方式:
- `Some installed casks are deprecated or disabled`:说明某些 Cask 已废弃或被禁用,例如 `ayugram`。可以自行寻找替代应用,或不再需要时卸载:
```bash
brew uninstall --cask ayugram
```
- `Homebrew's "sbin" was not found in your PATH`:说明 shell 的 PATH 缺少 Homebrew 的 sbin 目录。Apple Silicon Mac 通常可加入:
```bash
echo 'export PATH="/opt/homebrew/sbin:$PATH"' >> ~/.zshrc
source ~/.zshrc
```
Intel Mac 或 `/usr/local` 安装的 Homebrew 可加入:
```bash
echo 'export PATH="/usr/local/sbin:$PATH"' >> ~/.zshrc
source ~/.zshrc
```
### Cask 下载曾经报错但最终升级完成
`brew upgrade --cask --greedy --force` 有时会在下载阶段出现 `curl: (18) Transferred a partial file` 之类的瞬时错误,随后 Homebrew 又重试并完成安装。此时命令仍可能返回非零退出码。
脚本会在批量 Cask 升级命令失败后执行:
```bash
brew outdated --cask --greedy
```
如果没有剩余过期 Cask,脚本会把前一次错误视为已恢复并继续执行 `brew cleanup --prune=all`。如果仍有过期 Cask,脚本会逐个重试;网络类错误按配置退避重试,其他错误执行一次。最终仍有过期 Cask 时,脚本保留失败退出状态,方便发现真实失败项。
### Cask 报 `Running Homebrew as root`
某些 macOS/Homebrew 组合在覆盖现有 App、复制扩展属性时,会从 Cask 内部通过 `sudo` 调用 `brew ruby`,随后被 Homebrew 自身的 root 安全检查拒绝。这不代表整个 `brewup` 是通过 `sudo brew` 启动的。
新版脚本会在批量升级失败后,对仍然过期的 Cask 去掉 `--force` 单独重试。例如:
```bash
brew upgrade --cask --greedy visual-studio-code
```
如果单独重试仍失败,请到“系统设置 → 隐私与安全性 → App 管理”中允许当前终端管理应用,然后再次运行。不要使用 `sudo brew upgrade`。
## 注意事项
- 脚本启用了 `set -e` 和 `set -o pipefail`,关键命令失败会终止流程。
- `brew upgrade --cask --greedy --force` 可能升级或替换已安装 GUI 应用,建议先保存重要工作。
- 远程启动器属于“下载后执行”模式,只应从可信仓库使用。
- 在公司设备或受管 macOS 上运行前,先确认 Homebrew、Cask、Keychain 和 sudo 策略允许自动升级。