From 54a4fa7e65394d5a0aa9e15c1bbc8fee511b33d3 Mon Sep 17 00:00:00 2001 From: Orion Date: Fri, 8 May 2026 01:53:12 +0800 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E2=9C=A8=20=E6=96=B0=E5=A2=9E=20?= =?UTF-8?q?Homebrew=20=E8=BF=9C=E7=A8=8B=E5=90=AF=E5=8A=A8=E5=99=A8?= =?UTF-8?q?=E5=B9=B6=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 `brew-upgrade-manager-bootstrap.sh` 启动器脚本。该启动器支持通过 macOS Keychain 安全存储 sudo 密码,并能自动从远程拉取最新的 Homebrew 升级主脚本执行,随后清理临时文件。 同步更新 `README.md`,提供了推荐的 `brewup` 函数配置方法、Keychain 密码管理说明以及 SHA256 校验等调试指南。 主要变更: - 新增支持 Keychain 认证的远程启动器脚本 - 实现 sudo 凭据自动管理与安全存储 - 完善项目文档,增加详细的使用说明和配置推荐 --- homebrew/README.md | 181 ++++++++++++++++----- homebrew/brew-upgrade-manager-bootstrap.sh | 66 ++++++++ 2 files changed, 205 insertions(+), 42 deletions(-) create mode 100755 homebrew/brew-upgrade-manager-bootstrap.sh diff --git a/homebrew/README.md b/homebrew/README.md index 74fa4f1..a741ae5 100644 --- a/homebrew/README.md +++ b/homebrew/README.md @@ -1,48 +1,89 @@ # 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 doctor` 做健康检查,发现问题时给出警告但不中断后续流程。 -- 自动检查并安装 `buo/cask-upgrade`,用于提供 `brew cu`。 -- 使用 `brew upgrade --formula` 升级命令行 Formula。 -- 使用 `brew cu -yaq` 升级 Cask GUI 应用。 -- 使用 Python PTY 包装 `brew cu`,用于处理交互输入转发和终端尺寸问题。 -- 执行 `brew cleanup --prune=all` 清理旧版本与缓存。 +- 执行 `brew doctor` 做健康检查;发现问题时给出警告,但不中断后续流程。 +- 检查并安装 `buo/cask-upgrade` tap,保留 GUI 应用升级相关兼容能力。 +- 使用 `brew upgrade --formula` 升级命令行工具。 +- 使用 `brew upgrade --cask --greedy --force` 强制升级 GUI 应用。 +- 执行 `brew cleanup --prune=all` 清理旧版本和缓存。 +- 支持固定终端宽度,避免非交互环境下输出宽度异常。 +- 启动器支持通过 macOS Keychain 保存并读取 sudo 密码,用于 `sudo -A -v` 预刷新 sudo 凭据。 ## 依赖 - macOS - Homebrew - Bash -- Python 3 -- 可访问 Homebrew tap 的网络环境 +- `curl` +- macOS Keychain 工具 `/usr/bin/security`,仅启动器需要 可先检查: ```bash 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 cd homebrew chmod +x brew-upgrade-manager.sh -``` - -运行: - -```bash ./brew-upgrade-manager.sh ``` -默认会动态读取当前终端宽度;运行过程中缩放窗口时,`brew cu` 的 PTY 尺寸也会跟随更新。如果遇到非交互环境或某些表格渲染异常,可以指定固定终端宽度: +指定固定终端宽度: ```bash ./brew-upgrade-manager.sh --width 130 @@ -55,54 +96,110 @@ chmod +x 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 -sudo -v -./brew-upgrade-manager.sh +brewup-sudo-password ``` -如果 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= brewup +``` + +如果哈希不匹配,启动器会停止执行。 + +## 调试 + +查看启动器下载到的主脚本首行: + +```bash +BREWUP_DEBUG=1 brewup +``` ## 执行流程 -1. 打印分隔线并更新 Homebrew 仓库。 -2. 执行 `brew doctor`。 -3. 检查 `buo/cask-upgrade` tap。 -4. 升级 Formula。 -5. 升级 Cask。 -6. 清理 Homebrew 缓存。 +主脚本执行顺序: + +1. `brew update -v` +2. `brew doctor` +3. 检查 `buo/cask-upgrade` tap +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 -brew tap buo/cask-upgrade +security delete-generic-password -a "$USER" -s brewup-sudo-password +brewup ``` -如果失败,通常是网络、Homebrew tap 或权限问题。 +### 表格或输出宽度异常 -### 表格渲染或 Ruby 报终端宽度错误 - -默认会跟随终端窗口变化;如果某些环境无法正确报告窗口尺寸,可以使用固定宽度运行: +指定固定宽度: ```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`,关键命令失败会终止流程。 -- 升级 GUI 应用可能关闭或替换已安装应用,建议在重要工作保存后执行。 -- 如果你使用公司设备或受管 macOS,先确认 Homebrew、Cask 和 sudo 策略是否允许自动升级。 +- `brew upgrade --cask --greedy --force` 可能升级或替换已安装 GUI 应用,建议先保存重要工作。 +- 远程启动器属于“下载后执行”模式,只应从可信仓库使用。 +- 在公司设备或受管 macOS 上运行前,先确认 Homebrew、Cask、Keychain 和 sudo 策略允许自动升级。 diff --git a/homebrew/brew-upgrade-manager-bootstrap.sh b/homebrew/brew-upgrade-manager-bootstrap.sh new file mode 100755 index 0000000..29712ba --- /dev/null +++ b/homebrew/brew-upgrade-manager-bootstrap.sh @@ -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" "$@"