Files
script/homebrew/README.md
T
orion 8124810b6e feat(homebrew): 添加 Homebrew 输出流式着色功能
- 实现 can_stream_colorize_tty_output 函数检测流式着色支持
- 添加 colorize_brew_tty_stream 函数处理实时进度条和语义配色
- 使用 Perl 脚本实现流式过滤器,即时处理 CRLF 和 CR 换行
- 优化 run_brew_colored 函数集成伪终端和流式着色
- 改进日志记录功能,保留错误分类和重试决策所需信息
- 修复进度条缓冲问题,确保实时显示升级状态
2026-07-24 08:32:50 +08:00

337 lines
12 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` 做健康检查;发现问题时给出警告,但不中断后续流程。
- 批量升级 Formula 和 Cask;批量命令失败后,仅对仍然过期的项目逐个重试。
- Formula 与 Cask 阶段相互隔离,一个包失败不会阻止另一类包继续升级。
- 默认使用 `brew upgrade --cask --greedy`,不强制覆盖已有 App;需要时可显式开启 `--force`
- 网络、下载、HTTP 429/5xx 等瞬时错误会自动退避重试;权限、root、证书、checksum 和磁盘空间错误不会盲目重试。
- 同一用户只能运行一个主脚本实例,避免并发升级争用 Homebrew 锁和 `/Applications`
- 升级与失败重试全部结束后,执行 `brew cleanup --prune=all` 清理全部缓存和旧版本。
- 支持 `--width`、环境变量宽度和终端动态宽度。
- 交互式终端中保留 Homebrew 6 的原生实时下载进度条和动态状态。
- 启动器支持通过 macOS Keychain 保存并读取 sudo 密码,用于 `sudo -A -v` 预刷新 sudo 凭据。
## 依赖
- macOS
- Homebrew
- Bash
- `curl`
- macOS 自带的 `/usr/bin/script``/usr/bin/perl`(用于兼顾实时进度与自定义配色)
- 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`。用于错误分类的临时日志会在命令结束或收到信号后删除。
在交互式终端中,主脚本会通过 macOS 自带的 `script(1)` 为 Homebrew 创建伪终端,以便 Homebrew 6 实时重绘并行下载进度。后续的流式着色器会立即透传进度条的回车刷新,并只对完整文本行应用包名、版本和状态配色。需要记录错误以供重试判断时,`script(1)` 也会同步保存日志。如果将 `brewup` 输出重定向到文件或 CI,则自动切换为稳定的逐行输出,不绘制动态进度条。
## 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 升级阶段会捕获错误、继续隔离其他失败项,并在最后统一返回状态。
- Homebrew 输出在交互式终端、重定向和 CI 中都会经过同一套语义着色,避免版本变化行因原生 TTY 输出而丢失颜色。
- `brew upgrade --cask --greedy` 可能退出正在运行的 GUI 应用,建议先保存重要工作。
- 不建议日常启用 `HB_CASK_FORCE=1`;它会允许 Homebrew 覆盖已有 Cask 文件。
- 远程启动器属于“下载后执行”模式,只应从可信仓库使用。
- 在公司设备或受管 macOS 上运行前,先确认 Homebrew、Cask、Keychain 和 sudo 策略允许自动升级。