feat(core): 新增 Homebrew 远程启动器并更新文档

新增 `brew-upgrade-manager-bootstrap.sh` 启动器脚本。该启动器支持通过 macOS Keychain 安全存储 sudo 密码,并能自动从远程拉取最新的 Homebrew 升级主脚本执行,随后清理临时文件。

同步更新 `README.md`,提供了推荐的 `brewup` 函数配置方法、Keychain 密码管理说明以及 SHA256 校验等调试指南。

主要变更:
- 新增支持 Keychain 认证的远程启动器脚本
- 实现 sudo 凭据自动管理与安全存储
- 完善项目文档,增加详细的使用说明和配置推荐
This commit is contained in:
2026-05-08 01:53:12 +08:00
parent 5d40ad44e9
commit 54a4fa7e65
2 changed files with 205 additions and 42 deletions

View File

@@ -1,48 +1,89 @@
# Homebrew Upgrade Manager # Homebrew Upgrade Manager
`brew-upgrade-manager.sh` 是一个 macOS Homebrew 升级脚本,用按固定流程更新仓库、检查环境、升级命令行工具和 GUI 应用,并清理旧缓存。 `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 update -v` 更新 Homebrew 仓库。
- 执行 `brew doctor` 做健康检查发现问题时给出警告但不中断后续流程。 - 执行 `brew doctor` 做健康检查发现问题时给出警告但不中断后续流程。
- 自动检查并安装 `buo/cask-upgrade`,用于提供 `brew cu` - 检查并安装 `buo/cask-upgrade` tap保留 GUI 应用升级相关兼容能力
- 使用 `brew upgrade --formula` 升级命令行 Formula - 使用 `brew upgrade --formula` 升级命令行工具
- 使用 `brew cu -yaq` 升级 Cask GUI 应用。 - 使用 `brew upgrade --cask --greedy --force` 强制升级 GUI 应用。
- 使用 Python PTY 包装 `brew cu`,用于处理交互输入转发和终端尺寸问题 - 执行 `brew cleanup --prune=all` 清理旧版本和缓存
- 执行 `brew cleanup --prune=all` 清理旧版本与缓存 - 支持固定终端宽度,避免非交互环境下输出宽度异常
- 启动器支持通过 macOS Keychain 保存并读取 sudo 密码,用于 `sudo -A -v` 预刷新 sudo 凭据。
## 依赖 ## 依赖
- macOS - macOS
- Homebrew - Homebrew
- Bash - Bash
- Python 3 - `curl`
- 可访问 Homebrew tap 的网络环境 - macOS Keychain 工具 `/usr/bin/security`,仅启动器需要
可先检查: 可先检查:
```bash ```bash
brew --version brew --version
python3 --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 ```bash
cd homebrew cd homebrew
chmod +x brew-upgrade-manager.sh chmod +x brew-upgrade-manager.sh
```
运行:
```bash
./brew-upgrade-manager.sh ./brew-upgrade-manager.sh
``` ```
默认会动态读取当前终端宽度;运行过程中缩放窗口时,`brew cu` 的 PTY 尺寸也会跟随更新。如果遇到非交互环境或某些表格渲染异常,可以指定固定终端宽度: 指定固定终端宽度:
```bash ```bash
./brew-upgrade-manager.sh --width 130 ./brew-upgrade-manager.sh --width 130
@@ -55,54 +96,110 @@ chmod +x brew-upgrade-manager.sh
HB_TERMINAL_WIDTH=130 ./brew-upgrade-manager.sh HB_TERMINAL_WIDTH=130 ./brew-upgrade-manager.sh
``` ```
优先级为:命令行 `--width` 高于 `HB_TERMINAL_WIDTH`。两者都不设置时使用动态终端宽度 优先级为:命令行 `--width` 高于 `HB_TERMINAL_WIDTH`。两者都不设置时,脚本会读取当前终端宽度;无法读取时默认使用 `130`
## sudo 认证 ## 启动器行为
脚本不会保存、传递或自动注入 sudo 密码。如果你的 `brew cu` 流程会触发 sudo建议在运行脚本前先刷新 sudo 凭据 `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 ```bash
sudo -v brewup-sudo-password
./brew-upgrade-manager.sh
``` ```
果 sudo 凭据过期,脚本内的 PTY 逻辑只会把你的键盘输入转发给子进程,不会嗅探 `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
```
## 执行流程 ## 执行流程
1. 打印分隔线并更新 Homebrew 仓库。 主脚本执行顺序:
2. 执行 `brew doctor`
3. 检查 `buo/cask-upgrade` tap。 1. `brew update -v`
4. 升级 Formula。 2. `brew doctor`
5. 升级 Cask。 3. 检查 `buo/cask-upgrade` tap
6. 清理 Homebrew 缓存。 4. `brew upgrade --formula`
5. `brew upgrade --cask --greedy --force`
6. `brew cleanup --prune=all`
## 常见问题 ## 常见问题
### `brew cu` 不存在 ### 首次运行为什么要输入 sudo 密码?
脚本会自动执行: 启动器会把 sudo 密码保存到当前用户的 macOS Keychain后续通过临时 `SUDO_ASKPASS` 脚本读取,用于刷新 sudo 凭据。密码不会写入仓库,也不会写入主脚本。
### Keychain 中的 sudo 密码不可用
通常是系统密码已变更,或 Keychain 条目内容不再正确。删除后重新运行即可:
```bash ```bash
brew tap buo/cask-upgrade security delete-generic-password -a "$USER" -s brewup-sudo-password
brewup
``` ```
如果失败通常是网络、Homebrew tap 或权限问题。 ### 表格或输出宽度异常
### 表格渲染或 Ruby 报终端宽度错误 指定固定宽度:
默认会跟随终端窗口变化;如果某些环境无法正确报告窗口尺寸,可以使用固定宽度运行:
```bash ```bash
./brew-upgrade-manager.sh --width 130 brewup --width 130
``` ```
### sudo 卡住 或:
先在交互终端运行 `sudo -v`,确认 sudo 凭据有效后再执行脚本。非交互环境中请避免依赖脚本内输入密码。 ```bash
HB_TERMINAL_WIDTH=130 brewup
```
### `brew tap buo/cask-upgrade` 失败
通常是网络、Homebrew tap 或权限问题。先确认 Homebrew 可正常访问 GitHub 和对应 tap。
## 注意事项 ## 注意事项
- 脚本启用了 `set -e``set -o pipefail`,关键命令失败会终止流程。 - 脚本启用了 `set -e``set -o pipefail`,关键命令失败会终止流程。
- 升级 GUI 应用可能关闭或替换已安装应用,建议重要工作保存后执行 - `brew upgrade --cask --greedy --force` 可能升级或替换已安装 GUI 应用,建议先保存重要工作。
- 如果你使用公司设备或受管 macOS先确认 Homebrew、Cask 和 sudo 策略是否允许自动升级。 - 远程启动器属于“下载后执行”模式,只应从可信仓库使用。
- 在公司设备或受管 macOS 上运行前,先确认 Homebrew、Cask、Keychain 和 sudo 策略允许自动升级。

View File

@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# 目标:下载远程升级脚本、执行、销毁临时文件
set -euo pipefail
REMOTE="https://git.orionc.me/orion/script/raw/branch/main/homebrew/brew-upgrade-manager.sh"
TEMP="$(mktemp "${TMPDIR:-/tmp}/brew-upgrade-manager.XXXXXX.sh")"
KEYCHAIN_SERVICE="${BREWUP_KEYCHAIN_SERVICE:-brewup-sudo-password}"
ASKPASS_TEMP="$(mktemp "${TMPDIR:-/tmp}/brewup-askpass.XXXXXX.sh")"
cleanup() {
rm -f "$TEMP" "$ASKPASS_TEMP"
}
trap cleanup EXIT INT TERM
PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
export PATH
setup_sudo_askpass() {
cat > "$ASKPASS_TEMP" <<'EOF'
#!/usr/bin/env bash
exec /usr/bin/security find-generic-password -a "${USER:-$(id -un)}" -s "${BREWUP_KEYCHAIN_SERVICE:-brewup-sudo-password}" -w
EOF
chmod 700 "$ASKPASS_TEMP"
export SUDO_ASKPASS="$ASKPASS_TEMP"
export BREWUP_KEYCHAIN_SERVICE="$KEYCHAIN_SERVICE"
if ! /usr/bin/security find-generic-password -a "$USER" -s "$KEYCHAIN_SERVICE" -w >/dev/null 2>&1; then
printf "首次使用:请输入一次 sudo 密码,将保存到 macOS Keychain"
IFS= read -r -s BREWUP_SUDO_PASSWORD
printf "\n"
/usr/bin/security add-generic-password -U -a "$USER" -s "$KEYCHAIN_SERVICE" -w "$BREWUP_SUDO_PASSWORD" >/dev/null
unset BREWUP_SUDO_PASSWORD
fi
echo "正在通过 Keychain 准备 sudo 凭据..."
if ! sudo -A -v; then
echo "Keychain 中的 sudo 密码不可用,请删除后重新保存:" >&2
echo " security delete-generic-password -a \"$USER\" -s \"$KEYCHAIN_SERVICE\"" >&2
exit 1
fi
}
setup_sudo_askpass
echo "正在下载远程脚本..."
curl -f -sSL "$REMOTE" -o "$TEMP"
chmod 600 "$TEMP"
if [[ -n "${BREWUP_SHA256:-}" ]]; then
echo "正在校验脚本 SHA256..."
actual_sha256="$(shasum -a 256 "$TEMP")"
actual_sha256="${actual_sha256%% *}"
if [[ "$actual_sha256" != "$BREWUP_SHA256" ]]; then
echo "脚本 SHA256 不匹配,已停止执行。" >&2
echo "Expected: $BREWUP_SHA256" >&2
echo "Actual: $actual_sha256" >&2
exit 1
fi
fi
if [[ "${BREWUP_DEBUG:-}" == "1" ]]; then
echo "Downloaded script first line:"
head -n 1 "$TEMP"
fi
bash "$TEMP" "$@"