ccaff22653
- 实现终端主题检测功能,支持深色/浅色模式自动切换配色方案 - 添加 256 色终端支持,优化不同背景下的颜色对比度 - 引入版本变更高亮显示,突出包名、旧版本、新版本的变化 - 集成 OSC 11 协议查询终端背景色,提升跨终端兼容性 - 将清理策略从按日期清理改为统一执行 brew cleanup --prune=all - 移除自定义缓存清理逻辑,使用 Homebrew 内置清理机制 - 增强输出着色功能,按语义对 Homebrew 输出进行彩色标记 - 更新文档说明新的清理流程和终端配色特性
332 lines
11 KiB
Markdown
332 lines
11 KiB
Markdown
# 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 --prune=all` 清理全部缓存和旧版本。
|
||
- 支持 `--width`、环境变量宽度和终端动态宽度。
|
||
- 启动器支持通过 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 --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 配置:
|
||
|
||
```bash
|
||
source ~/.zshrc
|
||
```
|
||
|
||
之后直接运行:
|
||
|
||
```bash
|
||
brewup
|
||
```
|
||
|
||
传递参数时也可以正常转发给主脚本:
|
||
|
||
```bash
|
||
brewup --width 160
|
||
```
|
||
|
||
如果更偏好 alias,也可以使用:
|
||
|
||
```bash
|
||
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 了本仓库,也可以直接运行主脚本:
|
||
|
||
```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`。
|
||
|
||
## 终端主题与配色
|
||
|
||
主脚本默认使用 `HB_COLOR_THEME=auto`,优先跟随操作系统当前的日间/夜间外观:支持 macOS、iOS/iPadOS、Windows、WSL、Android,以及使用 freedesktop Portal、GNOME、KDE、Cinnamon 或 XFCE 的 Linux 桌面。
|
||
|
||
SSH 会话会直接以客户端终端背景为准,因为远端系统的日夜状态不代表 iPhone、iPad 或 Android 客户端当前主题。其他无法读取系统外观的环境(例如容器和无桌面服务器)也会继续读取 `COLORFGBG`,并通过标准 OSC 11 查询终端当前的真实背景色。Apple Terminal、iTerm2、Termius、Blink、a-Shell、iSH、Kitty、WezTerm、Alacritty 等支持相应信息的终端都能走通用回退。深色背景使用偏亮色阶,浅色背景使用更深的色阶,避免黄色、青色在白底上难以辨认。
|
||
|
||
如果终端屏蔽了背景色查询,或者需要固定配色,可以手动覆盖:
|
||
|
||
```bash
|
||
HB_COLOR_THEME=dark brewup
|
||
HB_COLOR_THEME=light brewup
|
||
```
|
||
|
||
终端支持 256 色时会使用精细色阶;否则自动退回标准 ANSI 深色/浅色方案。输出含义保持一致:包名为青色,旧版本为黄色,变化箭头为蓝色,新版本和成功状态为绿色,警告为黄色,错误为红色。
|
||
|
||
## 启动器行为
|
||
|
||
`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 名称为:
|
||
|
||
```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
|
||
```
|
||
|
||
下载连接和低速超时也可以调整:
|
||
|
||
```bash
|
||
BREWUP_CONNECT_TIMEOUT_SECONDS=20 BREWUP_LOW_SPEED_TIME_SECONDS=45 brewup
|
||
```
|
||
|
||
权限不足、以 root 运行 Homebrew、证书校验、checksum、磁盘空间不足等确定性错误不会盲目重复重试。批量升级失败后,脚本仍会对剩余项目逐个执行一次,以隔离真实失败项。
|
||
|
||
为了兼顾性能,Formula 和 Cask 的批量阶段都只执行一次,只有仍然过期的失败项进入单项重试。单项升级保持串行,因为并行运行多个 Homebrew 写操作会争用 Homebrew 锁和 `/Applications`。用于错误分类的临时日志会在命令结束或收到信号后删除。
|
||
|
||
## Cask 与清理策略
|
||
|
||
默认不使用 `--force`。确实需要覆盖已有 Cask 文件时:
|
||
|
||
```bash
|
||
HB_CASK_FORCE=1 brewup
|
||
```
|
||
|
||
升级、失败隔离和重试全部在前一步完成。最后的清理阶段只执行一次:
|
||
|
||
```bash
|
||
brew cleanup --prune=all
|
||
```
|
||
|
||
不再按日期区分新旧缓存,也不保留当天下载;清理由 Homebrew 自己统一完成。主脚本设置 `HOMEBREW_NO_INSTALL_CLEANUP=1`,避免每个包安装后重复清理。
|
||
|
||
如果 `brew doctor` 明显影响执行时间,也可以跳过:
|
||
|
||
```bash
|
||
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 --prune=all`,一次性清理全部缓存和旧版本
|
||
6. 汇总失败项并返回最终状态码
|
||
|
||
## 常见问题
|
||
|
||
### 首次运行为什么要输入 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`,脚本不再需要 `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` 有时会在下载阶段出现 `curl: (18) Transferred a partial file` 之类的瞬时错误,随后 Homebrew 又完成安装。此时批量命令仍可能返回非零退出码。
|
||
|
||
脚本会在批量 Cask 升级命令失败后执行:
|
||
|
||
```bash
|
||
brew outdated --cask --greedy
|
||
```
|
||
|
||
如果没有剩余过期 Cask,脚本会把前一次错误视为已恢复。如果仍有过期 Cask,脚本会逐个重试;网络类错误按配置退避重试,其他错误执行一次。最终仍有过期项时,脚本记录失败状态,然后进入统一的 `brew cleanup --prune=all` 清理阶段。
|
||
|
||
### Cask 报 `Running Homebrew as root`
|
||
|
||
某些 macOS/Homebrew 组合在覆盖现有 App、复制扩展属性时,会从 Cask 内部通过 `sudo` 调用 `brew ruby`,随后被 Homebrew 自身的 root 安全检查拒绝。这不代表整个 `brewup` 是通过 `sudo brew` 启动的。
|
||
|
||
新版脚本默认不再使用 `--force`,并在批量升级失败后对仍然过期的 Cask 单独重试。例如:
|
||
|
||
```bash
|
||
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 策略允许自动升级。
|