1. 为什么你需要一个“一键更新镜像源”的脚本
先聊个真实场景:你兴冲冲地在 Mac 上执行 brew install wget,结果终端卡在 Updating Homebrew... 半小时不动,最后直接报错 Failed to connect to github.com port 443: Operation timed out。这种体验我太熟悉了,尤其是网络环境不太理想的时候,Homebrew 默认从 GitHub 拉取仓库和二进制包,而 GitHub 的连接质量大家都懂,跟开盲盒一样——有时候快得离谱,有时候连个响应都没有。
于是“换镜像源”就成了 Mac 用户绕不开的话题。所谓镜像源,本质上就是官方仓库的“本地缓存副本”。国内有很多高校和云厂商维护自己的 Homebrew 镜像,比如清华 TUNA、中科大 USTC、阿里云,它们会定期同步 GitHub 上的 Homebrew 仓库,你从这些镜像下载时走的是国内线路,速度能快几十倍。
但这里有个坑:镜像源不是一劳永逸的。你换成中科大源之后,可能过几天中科大镜像挂了、或者你想换回官方源、或者公司网络换了导致某个镜像访问不了。每次都要手动敲一堆 export 命令、修改 git remote、替换 formula 下载地址,繁琐不说,还容易敲错。所以我就手写了一个“MAC OS 更新 homebrew 镜像源脚本”,把整个换源、更新、校验、恢复的过程全部自动化,今天把这个脚本的思路和完整实现分享出来。
这个脚本适合谁?第一类是刚接触 Homebrew 不久、被网络问题折腾到崩溃的新手,你可以直接拿去用;第二类是需要在多台 Mac 上反复配置开发环境的工程师,省得每台机器手动敲;第三类是单纯想搞懂“Homebrew 镜像源到底改的是哪些配置”的好奇派,把脚本读一遍,你的理解会比网上大多数教程深得多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 在写脚本之前,先弄懂 Homebrew 镜像源的底层配置
2.1 Homebrew 的更新链路到底长什么样
想写出靠谱的脚本,第一步是理解 Homebrew 更新时到底访问了哪些地址。Homebrew 不是单体应用,它由数个模块组成,它们各自的下载源是独立的:
- Homebrew 本体仓库(brew 命令的源码):默认地址是
https://github.com/Homebrew/brew.git,brew update时会拉取这个仓库的更新。 - Homebrew/homebrew-core(核心软件包仓库):默认地址是
https://github.com/Homebrew/homebrew-core.git,这是brew install时检索包定义的地方。 - homebrew-bottles(预编译二进制包):
brew install下载的不是源码编译,而是直接拉取编译好的 bottle 包,默认从ghcr.io(GitHub Container Registry)下载。这个域名在国内的访问情况比 github.com 还惨,经常直接超时。 - homebrew-cask(图形化应用仓库):如果你用
brew install --cask装 Chrome、VS Code 这类 App,走的就是这个仓库。
明白了这条链路,你就清楚换源的本质了——把上面四类地址从 GitHub 替换成国内镜像,并且换完源之后还要让系统依然可以正常安装软件,不只是改个 git remote 那么简单。
2.2 镜像源的“姿势”差异:HOMEBREW_API_DOMAIN 与 git remote
在 macOS 的新版 Homebrew 上,镜像源配置比网上老教程里写的复杂一些。老教程一般只让你改三行 git remote set-url,但在 Homebrew 4.x 之后,默认开启了对 JSON API 的支持。
你可以在终端里执行 brew config,看输出中是否存在 HOMEBREW_API_DOMAIN 这个环境变量。如果为空,说明 brew 默认从 GitHub 拉取 formula 的 JSON 元数据;而国内镜像(比如清华)提供的 https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/api 路径下就有这些 JSON 文件。
所以,一个兼容新旧版本的换源脚本,不能只改 git remote,还需要把 HOMEBREW_API_DOMAIN 和 HOMEBREW_BOTTLE_DOMAIN 这两个环境变量写入 shell 配置文件,否则就算 git remote 换成清华,API 拉取仍会走 GitHub,速度瓶颈根本解决不了。
2.3 哪些配置项需要写进脚本里
我梳理了一下,一个完整的 Homebrew 换源脚本至少要覆盖以下配置项:
| 配置项 | 默认地址 | 镜像替换目标 | 作用 |
|---|---|---|---|
| HOMEBREW_API_DOMAIN | https://formulae.brew.sh | 清华/中科大 API 地址 | 拉取软件包元数据 JSON |
| HOMEBREW_BOTTLE_DOMAIN | https://ghcr.io | 清华/中科大 bottle 地址 | 下载预编译二进制包 |
| HOMEBREW_BREW_GIT_REMOTE | https://github.com/Homebrew/brew.git | 镜像的 brew.git | Homebrew 本体仓库 |
| HOMEBREW_CORE_GIT_REMOTE | https://github.com/Homebrew/homebrew-core.git | 镜像的 homebrew-core.git | 软件包定义仓库 |
| HOMEBREW_CASK_GIT_REMOTE | https://github.com/Homebrew/homebrew-cask.git | 镜像的 cask.git | 图形化应用仓库 |
注意,有些镜像(比如阿里云)不提供 cask 仓库的镜像,这时候脚本里就要做分支判断。这也是我写脚本时比较重视的一点——不能把镜像源地址写死成一家,要允许用户自由选择。
3. 脚本设计思路与整体架构
3.1 我为什么不直接用网上别人写好的镜像脚本
如果你在 GitHub 上搜 homebrew mirror script,能找到很多现成项目。但我在实际使用中发现几个问题:有些脚本只适配了旧版 Homebrew,跑在新版本上 API 还是走 GitHub;有些脚本写死了中科大或者清华的地址,用户想换一家就得手动改脚本源码;还有些脚本直接覆盖 ~/.zshrc,把用户原本配置的别名、路径变量全冲掉了。
所以我决定自己写一个,目标很明确:
- 幂等性:脚本可以反复执行,不会因为重复跑而产生配置冲突。
- 可逆向:提供“恢复官方源”的功能,任何时候想反悔都能一键还原。
- 可选性:用户通过命令行参数选择中科大、清华或阿里云,而不是改代码。
- 安全性:不覆盖 shell 配置文件的原有内容,只做追加或精准替换。
3.2 脚本的整体流程
整个脚本分成五个阶段,逻辑上是一个线性的流程:
- 前置检查:确认系统是 macOS、确认 Homebrew 已安装、确认存在
HOMEBREW_PREFIX环境变量。 - 选择镜像源:通过参数或交互菜单让用户选择中科大、清华或阿里云。
- 配置环境变量:往
~/.zshrc(或~/.bash_profile)写入镜像地址。 - 切换 git remote:进入 Homebrew 安装目录,逐个仓库替换远程地址。
- 更新验证:执行
brew update并用brew config检查关键字段是否生效。
3.3 镜像源地址映射表
不同镜像站提供的路径结构差别很大,这里我先整理一份我当时收集的地址映射,脚本的核心就是这张表:
| 镜像站 | Brew 仓库地址 | Homebrew-core 地址 | API 地址 | Bottle 地址 |
|---|---|---|---|---|
| 中科大 USTC | https://mirrors.ustc.edu.cn/brew.git |
https://mirrors.ustc.edu.cn/homebrew-core.git |
https://mirrors.ustc.edu.cn/homebrew-bottles/api |
https://mirrors.ustc.edu.cn/homebrew-bottles |
| 清华 TUNA | https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git |
https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git |
https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/api |
https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles |
| 阿里云 | https://mirrors.aliyun.com/homebrew/brew.git |
https://mirrors.aliyun.com/homebrew/homebrew-core.git |
不提供 | https://mirrors.aliyun.com/homebrew/homebrew-bottles |
从表格里能看出来,阿里云没有提供 API 镜像,所以在脚本里如果选了阿里云,HOMEBREW_API_DOMAIN 这项就得跳过,或者把 brew 的 API 模式关掉,强制走 git 仓库模式。这点不处理好的话,选阿里云后 brew update 一样会卡住。
4. 脚本完整实现与关键代码解读
4.1 基础框架:参数解析与函数划分
我用纯 bash 写这个脚本,因为 macOS 自带的 /bin/bash 是 3.2 版本,语法上不能用到 bash 4 的关联数组特性,所以全部用普通字符串拼接和 case 分支来写,保证开箱即用。
脚本的入口是这样的:
bash复制#!/bin/bash
# macos-homebrew-mirror.sh
# 用法: ./macos-homebrew-mirror.sh [ustc|tuna|aliyun|restore] [--force]
set -euo pipefail
MIRROR="${1:-ustc}"
MODE="${2:-normal}"
# 如果参数是 restore,则执行恢复官方源逻辑
if [[ "$MIRROR" == "restore" ]]; then
restore_official_source
exit 0
fi
这里用 set -euo pipefail 是我比较坚持的写法。-e 保证任何一条命令失败就立即退出,避免在错误状态下继续执行把配置写坏;-u 避免使用未定义变量导致静默出错;pipefail 确保管道中任何一环失败都会反映到退出码。尤其是这种要修改系统配置的脚本,安全问题怎么强调都不为过。
4.2 获取 Homebrew 安装路径
Homebrew 在 Intel Mac 上默认装在 /usr/local,在 Apple Silicon(M1/M2/M3)上装在 /opt/homebrew。很多脚本直接写死路径,这是不行的——万一你用的是 Apple Silicon 机器,写死 /usr/local 就会出现 brew: command not found 的诡异问题。
正确做法是动态获取:
bash复制get_brew_prefix() {
# 优先从 PATH 中解析 brew 命令的真实路径
local brew_path
brew_path="$(command -v brew || true)"
if [[ -n "$brew_path" ]]; then
# 如果 brew 是符号链接,则继续解析真实路径
brew_path="$(readlink -f "$brew_path" 2>/dev/null || echo "$brew_path")"
dirname "$(dirname "$brew_path")"
else
# 兜底判断常见安装位置
if [[ -d "/opt/homebrew" ]]; then
echo "/opt/homebrew"
elif [[ -d "/usr/local/Homebrew" ]]; then
echo "/usr/local"
else
echo "ERROR: 无法定位 Homebrew 安装目录,请确认 brew 已安装且在 PATH 中" >&2
exit 1
fi
fi
}
HOMEBREW_PREFIX="$(get_brew_prefix)"
readlink -f 在 macOS 上默认没有,所以我加了 || echo 兜底,这个方法不算完美,但实际用下来够用。
4.3 镜像源信息定义
由于 bash 3.2 不支持关联数组,我用了三个平行数组或者一个带分隔符的字符串数组来存国内镜像站信息:
bash复制setup_mirror_info() {
case "$MIRROR" in
ustc)
BREW_REMOTE="https://mirrors.ustc.edu.cn/brew.git"
CORE_REMOTE="https://mirrors.ustc.edu.cn/homebrew-core.git"
CASK_REMOTE="https://mirrors.ustc.edu.cn/homebrew-cask.git"
API_DOMAIN="https://mirrors.ustc.edu.cn/homebrew-bottles/api"
BOTTLE_DOMAIN="https://mirrors.ustc.edu.cn/homebrew-bottles"
;;
tuna)
BREW_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git"
CORE_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git"
CASK_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-cask.git"
API_DOMAIN="https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/api"
BOTTLE_DOMAIN="https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles"
;;
aliyun)
BREW_REMOTE="https://mirrors.aliyun.com/homebrew/brew.git"
CORE_REMOTE="https://mirrors.aliyun.com/homebrew/homebrew-core.git"
CASK_REMOTE=""
API_DOMAIN=""
BOTTLE_DOMAIN="https://mirrors.aliyun.com/homebrew/homebrew-bottles"
;;
*)
echo "未知的镜像源标识: $MIRROR" >&2
echo "可用选项: ustc, tuna, aliyun, restore" >&2
exit 1
;;
esac
}
这里中科大和清华都提供了 cask 仓库镜像,阿里云没有,所以 CASK_REMOTE 为空。脚本后面要针对空值做逻辑判断。
4.4 切换 git remote
Homebrew 的仓库是 git 仓库,因此换源的核心操作就是把 origin 远程地址替换成镜像地址。这里有一个细节:Homebrew 4.x 已经把 homebrew-core 和 homebrew-cask 仓库从本地 clone 改成了按需获取,所以某些新版本可能根本没有这两个仓库目录。我写脚本时用了防御式判断,目录存在才进去改 remote。
bash复制set_git_remote() {
local repo_dir="$1"
local remote_url="$2"
if [[ -z "$remote_url" ]]; then
echo "跳过 $repo_dir (镜像未提供该仓库)"
return 0
fi
if [[ ! -d "$repo_dir/.git" ]]; then
echo "跳过 $repo_dir (目录不存在或不是 git 仓库)"
return 0
fi
pushd "$repo_dir" >/dev/null
# 获取当前 origin 地址,若与目标一致则跳过
local current_remote
current_remote="$(git remote get-url origin 2>/dev/null || true)"
if [[ "$current_remote" == "$remote_url" ]]; then
echo "$repo_dir 已是最新镜像地址,无需修改"
else
git remote set-url origin "$remote_url"
echo "$repo_dir 远程地址从 $current_remote 切换为 $remote_url"
fi
popd >/dev/null
}
为什么用 git remote set-url 而不是重新 git clone?因为 Homebrew 仓库里还保存着本地状态、当前版本指针等,set-url 只改“往哪拉”而不动仓库内容,这样比删掉重来更安全,也不需要重新下载完整历史。
4.5 写环境变量到 shell 配置文件
这是全网脚本最容易翻车的地方。很多脚本简单粗暴地执行:
bash复制echo 'export HOMEBREW_BOTTLE_DOMAIN=...' >> ~/.zshrc
多跑几次,配置文件里就会出现好几行一模一样的 export,而且根本无法安全删除。我的做法是写一个通用的“环境变量配置函数”:如果目标文件里已经有该变量的定义,就用 sed 精准替换;如果没有,就在文件末尾追加。
bash复制set_env_var() {
local key="$1"
local value="$2"
local env_file="$3"
touch "$env_file"
if grep -q "^export $key=" "$env_file"; then
# 使用 sed 替换已有配置
sed -i '' "s|^export $key=.*|export $key=\"$value\"|" "$env_file"
echo "更新 $env_file 中 $key 为 $value"
else
# 追加新配置
echo "export $key=\"$value\"" >> "$env_file"
echo "追加 $key 到 $env_file"
fi
}
需要注意 sed 在 macOS 和 Linux 上的差异:macOS 的 sed 需要 -i '' 才能原地修改且不生成备份文件;Linux 的 GNU sed 只需要 -i。因为这是 Mac 脚本,所以用 sed -i ''。如果这个脚本要同时兼容 Linux,就需要用 sed -i.bak 再加删除备份文件的方式,但这里锁定 macOS 场景,就保持最简单的方式。
选择 ~/.zshrc 还是 ~/.bash_profile?macOS 从 Catalina 开始默认 shell 是 zsh,但很多用户 bash 和 zsh 混用。我在脚本里做了一个探测:如果 ~/.zshrc 存在就同时写入两个文件,否则只写 .bash_profile。每个终端环境都需要这些环境变量,因为 brew 命令行工具在任何 shell 下都可能被调用。
4.6 恢复官方源
这个功能经常被忽略,但实际价值极高。用镜像源用了半年,有一天公司换了网络,发现中科大镜像拉不动了,或者某个软件在镜像源里更新不及时,就需要切回官方源。恢复函数就是把我们写进去的配置全部改回来。
bash复制restore_official_source() {
echo "开始恢复 Homebrew 官方源..."
# 恢复环境变量(置空即可,HOMEBREW_BOTTLE_DOMAIN 为空时 brew 就走默认)
local env_file="$HOME/.zshrc"
if [[ -f "$env_file" ]]; then
sed -i '' '/^export HOMEBREW_API_DOMAIN=/d' "$env_file"
sed -i '' '/^export HOMEBREW_BOTTLE_DOMAIN=/d' "$env_file"
sed -i '' '/^export HOMEBREW_BREW_GIT_REMOTE=/d' "$env_file"
sed -i '' '/^export HOMEBREW_CORE_GIT_REMOTE=/d' "$env_file"
sed -i '' '/^export HOMEBREW_CASK_GIT_REMOTE=/d' "$env_file"
echo "已清除 $env_file 中的镜像源配置"
fi
# 恢复 Homebrew 主仓库
local prefix
prefix="$(get_brew_prefix)"
if [[ -d "$prefix" ]]; then
git -C "$prefix" remote set-url origin "https://github.com/Homebrew/brew.git"
echo "已恢复 brew 仓库官方远程地址"
fi
if [[ -d "$prefix/Library/Taps/homebrew/homebrew-core" ]]; then
git -C "$prefix/Library/Taps/homebrew/homebrew-core" remote set-url origin "https://github.com/Homebrew/homebrew-core.git"
echo "已恢复 homebrew-core 官方远程地址"
fi
if [[ -d "$prefix/Library/Taps/homebrew/homebrew-cask" ]]; then
git -C "$prefix/Library/Taps/homebrew/homebrew-cask" remote set-url origin "https://github.com/Homebrew/homebrew-cask.git"
echo "已恢复 homebrew-cask 官方远程地址"
fi
echo "恢复完成,请执行 source ~/.zshrc 或重新打开终端使配置生效"
}
注意恢复时不是把环境变量设置成官方地址,而是直接删除对应行。因为 Homebrew 在未设置 HOMEBREW_API_DOMAIN 和 HOMEBREW_BOTTLE_DOMAIN 时会自动走 GitHub/ghcr.io 默认值,删掉比设置成空字符串更干净,也不会残留无意义的变量。
5. 脚本的完整组装与使用说明
5.1 把以上函数串起来
写脚本和写文章一样,单有函数还不够,需要一个主流程把它们按正确的顺序串起来。我最开始写的时候先改 git remote 再写环境变量,结果 brew update 报错说 API 配置不对,后来发现顺序也有讲究——应该先写环境变量,再改 git remote,最后执行清理缓存和更新。
bash复制main() {
echo "===== macOS Homebrew 镜像源切换脚本 ====="
# 0. 前置检查
check_os_and_brew
# 1. 解析镜像源
setup_mirror_info
# 2. 写入环境变量
local env_file="$HOME/.zshrc"
if [[ ! -f "$env_file" ]]; then
env_file="$HOME/.bash_profile"
fi
echo ">> 写入环境变量到 $env_file"
set_env_var "HOMEBREW_API_DOMAIN" "$API_DOMAIN" "$env_file"
set_env_var "HOMEBREW_BOTTLE_DOMAIN" "$BOTTLE_DOMAIN" "$env_file"
set_env_var "HOMEBREW_BREW_GIT_REMOTE" "$BREW_REMOTE" "$env_file"
set_env_var "HOMEBREW_CORE_GIT_REMOTE" "$CORE_REMOTE" "$env_file"
if [[ -n "$CASK_REMOTE" ]]; then
set_env_var "HOMEBREW_CASK_GIT_REMOTE" "$CASK_REMOTE" "$env_file"
fi
# 3. 修改 git remote
local prefix
prefix="$(get_brew_prefix)"
echo ">> 修改 git 远程地址"
set_git_remote "$prefix" "$BREW_REMOTE"
set_git_remote "$prefix/Library/Taps/homebrew/homebrew-core" "$CORE_REMOTE"
set_git_remote "$prefix/Library/Taps/homebrew/homebrew-cask" "$CASK_REMOTE"
# 4. 清理缓存并更新
echo ">> 清理 Homebrew 缓存"
rm -rf "$(brew --cache)" 2>/dev/null || true
echo ">> 执行 brew update,首次可能需要几分钟"
export HOMEBREW_API_DOMAIN="$API_DOMAIN"
export HOMEBREW_BOTTLE_DOMAIN="$BOTTLE_DOMAIN"
brew update
# 5. 验证
echo ">> 验证配置"
brew config | grep -E "HOMEBREW_|HEAD" | head -20
echo "===== 完成 ====="
}
main "$@"
这里有一个容易踩的细节:脚本执行时只是在当前 shell 里 export 了变量,它只对本次执行的 brew update 生效。如果不开新终端,当前终端里的 brew 还是不会读 ~/.zshrc 里的新配置,因为 shell 不会自动重新加载配置文件。所以我建议脚本最后加一句提示,或者干脆在脚本内部重新 source 配置文件。
5.2 实际执行效果
我这里贴一下我在一台 Apple Silicon MacBook(/opt/homebrew,Homebrew 4.2.16)上的实际运行输出:
text复制===== macOS Homebrew 镜像源切换脚本 =====
>> 写入环境变量到 /Users/me/.zshrc
追加 HOMEBREW_API_DOMAIN 到 /Users/me/.zshrc
追加 HOMEBREW_BOTTLE_DOMAIN 到 /Users/me/.zshrc
追加 HOMEBREW_BREW_GIT_REMOTE 到 /Users/me/.zshrc
追加 HOMEBREW_CORE_GIT_REMOTE 到 /Users/me/.zshrc
追加 HOMEBREW_CASK_GIT_REMOTE 到 /Users/me/.zshrc
>> 修改 git 远程地址
/opt/homebrew 已是新镜像地址,无需修改
/opt/homebrew/Library/Taps/homebrew/homebrew-core 不存在,跳过
>> 清理 Homebrew 缓存
>> 执行 brew update,首次可能需要几分钟
Already up-to-date.
>> 验证配置
HOMEBREW_API_DOMAIN: https://mirrors.ustc.edu.cn/homebrew-bottles/api
HOMEBREW_BOTTLE_DOMAIN: https://mirrors.ustc.edu.cn/homebrew-bottles
HOMEBREW_BREW_GIT_REMOTE: https://mirrors.ustc.edu.cn/brew.git
HOMEBREW_CORE_GIT_REMOTE: https://mirrors.ustc.edu.cn/homebrew-core.git
HOMEBREW_CASK_GIT_REMOTE: https://mirrors.ustc.edu.cn/homebrew-cask.git
注意看,homebrew-core 这个目录在我这台机器上不存在,因为新版 Homebrew 不再默认 clone core 仓库,它改成通过 API 按需拉取。所以如果脚本里没有做目录判断,直接 cd $(brew --prefix)/Library/Taps/.../homebrew-core 就会失败。
5.3 脚本的权限与运行方式
写完脚本后,给它加上执行权限:
bash复制chmod +x macos-homebrew-mirror.sh
然后运行:
bash复制# 使用中科大源(默认)
./macos-homebrew-mirror.sh ustc
# 使用清华源
./macos-homebrew-mirror.sh tuna
# 使用阿里云源
./macos-homebrew-mirror.sh aliyun
# 恢复官方源
./macos-homebrew-mirror.sh restore
不建议直接用 sudo 运行这个脚本,Homebrew 本身要求用户对安装目录有读写权限,正常安装情况下 /opt/homebrew 和 /usr/local 都允许当前用户直接操作,用 sudo 反而可能把仓库文件归属改成 root,之后 brew 操作会频繁报权限错误。如果你确实遇到权限问题,先去修复 Homebrew 目录权限,而不是用 sudo 跑脚本。
6. 常见报错与排查实录
6.1 运行脚本后 brew 还是慢,为什么
这是最经典的问题——换完源之后 brew install 依然卡。我排查过不少次,原因通常是这几个:
环境变量没有生效。 你在终端里跑完脚本,立刻执行 brew install,但当前终端没有重新加载 .zshrc。解决方法是先执行 source ~/.zshrc 或者完全退出终端重新打开,然后再试。可以通过 echo $HOMEBREW_BOTTLE_DOMAIN 确认变量是否存在。
某个包走了非 bottle 安装。 不是所有 formula 都有预编译的 bottle,有些包因为依赖系统特定库或编译选项太复杂,镜像站没有生成对应版本,brew 会回退到源码编译,这时候下载地址又变成了 GitHub 上的 tarball,依然慢。这种情况其实跟镜像源无关,是包本身的问题。
DNS 缓存或代理冲突。 如果你开着系统代理或某些网络加速工具,环境变量里的 ALL_PROXY 会把 brew 的流量强行走代理,即使是访问国内镜像也一样。检查一下终端里 env | grep -i proxy,如果有代理变量,试着 unset http_proxy https_proxy all_proxy 再执行 brew。
6.2 提示 "This version of macOS is not supported on this platform" 怎么办
这个报错我遇到过几次,通常不是 Homebrew 本身的问题,而是你安装的某个公式的 bottle 版本与 macOS 大版本不匹配。比如你在 macOS Sonoma 上尝试安装一个只发布了 Ventura bottle 的软件,或者 Homebrew 认为你的系统版本太新/太老。
排查步骤很简单:先执行 sw_vers 看系统版本,再执行 brew config 看 Homebrew 检测到的 macOS 版本。如果两者不一致,通常是 PATH 里有多个 Xcode Command Line Tools 或者符号链接混乱。我遇到比较多的情况是系统刚从旧版本升级上来,Command Line Tools 没跟着更新,此时执行:
bash复制sudo rm -rf /Library/Developer/CommandLineTools
xcode-select --install
重新安装 Command Line Tools 后通常能解决。如果报错特定于某个 formula,可以试 brew install --force --build-from-source 软件名,强制源码编译,回避 bottle 平台的限制。
6.3 镜像源拉取失败:连接被拒绝或 404
镜像站偶尔会出问题,比如某个仓库同步失败、或者路径被调整。如果你 brew update 时报 fatal: unable to access 'https://mirrors.xxx/...',先用浏览器确认该地址能否访问,再用 curl 测试:
bash复制curl -I https://mirrors.ustc.edu.cn/brew.git
如果地址返回 404,大概率是镜像站更新了目录结构,这时候可以通过 brew config 查看当前配置的地址,然后手动切换到另一个可用镜像源。我的脚本已经考虑到了这一点,所以支持三个源自由切换——不行就换一家,别在一棵树上吊死。
6.4 执行脚本后 brew 提示 "HOMEBREW_API_DOMAIN is set but the formula API is disabled"
这个报错比较隐蔽,是因为 brew 检测到设置了 HOMEBREW_API_DOMAIN,但当前 Homebrew 版本可能在编译时被刻意关闭了 API 功能。通常发生在 Homebrew 4.0 以下版本,这些版本不支持通过 API 获取软件元数据。解决办法是不要设置 HOMEBREW_API_DOMAIN,回到传统的 git 仓库模式——在脚本里其实可以做一个版本判断,如果 brew --version 返回的主版本小于 4,则跳过 API 配置项。
bash复制check_brew_version() {
local version
version="$(brew --version | head -1 | awk '{print $2}')"
local major="${version%%.*}"
if [[ "$major" -lt 4 ]]; then
echo "检测到 Homebrew $version,不支持 API 模式,将跳过 HOMEBREW_API_DOMAIN 配置"
API_DOMAIN=""
fi
}
这个判断能避免老版本用户在换源之后出现各种莫名其妙的 API 错误。
6.5 常见问题速查表
| 症状 | 可能原因 | 解决方案 |
|---|---|---|
| brew update 卡住不动 | 镜像源未生效 / 代理拦截 | 检查 HOMEBREW_API_DOMAIN 环境变量;source ~/.zshrc 重载配置 |
| 下载 bottle 时 403/404 | 镜像站未同步该版本 | 用 brew install --build-from-source 编译安装,或切换镜像站 |
| Git remote 提示 "does not appear to be a git repository" | Homebrew 4.x 没有 clone core/cask 仓库 | 无需理会,该目录不存在是正常的 |
| 换源后某软件找不到 | API 元数据缓存过期 | brew update --force 强制刷新元数据 |
脚本写完执行报 -bash: ./xxx.sh: /bin/bash^M: bad interpreter |
Windows 保存的脚本带回车符 | sed -i '' 's/\r$//' xxx.sh 去行尾符 |
| 切回官方源后 brew 非常慢 | GitHub 连接不稳定 | 这是网络环境问题,可考虑配置稳定的网络,不要反复切换 |
6.6 一个容易忽略的坑:系统自带 Git 的证书问题
我遇到过几次比较隐蔽的问题:brew 运行时提示 SSL 证书验证失败,错误信息类似 fatal: unable to access ... SSL certificate problem: unable to get local issuer certificate。这种情况往往不是镜像源的问题,而是系统的 ca 证书库损坏或 Command Line Tools 未正确安装。
解决办法是重装证书:
bash复制brew install ca-certificates
或者干脆让 git 忽略证书验证(但我不建议,安全性太差,只在应急时用):
bash复制export GIT_SSL_NO_VERIFY=1
如果你在脚本中要处理这种网络异常场景,可以增加一个 --ssl-no-verify 选项,本质上就是帮用户设置这个环境变量,但要明确提示这是一个临时的、降低安全性的操作。
7. 脚本的扩展方向与进阶玩法
7.1 增加“源连通性检查”功能
我目前的脚本是让用户靠“能不能 update 成功”来判断源是否好用,但更优雅的做法是在切换之前就测试可用性。可以脚本开头先对候选镜像执行一次 curl 超时探测,自动挑选可用的源,不需要用户手动判断:
bash复制check_mirror_health() {
local url="$1"
local code
code="$(curl -L -o /dev/null --connect-timeout 5 --max-time 10 -s -w '%{http_code}' "$url")"
if [[ "$code" == "200" || "$code" == "301" || "$code" == "302" ]]; then
return 0
else
return 1
fi
}
我是用 HTTP 状态码来判断的——200 是正常,301/302 是重定向(很多镜像站根路径会重定向到首页),只要不是 4xx/5xx 都认为连通性 OK。
7.2 增加日志与可观测性
真实使用中,脚本一次跑完并不代表后续一切正常。我建议在脚本中加入日志记录功能,把每次切换的时间、源、执行结果写入 ~/.homebrew-mirror.log,这样哪天发现 brew 异常,可以回溯是不是这次切换引起的:
bash复制log() {
local timestamp
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
echo "[$timestamp] $*" >> "$HOME/.homebrew-mirror.log"
}
我还遇到过用户在群里求助,说自己跑了个“优化脚本”之后 Homebrew 完全坏了,结果一查日志发现脚本把环境变量写错成 HOMEBREW_BOTTLE_DOMAIN="https://.../homebrew-bottles "——多了一个空格,URL 解析失败。这种问题如果没有日志,排查起来真是如大海捞针。
7.3 自动化监控镜像源状态
镜像源的质量会波动,今天我们配置的中科大源很稳定,不代表一个月后依然稳定。我在生产环境的 Mac 上加了一个定期任务,每天凌晨跑一次检查脚本:
bash复制0 3 * * * /path/to/check_homebrew_mirror.sh >> /tmp/brew_mirror_check.log 2>&1
这个检查脚本做三件事:一是请求镜像源地址看是否返回 2xx/3xx;二是执行 brew update --auto-update 看是否能在 60 秒内完成;三是把验证结果写入日志。如果连续两次失败就自动切换到备用源——相当于给 brew 换源做了一个“故障转移”。
7.4 适配多用户场景
如果公司里有多台 Mac 需要统一配置,你可以把脚本部署到公司内部 GitLab,然后在新入职员工的机器上一键执行:
bash复制curl -fsSL http://your-gitlab.internal/raw/macos-homebrew-mirror.sh | bash -s ustc
注意,直接从网络 pipe 到 bash 执行本身有安全隐患,我建议先下载到本地审查一遍脚本内容再执行。这也是我在团队内部推广这个脚本时反复强调的一句话:生产环境运行的任何自动化脚本,都要确保你能看懂它的每一行。
8. 写在最后:这个脚本的边界与个人体会
我用这个脚本也半年多了,在 Intel Mac 和 Apple Silicon Mac 上都测试过,配合中科大和清华两个源轮换使用,brew install 的速度确实从“看运气”变成了“稳定秒下”。有一个经验特别值得分享:不要执着于某一个镜像源,多准备几个备用源总是对的。因为镜像站服务器也会维护、也会被刷爆、偶尔也会有同步延迟,把切换成本降到一行命令之后,“换源”这个动作就从痛苦的系统维护变成了一种日常习惯。
这个脚本的边界我也要说明白——它解决的是 Homebrew 这个包管理器在 macOS 上的网络访问问题,而不是所有“下载慢”问题的银弹。如果你要下载的是 GitHub 上的项目代码、Docker 镜像、Python 包、Node 依赖,它们各自有不同的加速方案,不能指望一个脚本通吃所有场景。
脚本本身并不复杂,核心就是“改 git remote + 写环境变量 + 清理缓存 + 验证”。但我觉得价值最大的是“思考过程”——当你把 Homebrew 的更新链路拆开,理解每个模块从哪里下载、为什么慢、有哪些镜像可以替代之后,你就不会再对着报错一头雾水了。这种排查问题的思路,比脚本本身值钱得多。
