为什么要重装
本地的 Homebrew 从 6.x 一路升级上来,混杂了多年前的 tuna 镜像源配置、残留的 Cask,以及一些不再使用的 formula。这次决定推倒重来:彻底卸载(含所有 Cask 与配置),装最新版,再把需要的东西装回来,最后加一个每周自动更新的定时任务,一劳永逸。
整个过程在 Apple Silicon 的 Mac(brew 前缀为 /opt/homebrew)上完成,旧版本 6.0.21,新版本 7.0.6。
第一步:卸载前先摸底
动手前先记录现场,免得重装时想不起来原来装过什么:
brew list --formula | wc -l # 48
brew list --cask # datagrip google-chrome intellij-idea lens orbstack typora
brew tap
brew services list
另外记下 shell 配置里和 brew 相关的行(~/.zprofile 里的 brew shellenv 和镜像源 export),后面清理要用。
第二步:卸载 Cask(带 —zap)
Cask 的卸载分两步走:先用 --zap 卸载,这样连应用的用户配置数据一起清掉,才算”彻底”:
brew services stop --all
for c in datagrip google-chrome intellij-idea lens orbstack typora; do
brew uninstall --cask --zap --force "$c"
done
这里踩了两个坑:
--zap会删用户数据,务必想清楚。比如 DataGrip / IntelliJ 的设置和插件目录会被清空。Chrome 的用户数据(书签、历史)同样在 zap 范围内,这次因为缺少”完全磁盘访问权限”而跳过了,反而保住了数据——算是因祸得福。- OrbStack 有系统级残留。它的
~/Library/Group Containers/...dev.orbstack目录连sudo都删不掉(TCC 拦截),需要先给终端开启”完全磁盘访问权限”再手动清理。
收尾时还有两个 Cask(google-chrome、typora)因为 zap 阶段报错而停留在”已安装”状态,用不带 --zap 的强制卸载补刀即可,App 本体当时其实已经被删掉了:
brew uninstall --cask --force google-chrome typora
brew list --cask # 空,干净了
第三步:运行官方卸载脚本
curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/uninstall.sh -o /tmp/brew_uninstall.sh
NONINTERACTIVE=1 bash /tmp/brew_uninstall.sh --force --path=/opt/homebrew
官方脚本默认会连 ~/Library/Caches/Homebrew 和 ~/Library/Logs/Homebrew 一起删(除非加 --skip-cache-and-logs),这一步不需要额外操作。
但脚本在删除 /opt/homebrew 本体和 /etc/paths.d/homebrew 时需要 sudo,在非交互终端里会直接报 Need sudo access 而不删除任何东西。我的解法是用 macOS 的 GUI 提权弹窗代替终端输密码:
osascript -e 'do shell script "rm -rf /opt/homebrew /etc/paths.d/homebrew" with administrator privileges'
会弹出系统密码框,输一次开机密码即可,适合各类自动化场景。
第四步:手动清理残留配置
官方脚本不管 shell 配置文件,手动清理 ~/.zprofile 里这几行:
eval "$(/opt/homebrew/bin/brew shellenv zsh)"
export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git"
export HOMEBREW_CORE_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git"
删掉旧的 tuna 镜像源,让新装的 brew 回到官方源。至此旧环境清空约 1.4GB,which brew 已经找不到了。
最大的一课:我的
pi命令行工具装在旧 brew 前缀里(/opt/homebrew/bin/pi),rm -rf /opt/homebrew把它一起带走了。重装 brew 之后记得把装在 prefix 里的全局工具也重装一遍。
第五步:安装最新版 Homebrew
官方安装命令配合非交互模式:
curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh -o /tmp/brew_install.sh
NONINTERACTIVE=1 bash /tmp/brew_install.sh
同样有 sudo 密码问题。install.sh 明确拒绝以 root 身份整体运行(Don't run this as root!),但它支持 SUDO_ASKPASS——于是写一个用 AppleScript 弹窗要密码的 askpass helper:
#!/bin/bash
# /tmp/brew_askpass.sh
osascript -e 'display dialog "Homebrew 安装需要管理员密码:" default answer "" with hidden answer buttons {"确定"} default button "确定" with title "Homebrew"' -e 'text returned of result'
chmod +x /tmp/brew_askpass.sh
SUDO_ASKPASS=/tmp/brew_askpass.sh NONINTERACTIVE=1 bash /tmp/brew_install.sh
安装过程会弹出一次图形密码框,输完密码后全程自动,一次成功。装完按提示把 eval "$(/opt/homebrew/bin/brew shellenv zsh)" 加回 ~/.zprofile,新开终端验证:
brew --version # Homebrew 7.0.6
brew list # 空的,干干净净
第六步:把软件装回来
按摸底时记下的清单原样恢复。48 个 formula 一条命令(依赖会自动解决):
brew install awscli brotli c-ares ca-certificates cairo fontconfig freetype gettext \
giflib glib graphite2 harfbuzz hdrhistogram_c icu4c@78 jpeg-turbo kubernetes-cli \
libnghttp2 libnghttp3 libngtcp2 libpng libtiff libunistring libuv libx11 libxau \
libxcb libxdmcp libxext libxrender little-cms2 lz4 lzo maven mpdecimal node@24 \
openjdk openssl@3 pcre2 pixman python@3.13 readline simdjson sqlite uv uvwasi \
xorgproto xz zstd
6 个 Cask:
brew install --cask datagrip google-chrome intellij-idea lens orbstack typora
其中 intellij-idea 第一次下载遇到 curl: (92) HTTP/2 stream ... PROTOCOL_ERROR,单独重试一次就成功了——JetBrains 的大包下载偶发失败很常见,重试即可。当时恢复到:63 个 formula(含自动拉入的依赖)+ 6 个 Cask,全部最新版。OrbStack 装完后记得手动打开一次 App 完成初始化。
后续又把系统里其他能 brew 管理的应用也迁了进来,并装了 Ghostty,见下文。
第七步:每周自动更新
手动更新坚持不了几周,写个脚本交给 macOS 原生的 launchd。脚本 ~/scripts/brew-weekly-upgrade.sh 核心就是:
brew update
brew upgrade
brew cleanup
brew autoremove
外加几处工程化细节:
- 用
mkdir原子操作做防重入锁,避免上次没跑完又触发; - 日志写到
~/Library/Logs/Homebrew/weekly-upgrade.log,超 5MB 自动轮转; - 代理自适应:本地代理端口通就用它,不通就直连(提前验证过直连 GitHub 没问题)。
定时任务 ~/Library/LaunchAgents/com.lucas.brew-weekly-upgrade.plist,每周一 09:00 执行:
<key>StartCalendarInterval</key>
<dict>
<key>Weekday</key>
<integer>1</integer>
<key>Hour</key>
<integer>9</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
注册并验证:
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.lucas.brew-weekly-upgrade.plist
launchctl print gui/$(id -u)/com.lucas.brew-weekly-upgrade | grep -E "state|calendar"
# 手动试跑一次
bash ~/scripts/brew-weekly-upgrade.sh
把系统里其他应用也迁入 brew
重装完成后顺手盘点了 /Applications,把能用 brew 管理的应用全部迁入 brew,这样每周自动更新能一并覆盖。方法:先 brew search --cask 确认 token,再用 brew info --cask --json=v2 核对装出来的 App 名和本地一致(大小写无所谓,macOS 文件系统不敏感),然后删旧版、brew install --cask 装回。只删 App 本体,~/Library 里的账号数据和设置都在,打开即原样。
这次迁入的 13 个:百度网盘、ChatGPT、Claude、GitHub Desktop、Sourcetree、腾讯会议、迅雷、VirtualBox、WebStorm、微信、企业微信(token 是 wechatwork)、WPS 国际版(本机是 12.1.26046,对应 wpsoffice 而非 wpsoffice-cn)、语雀。
几个有意没动的:
- Antify:搜过,没有对应 Cask,保持不动。
- 爱思助手(i4Tools):没有 Cask,直接卸载。顺手把残留一起清了:
~/Library/Preferences/cn.i4Tools.mac.plist和~/Library/Application Support/爱思助手,复查/Library启动项无残留。 - 豆包浏览器:那个 948 字节的跳转小文件,一直都在,从没动过(别和豆包主 App 搞混)。
两个”钉子户”:豆包与网易云音乐
豆包和网易云音乐有 Cask,但装不上。下载没问题(hdiutil verify 显示 checksum VALID),卡在 brew 内部的 hdiutil attach:系统弹”此磁盘映像可能有问题”的确认框,在非交互环境下被自动取消。试过清隔离属性、Finder 里手动点确认、-noverify,一律不行;但同文件用 diskutil image attach 一次挂载成功——这是新系统上 hdiutil 的策略问题,不是文件损坏。
解法:diskutil 挂载后手动把 Doubao.app / NeteaseMusic.app 拷回 /Applications,然后卸载镜像。这两个就只能手动管理了——好在它们自带应用内自动更新。每周更新脚本里加了一段提醒,每次跑完顺带报告它俩的版本号,看到有新版就去应用内点更新。
Ghostty:安装与配置
顺手把终端换成了 Ghostty:
brew install --cask ghostty
brew install --cask font-jetbrains-mono
字体版权提醒:JetBrains Mono 采用 SIL Open Font License 1.1,个人与商业使用均免费;且该 Cask 从 JetBrains 官方 GitHub Releases 直接下载,无版权风险。
配置在 ~/Library/Application Support/com.mitchellh.ghostty/config:
# 无限滚动:1 亿行,实际只受内存限制
scrollback-limit = 100000000
font-family = "JetBrains Mono"
font-size = 16
# 行高 1.3 倍(Ghostty 没有单独的 line-height,用单元格高度百分比)
adjust-cell-height = 30%
改完新开窗口即生效。
踩坑总结
--zap是双刃剑:清得彻底,但连 IDE 配置、插件一起删,执行前确认备份。- Cask 卸载可能因”完全磁盘访问权限”部分失败:App 本体一般能删掉,残留的用户数据手动补删即可,Chrome 书签这次就是这样保住的。
- 非交互环境下的 sudo:
osascript ... with administrator privileges和SUDO_ASKPASS组合,可以在不暴露密码的前提下完成提权。 - 别删了自己脚下的船:通过 brew prefix 安装的全局工具(如
pi、npm全局包)会随rm -rf /opt/homebrew一起消失,提前列出来,重装后补回。 - 大包 Cask 下载失败先重试:JetBrains 系的 dmg 偶发
PROTOCOL_ERROR,基本都是网络抖动,重试一次就好。 - 迁移前先核对 App 名:用
brew info --cask --json=v2看 artifact 里的 App 名和本地是否一致,删错了很麻烦;企业微信的 token 是wechatwork,WPS 有国际版和 CN 版之分,先看清本机版本再选。 - 不是所有 Cask 都装得上:某些国产厂商的 dmg 在新系统上过不了
hdiutil挂载确认,认栽手动装,让它们用应用内更新即可。
DISCUSSION
讨论与反馈