github_proxy:一个开关接管 zsh、git 与 Homebrew 的 GitHub 加速
TL;DR:无代理、无 VPN 的国内网络下,
github_proxy on让 zsh 里的git clone/fetch、curl/wget,以及 Homebrew 升级时对 GitHub 的直连 全部改走镜像;github_proxy off一键恢复直连。核心依据是三个实测结论:brew 调用外部命令时会过滤非HOMEBREW_前缀的环境变量;HOMEBREW_ARTIFACT_DOMAIN在这版 brew 里只管 ghcr.io;真正能接管 brew 网络请求的是它的 shim(HOMEBREW_CURL/HOMEBREW_GIT)。实测同一个 GitHub 托管的 cask:直连 240 秒超时失败,走镜像 2 秒完成,且文件 SHA-256 与官方一致。
1. 前置条件与适用环境
| 项目 | 本机实测值 | 说明 |
|---|---|---|
| 系统 | macOS 27.2 / Apple Silicon | Intel 机器同样适用 |
| Shell | zsh 5.9 + Oh My Zsh | 依赖交互式 zsh 加载 ~/.zshrc |
| Homebrew | 7.0.7(/opt/homebrew) | 接管 Homebrew 需要 4.x 以上 |
| git / curl | git 2.56.0 / curl 8.7.1 | macOS 自带或 brew 安装均可 |
| 网络 | 国内直连,无代理无 VPN | hf-mirror、gh-proxy 可达 |
| 镜像 | https://gh-proxy.com/ | 可用 GITHUB_PROXY_MIRROR 替换 |
| 已存在的本机镜像 | USTC:API / bottles / brew.git / homebrew-core.git | 与本方案互补,不冲突 |
前置认知:镜像只能解决“能不能下、下得快不快“,不能改变下载内容——所有经镜像下载的文件仍由原工具按 SHA-256 校验,校验不过就重来,不存在被镜像掉包的风险。
适用边界:本方案面向“在终端里干活“的场景。
浏览器、VS Code、npm/pip/go、以及程序内自建 HTTP 客户端的下载(例如 focr)不经过 zsh 也不经过 brew,
函数无法接管——那类工具要用它自己的镜像参数(如 --manifest、PIP_INDEX_URL)。
> **需求场景描述**- Homebrew 下载 更新 (cask类型; Formula –build-form-source 类型; 第三方Tap 走github做仓库) brew update, brew upgrade, brew install –build-from-source
- Github url 安装 更新 拉取模型,例如
focrfocr pull - Github 代码仓库 git clone , git pull ,git push
2. 文件与路径一览
| 路径 | 作用 | 需要手动提供? |
|---|---|---|
~/.zsh/functions/github_proxy.zsh | 主脚本(约 590 行),由 ~/.zshrc 自动 source | ✅ 唯一需要你放的文件 |
~/.local/state/github-proxy/state | 开关状态文件,唯一权威来源(内容为 on / off) | ❌ 首次 on/off 时自动生成 |
~/.local/state/github-proxy/bin/brew-curl | 自动生成的 curl 包装器(镜像地址写死在文件内) | ❌ 加载脚本时自动生成 |
~/.local/state/github-proxy/bin/brew-git | 自动生成的 git 包装器 | ❌ 加载脚本时自动生成 |
~/.local/state/github-proxy/debug | 调试开关的标记文件(存在即记录) | ❌ 仅 debug on 时创建 |
~/.local/state/github-proxy/brew.log(含 .1) | 调试日志:包装器实际请求的地址,超 1 MB 自动轮转 | ❌ 仅调试时写入 |
结论:你只需要提供 1 个文件——主脚本(附录 A)。
其余 5 个都是运行时产物,删掉后下次开 shell 会自动重建;反过来,把它们拷贝给别人也没有意义(包装器里写死了镜像地址与绝对路径)。
附录 B 列出了自动生成文件的具体内容,方便先审阅再落地。
加载链路:github_proxy.zsh 放在哪个目录由你决定,关键是在 ~/.zshrc 里写出这个路径并 source 它——这就是全部的“安装动作“:
# ~/.zshrc —— 指向你自己存放该脚本的位置(本项目示例放在 ~/.zsh/functions/)
source ~/.zsh/functions/github_proxy.zsh若习惯把自定义函数集中管理,也可以整目录遍历加载(此时无需逐个写文件名):
if [ -d ~/.zsh/functions ]; then
for func in ~/.zsh/functions/*.zsh; do
source "$func"
done
fi状态目录则固定在 XDG_STATE_HOME(默认 ~/.local/state)下,属于“可重建的运行态“,删掉也能自动重建。
3. 功能
3.1 命令速查
github_proxy # 看状态
github_proxy on # 开启(默认状态)
github_proxy off # 关闭,全部直连
github_proxy toggle # 切换
github_proxy test # 实测镜像 vs 直连速度
github_proxy url <URL> # 只看改写结果,方便排查
github_proxy brew # 看 Homebrew 接管状态
github_proxy debug on # 记录包装器实际请求的地址(排查用)3.2 接管范围矩阵
| 场景 | 是否接管 | 走向 |
|---|---|---|
git clone / fetch / pull / ls-remote / submodule | ✅ | 走镜像 |
git push | ➖ 不动 | 仍走你自己的 SSH(见 4.2) |
curl / wget 中的 github 域名 | ✅ | 走镜像 |
| Homebrew 升级时的 cask 包 / formula 源码 / tap 定义 | ✅ | 走镜像 |
| Homebrew 的 API / bottles / 核心仓库 | ➖ 不动 | 你已有的 USTC 镜像 |
gh CLI | ❌ | 自带客户端,只能用 HTTPS_PROXY |
| npm / pip / go / VS Code / 浏览器 / focr | ❌ | 不经过 zsh,需各自配置 |
SSH 形式 git@github.com:… | ❌ | 保持直连(国内一般可用) |
3.3 三种状态语义
| 状态 | 含义 | 持久化 |
|---|---|---|
on | 走镜像(默认) | 写入 state 文件 |
off | 完全直连,且不残留任何环境变量 | 写入 state 文件 |
GITHUB_PROXY_FORCE=off zsh | 只影响这次会话的临时覆盖 | 不动 state 文件 |
4. 设计:三层接管
flowchart TD
A["你在 zsh 里敲命令"] --> B["git / curl / wget 函数包装层<br/>改写参数里的 URL"]
C["brew upgrade / fetch"] --> D["HOMEBREW_CURL_PATH<br/>HOMEBREW_GIT_PATH"]
D --> E["生成的包装器<br/>brew-curl / brew-git"]
B --> F["https://gh-proxy.com/https://github.com/…"]
E --> F
F --> G["镜像回源并返回<br/>原工具仍校验 SHA-256"]4.1 第一层:zsh 函数改写参数
curl / wget 被函数包裹,只改写以 http(s):// 开头的参数,其余参数(-o、-H、--data 等)原样透传:
curl() {
local -a args=()
local a
for a in "$@"; do
case "$a" in
http://*|https://*) args+=("$(_gp_url "$a")") ;;
*) args+=("$a") ;;
esac
done
command curl "${args[@]}"
}改写是双向可逆的:_gp_unmirror 会先剥掉已有的镜像前缀再按当前开关决定,所以从浏览器复制来的 gh-proxy.com/https://github.com/… 在关闭状态下会自动还原成直连地址。
4.2 第二层:借 git 自己的 URL 重写规则
git 函数不直接改参数,而是注入 git 原生的重写配置——这样仓库里记录的 remote 地址仍然是原始 GitHub 地址,关掉开关就自动恢复直连,不会把镜像地址固化进 .git/config:
git() {
...
local -a cfg=()
for h in github.com raw.githubusercontent.com codeload.github.com \
gist.github.com objects.githubusercontent.com; do
cfg+=(-c "url.${m}https://${h}/.insteadOf=https://${h}/")
done
command git "${cfg[@]}" "$@"
}git push 不做任何注入,保持原样——本机全局配置里本来就有 pushInsteadOf = https://github.com/(推送改走 SSH),所以写操作永远不受影响。
唯一需要补救的是 git clone:实测 git 会把实际使用的地址写进 .git/config。因此 clone 结束后脚本会把 remote 还原成原始地址,子模块一并处理:
if (( rc == 0 )); then
case "$sub" in
clone) _gp_clone_target_dir "$@" && _gp_fix_remotes "$target" ;;
submodule) _gp_fix_remotes "$PWD" ;;
esac
fi4.3 第三层:Homebrew 的 shim 才是接管点
brew 是 Ruby 程序,它发出的请求不经过 zsh。但它调用外部命令时会先经过自己的 shim,而 shim 会执行 $HOMEBREW_CURL / $HOMEBREW_GIT:
# /opt/homebrew/Library/Homebrew/shims/shared/curl(节选)
try_exec_non_system "${HOMEBREW_CURL:-curl}" "$@"
safe_exec "/usr/bin/curl" "$@"而 brew.sh 的 setup_curl() / setup_git() 会优先采纳环境变量:
elif [[ -n "${HOMEBREW_CURL_PATH}" ]]
then
HOMEBREW_CURL="${HOMEBREW_CURL_PATH}"于是 github_proxy on 只要生成两个包装器脚本并导出两个变量,brew 的全部 curl/git 调用就都过我们的手了。验证方式极简——让 brew 自己告诉我们它用的是谁:
$ HOMEBREW_CURL_PATH=/path/to/wrapper brew config | grep '^Curl:'
Curl: 8.7.1 => /path/to/wrapper4.4 三个反直觉的实测结论
① brew 会过滤非 HOMEBREW_ 前缀的环境变量。 用一个只记录参数的探针包装器挂在 HOMEBREW_CURL_PATH 上,同时导出 GITHUB_PROXY_MIRROR 和 GITHUB_PROXY_DEBUG,包装器收到的是空值:
MIRROR_ENV=[] DEBUG_ENV=[]结论:镜像地址不能靠环境变量传给包装器,必须在生成包装器时写死进文件。
② HOMEBREW_ARTIFACT_DOMAIN 在这版 brew 里只管 ghcr.io。 翻源码可以看到它只对 GitHub Packages 域名生效:
# Library/Homebrew/download_strategy/curl_download_strategy.rb
u.sub(%r{^https?://#{GitHubPackages::URL_DOMAIN}/}o, "#{domain}/")
# github_packages.rb: URL_DOMAIN = "ghcr.io"所以网上那套“设置 HOMEBREW_ARTIFACT_DOMAIN 就能给所有下载加速“的说法,在 7.x 上对 github.com 无效。
③ 导出的开关变量会泄漏,导致“关了却还是开着“。 早期版本把 GITHUB_PROXY 导出给子进程判断,结果它被子进程继承,新终端里读到的仍是 1,状态文件写着 off 却依然走镜像。修法是让状态文件成为唯一权威,GITHUB_PROXY 只做本 shell 内镜像变量且不导出;需要临时覆盖时用语义明确的 GITHUB_PROXY_FORCE。
④ 有些请求的命令行里根本没有 URL。 brew update 更新第三方 tap 时执行的是 git -C <tap目录> fetch --force origin——地址来自仓库 .git/config 里的 remote.origin.url。只改写命令行参数的包装器对这类请求完全无效,于是 20 个 tap 各自裸连 github.com,各卡满 75 秒连接超时。修法是让包装器额外注入 git 原生的重写规则:
cfg=()
for h in github.com raw.githubusercontent.com codeload.github.com gist.github.com; do
cfg+=(-c "url.${MIRROR}https://${h}/.insteadOf=https://${h}/")
done
# …改写命令行参数后一并传入
exec "$REAL" "${cfg[@]}" "${args[@]}"验证方式是用 GIT_TRACE=1 看真实连接,修复后变成 git-remote-https origin https://gh-proxy.com/https://github.com/<user>/homebrew-tap;同一台机器上 brew update 从「20 个 tap 全部 75 秒超时」变成 14 秒完成、0 失败。
5. 关键实现片段
生成的 curl 包装器(brew-curl,由主脚本按当前镜像生成):
#!/bin/bash
# 由 github_proxy.zsh 自动生成,请勿手改
MIRROR="https://gh-proxy.com/"
REAL="/usr/bin/curl"
args=()
for a in "$@"; do
case "$a" in
https://github.com/*|https://raw.githubusercontent.com/*|https://codeload.github.com/*|https://gist.github.com/*|https://objects.githubusercontent.com/*)
args+=("${MIRROR}${a}") ;;
*) args+=("$a") ;;
esac
done
exec "$REAL" "${args[@]}"几个刻意的设计选择:
- 只改写白名单域名,
https://itsycal.s3.amazonaws.com/…这类非 GitHub 地址原样直连,避免把镜像当成通用代理而拖垮无关下载。 REAL写死为真实 curl 路径(默认/usr/bin/curl,设了HOMEBREW_FORCE_BREWED_CURL则用 brew 的 curl),不通过 PATH 查找,杜绝包装器递归调用自己。brew-git对push直接exec原样放行,写操作绝不经过镜像。brew-git额外注入-c url.*.insteadOf:brew 更新第三方 tap 用的是git fetch origin,命令行里没有 URL,只改写参数的写法会漏掉这类请求(见 4.4 ④)。- off 时还原而非清空:如果用户自己设过
HOMEBREW_CURL_PATH,脚本先记下原值,关闭时完整还回去。
调试日志的埋点也很克制——包装器每次只多一次文件存在性判断,github_proxy debug on 时才追加写入,日常零开销:
if [ -e "$MARKER" ]; then
{ printf '== %s\n' "$(date +%T)"; for a in "${args[@]}"; do printf ' %s\n' "$a"; done; } >> "$LOG" 2>/dev/null
fi6. 测试与验证
6.1 探针:先看 brew 到底请求了什么
把只记录、不改写的探针挂在 HOMEBREW_CURL_PATH 上跑一次 brew fetch --build-from-source,抓到的真实地址正是 USTC 镜像覆盖不到的部分:
https://raw.githubusercontent.com/Homebrew/homebrew-core/3a68ac0…/Formula/d/dust.rb
https://codeload.github.com/bootandy/dust/tar.gz/refs/tags/v1.2.6
https://github.com/bootandy/dust/archive/refs/tags/v1.2.6.tar.gz这解释了为什么配了 USTC 镜像,升级时依然会卡——元数据和 bottle 走了镜像,formula 定义、源码包、cask 的 app 包仍然是直连。
6.2 速度对比
| 场景 | 直连 | 走镜像 |
|---|---|---|
brew fetch --cask hiddenbar(约 2 MB,GitHub Releases) | 240 s 超时失败 | 2.0 s |
brew fetch --cask maccy(约 5 MB,GitHub Releases) | 未单独计时 | 2.2 s |
brew fetch --build-from-source jq(源码包在 GitHub) | 单次 >160 s 仍未完成 | — |
| 10 MB 单文件三次采样 | 158 / 11 / 45 KB/s | 24 / 51 / 44 KB/s |
必须诚实地说:镜像不是恒定更快。上表最后一行显示两者都在抖动区间里,本机也测到过镜像 9.4 MB/s 的高峰与几十 KB/s 的低谷。镜像的主要价值是可达性兜底——直连会出现彻底超时(000)或长时间挂住,镜像通常能救回;具体该开还是该关,用 github_proxy test 当场判断。
6.3 内容完整性
经镜像下载的文件全部与原工具校验值一致:
| 文件 | 校验结果 |
|---|---|
tokenizer.json(9.5 MB,GitHub Releases) | SHA-256 与官方清单一致 |
Hidden-Bar-v1.11.1-macos.zip | 与 cask 的 sha256 一致 |
Maccy.app.zip | 与 cask 的 sha256 一致 |
6.4 on/off 双状态校验
用真正走 source ~/.zshrc 的全新交互式终端逐项验证:
| 检查项 | ON | OFF |
|---|---|---|
HOMEBREW_CURL_PATH | 指向 brew-curl | 未设置 |
brew config 的 Curl 行 | => …/bin/brew-curl | => /usr/bin/curl |
git ls-remote github.com | 正常(走镜像) | 直连,可能超时(网络本身抖动) |
curl 取 raw.githubusercontent 文件 | 200 | 200 |
brew info(USTC API) | 正常 | 正常 |
| GitHub 托管的 cask 下载 | 正常 | 依赖直连状态 |
git push | 走 SSH,不受影响 | 走 SSH,不受影响 |
另外专门构造了“脏环境“测试:故意 export GITHUB_PROXY=1 并注入 HOMEBREW_CURL_PATH,在状态文件为 off 时启动新终端——结果正确关闭(HOMEBREW_CURL_PATH 被清掉、brew 回到 /usr/bin/curl)。这正是 4.4 ③ 那个缺陷的回归验证。
6.5 踩坑记录
| 现象 | 根因 | 处置 |
|---|---|---|
改了 HOMEBREW_ARTIFACT_DOMAIN 对 github 无效 | 该变量只管 ghcr.io | 改用 shim 接管 |
| 包装器拿不到镜像地址 | brew 过滤非 HOMEBREW_ 变量 | 生成时写死进包装器 |
| clone 出来的仓库 push 失败 | git 把镜像地址写进了 remote | clone 后自动还原 + 子模块同步 |
off 后新终端仍走镜像 | 导出的状态变量被继承 | 状态文件唯一权威、变量不导出 |
brew fetch --build-from-source 仍很慢 | 该路径会先拉 formula 的 .rb 与源码包 | 这两处现在也在白名单里 |
7. 局限与边界
- 只对交互式 zsh 生效;
command git、绝对路径调用、脚本里的#!/bin/sh不经过函数。 ghCLI 走自己的 HTTP 客户端,URL 改写对它无效,只能配HTTPS_PROXY。- SSH 形式地址保持直连;若 SSH 也不通,可在
~/.ssh/config里改用ssh.github.com:443。 - 镜像可达性取决于第三方服务,
gh-proxy.com之外的其他候选(ghfast.top、ghproxy.net、hk.gh-proxy.com)在本机实测均已失效或极慢,所以脚本保留了“主镜像 + HF 镜像兜底“的顺序,并允许用GITHUB_PROXY_MIRROR随时替换。 - brew 下载的大件(如 cask 里的几百 MB 应用)仍会受镜像带宽限制;此时
brew upgrade建议放在tmux里跑。
8. 速查与维护
# 换镜像
export GITHUB_PROXY_MIRROR="https://你的镜像/"
github_proxy on # 重新生成包装器并生效
# 只关 Homebrew 接管,保留 git/curl 接管
export GITHUB_PROXY_BREW_ON=0
# 排查:看包装器实际请求了哪些地址
github_proxy debug on
brew fetch --cask maccy
tail -n 20 ~/.local/state/github-proxy/brew.log
# 体检
github_proxy status
github_proxy brew
github_proxy test维护要点:状态目录整体可删(下次开 shell 自动重建);off 若发现残留,先 github_proxy off 再开新终端即可,因为状态文件是唯一权威。
9. 移植到别人的机器
9.1 前置检查
zsh --version # 需要 zsh(macOS 自带)
ls ~/.zshrc # 交互式 shell 的入口配置第一步:决定脚本放哪。 任意目录都行,下文统一以 ~/.zsh/functions/ 举例。
第二步:在 ~/.zshrc 里写出这个路径。 这是唯一必须手工添加的一行,路径换成你实际存放的位置:
source ~/.zsh/functions/github_proxy.zsh如果同时管理多个自定义函数,可以改成整目录遍历(新增脚本就不用再改 .zshrc):
if [ -d ~/.zsh/functions ]; then
for func in ~/.zsh/functions/*.zsh; do
source "$func"
done
fi改完后可以用这一行确认路径确实被引用了:
grep -n "github_proxy\|zsh/functions" ~/.zshrc要求:必须是交互式 zsh(~/.zshrc 只被交互式 shell 加载)。bash、fish、脚本里的 #!/bin/sh 都不会生效。
9.2 放置脚本
方式一(推荐):下载到你选定的目录(下面以 ~/.zsh/functions/ 为例,必须与 9.1 里写进 .zshrc 的路径一致)
mkdir -p ~/.zsh/functions
curl -fLo ~/.zsh/functions/github_proxy.zsh \
https://suchaharcan.github.io/03Share_blog/github-proxy-zsh-brew/github_proxy.zsh方式二:把文末 附录 A 的源码整段复制进你那个路径下的 github_proxy.zsh。
只需要这一个文件。第一次被 source 时,脚本会自己在状态目录里创建运行时文件,各文件的生成时机如下:
| 运行时会生成 | 生成时机 | 内容 |
|---|---|---|
bin/brew-curl、bin/brew-git | 每次加载脚本 / github_proxy on | 镜像地址写死在内的包装器(附录 B) |
state | 第一次执行 on 或 off | on / off,默认开启时不写文件 |
debug | github_proxy debug on | 空标记文件 |
brew.log(+ .1) | 调试开启且 brew 真正调用包装器时 | 实际请求的地址,超 1 MB 自动轮转 |
所以不要把 bin/brew-curl 之类拷给别人用——里面的镜像地址和 REAL 路径是按本机写死的,交给别人反而失效;让他们从主脚本自动生成即可。
9.3 生效与验收
exec zsh # 或直接开一个新终端
github_proxy status # 期望:开启(走镜像)
github_proxy brew # 有 Homebrew 时期望:curl 已接管 / git 已接管逐项自测清单:
| 自测命令 | 期望结果 |
|---|---|
github_proxy url https://github.com/a/b | 输出带镜像前缀的地址 |
git ls-remote https://github.com/octocat/Hello-World.git HEAD | 能拿到 commit hash |
github_proxy test | 打印镜像/直连两组速度 |
brew config | grep '^Curl:' | ON 时指向 …/github-proxy/bin/brew-curl,OFF 时是 /usr/bin/curl |
brew fetch --cask maccy | 日志(github_proxy debug on)里出现镜像地址 |
git push(任意自己的仓库) | 仍走 SSH,与装之前一致 |
9.4 按需调整
| 变量 | 默认 | 作用 |
|---|---|---|
GITHUB_PROXY_MIRROR | https://gh-proxy.com/ | 换镜像(末尾斜杠必须保留) |
GITHUB_PROXY_BREW_ON | 1 | 设为 0 只关 Homebrew 接管,保留 git/curl 接管 |
GITHUB_PROXY_FORCE | 未设置 | on / off 临时覆盖一次会话,不改状态文件 |
GITHUB_PROXY_HOSTS | 源码内 7 个域名 | 要增删改写的域名,改这一处即可 |
路径与平台差异:
- 状态目录遵循 XDG:
${XDG_STATE_HOME:-$HOME/.local/state}/github-proxy/,想挪位置改GITHUB_PROXY_STATE一行。 - brew 位置自动探测:Apple Silicon
/opt/homebrew、Intel Mac/usr/local、Linuxbrew/home/linuxbrew/.linuxbrew,也可用HOMEBREW_PREFIX显式指定。 - 机器上没有 brew 时,Homebrew 那部分会静默跳过,只保留 zsh 层的 git/curl 接管——Linux 用户同样可用。
9.5 卸载
github_proxy off # 先关闭,撤掉导出的 HOMEBREW_* 变量
rm ~/.zsh/functions/github_proxy.zsh # 删脚本(换成你实际的存放路径)
rm -rf ~/.local/state/github-proxy # 删状态与自动生成的包装器(可随时重建)
exec zsh最后把 ~/.zshrc 里那行 source …/github_proxy.zsh 一并删掉(用整目录遍历的则跳过这步)。
附录 A:完整源码
与本文同步的版本共 591 行,文件名
github_proxy.zsh。 直接下载:github_proxy.zsh
#!/usr/bin/env zsh
# ============================================================================
# github_proxy.zsh —— GitHub 国内加速开关(zsh 交互式环境)
#
# 作用:把在 zsh 里发出的 github 相关请求自动改写到国内加速镜像;
# 关闭后立刻恢复直连。默认「开启」。
#
# 用法(github_proxy help 可随时查看):
# github_proxy # 看状态
# github_proxy on # 开启(默认状态)
# github_proxy off # 关闭,全部直连
# github_proxy toggle # 切换
# github_proxy test # 实测镜像 vs 直连速度
# github_proxy url <URL> # 只看改写结果,方便排查
# github_proxy brew # 看 Homebrew 接管状态
# github_proxy debug on # 记录包装器实际请求的地址(排查用)
#
# 覆盖范围(只在交互式 zsh 里生效,且不能用 `command xxx` / 绝对路径调用):
# ✅ git clone / fetch / pull / ls-remote / submodule / archive
# ✅ git push —— 完全不受影响:你自己的 pushInsteadOf 规则会把 https 推送
# 改写成 SSH,镜像只做只读加速,绝不会碰到写操作
# ✅ curl / wget 参数里出现的 github.com、raw.githubusercontent.com、
# codeload.github.com、gist.github.com、objects.githubusercontent.com
# ✅ Homebrew —— 用 HOMEBREW_CURL_PATH / HOMEBREW_GIT_PATH 指向本脚本生成的
# 包装器来接管。USTC 镜像只管 API / bottles / 两个核心仓库;brew 升级时对
# github.com、codeload.github.com、raw.githubusercontent.com 的直连
# (cask 的 app 包、formula 的源码包、tap 的 formula 定义)由这里加速
# ❌ 程序自身发起的其它请求:npm / pip / go、VS Code、浏览器、focr 等
# 这些不经过 zsh 也不经过 brew,函数无法接管(focr 用 --manifest 方案解决)
# ❌ gh CLI:走 api.github.com 且自带客户端,只能用 HTTPS_PROXY
# ❌ SSH 形式(git@github.com:…):保持直连,国内一般可用;
# 若不通,可在 ~/.ssh/config 里改用 ssh.github.com:443
#
# 依赖:git / curl(macOS 自带或 Homebrew 安装均可)
# 自定义镜像:export GITHUB_PROXY_MIRROR="https://你的镜像/"
# 只想关掉 Homebrew 接管(保留 git/curl 接管):export GITHUB_PROXY_BREW_ON=0
# ============================================================================
# ---------------------------------------------------------------------------
# 配置区(可用环境变量覆盖)
# ---------------------------------------------------------------------------
: ${GITHUB_PROXY_MIRROR:=https://gh-proxy.com/} # 末尾斜杠必须有
typeset -g GITHUB_PROXY_MIRROR
# 命中这些域名就改写
typeset -ga GITHUB_PROXY_HOSTS=(
github.com
www.github.com
raw.githubusercontent.com
codeload.github.com
gist.github.com
objects.githubusercontent.com
api.github.com
)
# 开关状态持久化位置(默认开启,只有显式 off 才写文件)
typeset -g GITHUB_PROXY_STATE="${XDG_STATE_HOME:-$HOME/.local/state}/github-proxy/state"
# ---- Homebrew 接管(curl / git 包装器由本脚本生成,固定在状态目录里)----
typeset -g GITHUB_PROXY_STATE_DIR="${GITHUB_PROXY_STATE:h}"
typeset -g GITHUB_PROXY_BIN="${GITHUB_PROXY_STATE_DIR}/bin"
typeset -g GITHUB_PROXY_BREW_CURL="${GITHUB_PROXY_BIN}/brew-curl"
typeset -g GITHUB_PROXY_BREW_GIT="${GITHUB_PROXY_BIN}/brew-git"
typeset -g GITHUB_PROXY_DEBUG_MARKER="${GITHUB_PROXY_STATE_DIR}/debug"
typeset -g GITHUB_PROXY_BREW_LOG="${GITHUB_PROXY_STATE_DIR}/brew.log"
# brew 位置:优先用环境变量,缺失时按常见安装路径自动探测
# (Apple Silicon: /opt/homebrew;Intel Mac: /usr/local;Linuxbrew: ~/.linuxbrew)
typeset -g GITHUB_PROXY_PREFIX="${HOMEBREW_PREFIX:-}"
if [[ -z "$GITHUB_PROXY_PREFIX" ]]; then
typeset _gp_p
for _gp_p in /opt/homebrew /usr/local /home/linuxbrew/.linuxbrew "$HOME/.linuxbrew"; do
if [[ -x "$_gp_p/bin/brew" ]]; then GITHUB_PROXY_PREFIX="$_gp_p"; break; fi
done
: ${GITHUB_PROXY_PREFIX:=/opt/homebrew}
unset _gp_p
fi
typeset -g GITHUB_PROXY_BREW_ON="${GITHUB_PROXY_BREW_ON:-1}" # 0 = 不接管 Homebrew
# 被我们改写之前的值,off 时原样还回去
typeset -g GITHUB_PROXY_PREV_CURL_PATH GITHUB_PROXY_PREV_GIT_PATH
typeset -gi GITHUB_PROXY_PREV_CURL_SET=0 GITHUB_PROXY_PREV_GIT_SET=0
# ---------------------------------------------------------------------------
# 内部:状态读写
# ---------------------------------------------------------------------------
_gp_state_read() {
local v=""
[[ -r "$GITHUB_PROXY_STATE" ]] && v="$(<"$GITHUB_PROXY_STATE" 2>/dev/null)"
case "$v" in
off|0|false|no) print -r -- off ;;
on|1|true|yes) print -r -- on ;;
*) print -r -- on ;; # 默认开启
esac
}
_gp_enabled() {
# 状态文件是唯一权威。GITHUB_PROXY 只作为本 shell 内的镜像变量、且不导出,
# 否则它会被子进程/其它程序继承,出现「文件写着 off 但新终端仍是 on」的假象。
# 需要临时覆盖时用 GITHUB_PROXY_FORCE=on|off(显式、不会被误继承)。
case "${GITHUB_PROXY_FORCE-}" in
on|1|true|yes) return 0 ;;
off|0|false|no) return 1 ;;
esac
[[ "$(_gp_state_read)" == on ]]
}
_gp_set() {
local want="$1" dir="${GITHUB_PROXY_STATE:h}"
[[ -d "$dir" ]] || mkdir -p "$dir" 2>/dev/null
[[ -w "$dir" ]] && print -r -- "$want" >| "$GITHUB_PROXY_STATE" 2>/dev/null
export -n GITHUB_PROXY 2>/dev/null # 不导出:避免泄漏给子进程
if [[ "$want" == on ]]; then
GITHUB_PROXY=1
_gp_brew_enable
else
GITHUB_PROXY=0
_gp_brew_disable
fi
}
# ---------------------------------------------------------------------------
# Homebrew 接管
# brew 调 curl/git 时会把非 HOMEBREW_ 前缀的环境变量过滤掉(实测
# GITHUB_PROXY_MIRROR 传不进包装器),所以镜像地址必须写死在包装器脚本里,
# 每次开启时重新生成一次。
# ---------------------------------------------------------------------------
_gp_brew_available() {
[[ "$GITHUB_PROXY_BREW_ON" == 0 ]] && return 1
[[ -x "${GITHUB_PROXY_PREFIX}/bin/brew" ]] || return 1
return 0
}
_gp_write_brew_wrappers() {
local dir="$GITHUB_PROXY_BIN" tpl
[[ -d "$dir" ]] || mkdir -p "$dir" 2>/dev/null
[[ -d "$dir" ]] || return 1
# 还原成 brew 原本会用的真实程序
local real_curl="${GITHUB_PROXY_PREFIX}/opt/curl/bin/curl"
if [[ -z "${HOMEBREW_FORCE_BREWED_CURL-}" || ! -x "$real_curl" ]]; then
real_curl="/usr/bin/curl"
fi
local real_git="${GITHUB_PROXY_PREFIX}/bin/git"
[[ -x "$real_git" ]] || real_git="/usr/bin/git"
# ---- curl 包装器:把 github 域名改写到镜像,其余原样放行 ----
tpl=$(cat <<'CURL_TPL'
#!/bin/bash
# 由 github_proxy.zsh 自动生成,请勿手改
MIRROR="__MIRROR__"
REAL="__REAL__"
MARKER="__MARKER__"
LOG="__LOG__"
args=()
for a in "$@"; do
case "$a" in
https://github.com/*|https://raw.githubusercontent.com/*|https://codeload.github.com/*|https://gist.github.com/*|https://objects.githubusercontent.com/*)
args+=("${MIRROR}${a}") ;;
*) args+=("$a") ;;
esac
done
if [ -e "$MARKER" ]; then
if [ -f "$LOG" ] && [ "$(wc -c < "$LOG" 2>/dev/null || echo 0)" -gt 1048576 ]; then mv -f "$LOG" "$LOG.1" 2>/dev/null; fi
{ printf '== %s\n' "$(date +%T)"; for a in "${args[@]}"; do printf ' %s\n' "$a"; done; } >> "$LOG" 2>/dev/null
fi
exec "$REAL" "${args[@]}"
CURL_TPL
)
tpl="${tpl//__MIRROR__/$GITHUB_PROXY_MIRROR}"
tpl="${tpl//__REAL__/$real_curl}"
tpl="${tpl//__MARKER__/$GITHUB_PROXY_DEBUG_MARKER}"
tpl="${tpl//__LOG__/$GITHUB_PROXY_BREW_LOG}"
print -r -- "$tpl" >| "$GITHUB_PROXY_BREW_CURL" 2>/dev/null
# ---- git 包装器:只读操作走镜像,push 原样放行 ----
# 关键点:brew update 更新第三方 tap 时执行的是
# git -C <tap> fetch --force origin
# 命令行里没有 URL,地址来自仓库 .git/config 里的 remote.origin.url。
# 因此除了改写命令行参数,还必须注入 git 原生的 insteadOf 重写规则,
# 否则这类请求会绕过镜像直接裸连 github.com。
tpl=$(cat <<'GIT_TPL'
#!/bin/bash
# 由 github_proxy.zsh 自动生成,请勿手改
MIRROR="__MIRROR__"
REAL="__REAL__"
MARKER="__MARKER__"
LOG="__LOG__"
sub=""
for a in "$@"; do
case "$a" in -*) continue ;; *) sub="$a"; break ;; esac
done
if [ "$sub" = "push" ]; then
if [ -e "$MARKER" ]; then
if [ -f "$LOG" ] && [ "$(wc -c < "$LOG" 2>/dev/null || echo 0)" -gt 1048576 ]; then mv -f "$LOG" "$LOG.1" 2>/dev/null; fi
{ printf '== %s (push, 原样放行)\n' "$(date +%T)"; for a in "$@"; do printf ' %s\n' "$a"; done; } >> "$LOG" 2>/dev/null
fi
exec "$REAL" "$@"
fi
# git 原生重写规则:覆盖 remote.origin.url 这类"写在配置里"的地址
cfg=()
for h in github.com raw.githubusercontent.com codeload.github.com gist.github.com; do
cfg+=(-c "url.${MIRROR}https://${h}/.insteadOf=https://${h}/")
done
args=()
for a in "$@"; do
case "$a" in
https://github.com/*|https://raw.githubusercontent.com/*|https://codeload.github.com/*|git@github.com:*)
args+=("${MIRROR}${a}") ;;
*) args+=("$a") ;;
esac
done
if [ -e "$MARKER" ]; then
if [ -f "$LOG" ] && [ "$(wc -c < "$LOG" 2>/dev/null || echo 0)" -gt 1048576 ]; then mv -f "$LOG" "$LOG.1" 2>/dev/null; fi
{ printf '== %s\n' "$(date +%T)"; for a in "${args[@]}"; do printf ' %s\n' "$a"; done; } >> "$LOG" 2>/dev/null
fi
exec "$REAL" "${cfg[@]}" "${args[@]}"
GIT_TPL
)
tpl="${tpl//__MIRROR__/$GITHUB_PROXY_MIRROR}"
tpl="${tpl//__REAL__/$real_git}"
tpl="${tpl//__MARKER__/$GITHUB_PROXY_DEBUG_MARKER}"
tpl="${tpl//__LOG__/$GITHUB_PROXY_BREW_LOG}"
print -r -- "$tpl" >| "$GITHUB_PROXY_BREW_GIT" 2>/dev/null
chmod 755 "$GITHUB_PROXY_BREW_CURL" "$GITHUB_PROXY_BREW_GIT" 2>/dev/null
[[ -x "$GITHUB_PROXY_BREW_CURL" && -x "$GITHUB_PROXY_BREW_GIT" ]]
}
_gp_brew_enable() {
_gp_brew_available || return 0
_gp_write_brew_wrappers || return 1
if (( ! GITHUB_PROXY_PREV_CURL_SET )) &&
[[ -n "${HOMEBREW_CURL_PATH-}" && "${HOMEBREW_CURL_PATH}" != "$GITHUB_PROXY_BREW_CURL" ]]; then
GITHUB_PROXY_PREV_CURL_PATH="${HOMEBREW_CURL_PATH}"
GITHUB_PROXY_PREV_CURL_SET=1
fi
export HOMEBREW_CURL_PATH="$GITHUB_PROXY_BREW_CURL"
if (( ! GITHUB_PROXY_PREV_GIT_SET )) &&
[[ -n "${HOMEBREW_GIT_PATH-}" && "${HOMEBREW_GIT_PATH}" != "$GITHUB_PROXY_BREW_GIT" ]]; then
GITHUB_PROXY_PREV_GIT_PATH="${HOMEBREW_GIT_PATH}"
GITHUB_PROXY_PREV_GIT_SET=1
fi
export HOMEBREW_GIT_PATH="$GITHUB_PROXY_BREW_GIT"
}
_gp_brew_disable() {
if [[ "${HOMEBREW_CURL_PATH-}" == "$GITHUB_PROXY_BREW_CURL" ]]; then
if (( GITHUB_PROXY_PREV_CURL_SET )) &&
[[ "${GITHUB_PROXY_PREV_CURL_PATH-}" != "$GITHUB_PROXY_BREW_CURL" ]]; then
export HOMEBREW_CURL_PATH="${GITHUB_PROXY_PREV_CURL_PATH}"
else
unset HOMEBREW_CURL_PATH
fi
fi
if [[ "${HOMEBREW_GIT_PATH-}" == "$GITHUB_PROXY_BREW_GIT" ]]; then
if (( GITHUB_PROXY_PREV_GIT_SET )) &&
[[ "${GITHUB_PROXY_PREV_GIT_PATH-}" != "$GITHUB_PROXY_BREW_GIT" ]]; then
export HOMEBREW_GIT_PATH="${GITHUB_PROXY_PREV_GIT_PATH}"
else
unset HOMEBREW_GIT_PATH
fi
fi
}
_gp_brew_active() {
[[ "${HOMEBREW_CURL_PATH-}" == "$GITHUB_PROXY_BREW_CURL" ||
"${HOMEBREW_GIT_PATH-}" == "$GITHUB_PROXY_BREW_GIT" ]]
}
# ---------------------------------------------------------------------------
# 内部:URL 改写
# ---------------------------------------------------------------------------
_gp_is_github_url() {
local u="$1" rest host
[[ "$u" == http://* || "$u" == https://* ]] || return 1
rest="${u#*://}"
host="${rest%%/*}"
host="${host%%:*}"
host="${host##*@}"
(( ${GITHUB_PROXY_HOSTS[(I)$host]} ))
}
# 加镜像前缀
_gp_mirror() {
local u="$1"
if _gp_is_github_url "$u"; then
print -r -- "${GITHUB_PROXY_MIRROR}${u}"
else
print -r -- "$u"
fi
}
# 去掉已存在的镜像前缀(复制粘贴带前缀的地址也能正确还原)
_gp_unmirror() {
local u="$1"
if [[ "$u" == ${GITHUB_PROXY_MIRROR}* ]]; then
print -r -- "${u#${GITHUB_PROXY_MIRROR}}"
else
print -r -- "$u"
fi
}
# 归一化 + 按当前开关改写:这是所有包装器共用的出口
_gp_url() {
local u="$1"
u="$(_gp_unmirror "$u")"
if _gp_enabled; then
_gp_mirror "$u"
else
print -r -- "$u"
fi
}
# ---------------------------------------------------------------------------
# 包装器 1:curl
# ---------------------------------------------------------------------------
curl() {
local -a args=()
local a
for a in "$@"; do
case "$a" in
http://*|https://*) args+=("$(_gp_url "$a")") ;;
*) args+=("$a") ;;
esac
done
command curl "${args[@]}"
}
# ---------------------------------------------------------------------------
# 包装器 2:wget
# ---------------------------------------------------------------------------
wget() {
local -a args=()
local a
for a in "$@"; do
case "$a" in
http://*|https://*) args+=("$(_gp_url "$a")") ;;
*) args+=("$a") ;;
esac
done
command wget "${args[@]}"
}
# ---------------------------------------------------------------------------
# 内部:把一个仓库(含子模块)里指向镜像的 remote 地址还原成原始地址
# git clone 会把"实际使用的地址"写进 .git/config,若不还原,之后 push
# 这条 remote 会指向镜像(镜像不支持写操作)。
# ---------------------------------------------------------------------------
_gp_fix_remotes_one() {
local dir="$1" name url clean
[[ -d "$dir" ]] || return 0
for name in ${(f)"$(command git -C "$dir" remote 2>/dev/null)"}; do
url="$(command git -C "$dir" remote get-url "$name" 2>/dev/null)" || continue
clean="$(_gp_unmirror "$url")"
[[ "$clean" == "$url" ]] || command git -C "$dir" remote set-url "$name" "$clean" 2>/dev/null
done
}
_gp_fix_remotes() {
local dir="$1" mod
[[ -d "$dir/.git" ]] || return 0
_gp_fix_remotes_one "$dir"
for mod in "$dir"/.git/modules/*(N/); do
_gp_fix_remotes_one "$mod"
done
}
# 从 clone 的参数里找出目标目录
_gp_clone_target_dir() {
local -a args=("$@")
local i url="" dir=""
for (( i = 2; i <= $#args; i++ )); do
local a="${args[i]}"
if [[ -z "$url" ]]; then
[[ "$a" == *://* || "$a" == *@*:* ]] && url="$a"
continue
fi
[[ "$a" == -* ]] && continue
dir="$a"; break
done
[[ -n "$url" ]] || return 1
[[ -n "$dir" ]] || dir="${${url%%\?*}#*/}" # 去掉 query,取路径最后一段
dir="${dir%.git}"
[[ -n "$dir" ]] || return 1
print -r -- "$dir"
}
# ---------------------------------------------------------------------------
# 包装器 3:git
# 通过注入 git 自身的 URL 重写规则实现:
# · 读操作(clone/fetch/pull/...)自动走镜像
# · 写操作(push)不注入任何规则,保持你原有的 SSH 推送习惯
# · clone 结束后把 remote 还原成原始 github 地址,仓库保持"干净"
# ---------------------------------------------------------------------------
git() {
if ! _gp_enabled; then
command git "$@"
return
fi
# 找出子命令(跳过选项)
local sub="" a
for a in "$@"; do
[[ "$a" == -* ]] && continue
sub="$a"; break
done
local m="$GITHUB_PROXY_MIRROR" h
local -a cfg=()
for h in github.com raw.githubusercontent.com codeload.github.com \
gist.github.com objects.githubusercontent.com; do
cfg+=(-c "url.${m}https://${h}/.insteadOf=https://${h}/")
done
command git "${cfg[@]}" "$@"
local rc=$?
# clone / submodule 之后收拾 remote 地址
if (( rc == 0 )); then
case "$sub" in
clone)
local target
target="$(_gp_clone_target_dir "$@")" && _gp_fix_remotes "$target"
;;
submodule)
_gp_fix_remotes "$PWD"
;;
esac
fi
return $rc
}
# ---------------------------------------------------------------------------
# 对外命令
# ---------------------------------------------------------------------------
# 帮助文本(github_proxy help / -h / --help / usage / 参数写错时显示)
_gp_help() {
local state="关闭(直连)"
_gp_enabled && state="开启(走镜像)"
print -r -- "github_proxy —— GitHub 国内加速开关(当前:${state})"
print -r -- ""
print -r -- " github_proxy # 看状态"
print -r -- " github_proxy on # 开启(默认状态)"
print -r -- " github_proxy off # 关闭,全部直连"
print -r -- " github_proxy toggle # 切换"
print -r -- " github_proxy test # 实测镜像 vs 直连速度"
print -r -- " github_proxy url <URL> # 只看改写结果,方便排查"
print -r -- " github_proxy brew # 看 Homebrew 接管状态"
print -r -- " github_proxy debug on # 记录包装器实际请求的地址(排查用)"
print -r -- ""
print -r -- "接管范围:"
print -r -- " git clone / fetch / pull / ls-remote / submodule / archive → 走镜像"
print -r -- " git push → 不受影响,仍走你的 SSH"
print -r -- " curl / wget 中的 github 域名 → 走镜像"
print -r -- " Homebrew 升级时的 github 直连(cask 的 app 包、formula 源码、"
print -r -- " tap 的 formula 定义 —— USTC 镜像管不到的部分) → 走镜像"
print -r -- ""
print -r -- "接管不到:"
print -r -- " npm / pip / go、VS Code、浏览器、focr 等程序自身发起的请求"
print -r -- " gh CLI(走 api.github.com,只能用 HTTPS_PROXY)"
print -r -- " SSH 形式地址 git@github.com:…"
print -r -- ""
local brew_state="未接管"
_gp_brew_active && brew_state="已接管(brew 的 curl/git → 包装器)"
print -r -- "Homebrew:${brew_state}"
print -r -- "镜像: ${GITHUB_PROXY_MIRROR} (用 GITHUB_PROXY_MIRROR 环境变量可替换)"
print -r -- "状态文件:${GITHUB_PROXY_STATE}"
print -r -- "临时覆盖:GITHUB_PROXY_FORCE=off zsh (只影响这一次会话,不动状态文件)"
}
github_proxy() {
local cmd="${1:-status}"
case "$cmd" in
on|enable|1)
_gp_set on
print -r -- "GitHub 代理:已开启 → ${GITHUB_PROXY_MIRROR}"
;;
off|disable|0)
_gp_set off
print -r -- "GitHub 代理:已关闭 → 直连 github.com(国内通常很慢或超时)"
;;
toggle|switch)
if _gp_enabled; then github_proxy off; else github_proxy on; fi
;;
status|"")
local state="关闭(直连)"
_gp_enabled && state="开启(走镜像)"
local brew_state="未接管"
_gp_brew_active && brew_state="已接管"
print -r -- "GitHub 代理:${state}"
print -r -- "镜像地址: ${GITHUB_PROXY_MIRROR}"
print -r -- "Homebrew: ${brew_state}(github_proxy brew 看详情)"
print -r -- "接管的域名:${(j:, :)GITHUB_PROXY_HOSTS}"
print -r -- "状态文件: ${GITHUB_PROXY_STATE}"
;;
brew|homebrew)
local c="未接管" g="未接管" gen="缺失"
[[ "${HOMEBREW_CURL_PATH-}" == "$GITHUB_PROXY_BREW_CURL" ]] && c="已接管"
[[ "${HOMEBREW_GIT_PATH-}" == "$GITHUB_PROXY_BREW_GIT" ]] && g="已接管"
[[ -x "$GITHUB_PROXY_BREW_CURL" && -x "$GITHUB_PROXY_BREW_GIT" ]] && gen="已生成"
print -r -- "Homebrew 接管:curl ${c} / git ${g}"
print -r -- " 包装器: ${gen}(${GITHUB_PROXY_BIN})"
print -r -- " HOMEBREW_CURL_PATH=${HOMEBREW_CURL_PATH:-(未设置)}"
print -r -- " HOMEBREW_GIT_PATH =${HOMEBREW_GIT_PATH:-(未设置)}"
if (( $+commands[brew] )); then
print -r -- " brew 实测:"
command brew config 2>/dev/null | command grep -E "^(Curl|Git):" | command sed 's/^/ /'
fi
print -r -- " 说明:USTC 镜像管 API / bottles / 两个核心仓库;"
print -r -- " cask 的 app 包、formula 源码包、tap 定义仍会直连 github.com,"
print -r -- " 开启后由本包装器改写(下载完仍由 brew 校验 sha256)。"
;;
debug)
case "${2:-}" in
on|1)
[[ -d "$GITHUB_PROXY_STATE_DIR" ]] || mkdir -p "$GITHUB_PROXY_STATE_DIR" 2>/dev/null
: >| "$GITHUB_PROXY_DEBUG_MARKER" 2>/dev/null
print -r -- "调试日志:已开启 → ${GITHUB_PROXY_BREW_LOG}"
print -r -- " (只记录 brew 经包装器发出的请求;用 github_proxy debug off 关闭)"
;;
off|0)
command rm -f "$GITHUB_PROXY_DEBUG_MARKER" 2>/dev/null
print -r -- "调试日志:已关闭(日志文件保留在 ${GITHUB_PROXY_BREW_LOG})"
;;
*)
if [[ -e "$GITHUB_PROXY_DEBUG_MARKER" ]]; then
print -r -- "调试日志:开启(${GITHUB_PROXY_BREW_LOG})"
else
print -r -- "调试日志:关闭"
fi
;;
esac
;;
url)
if [[ -z "$2" ]]; then
print -ru2 -- "用法:github_proxy url <URL>"
return 2
fi
print -r -- "$(_gp_url "$2")"
;;
test|check|bench)
local probe_gh="https://github.com/Dicklesworthstone/franken_ocr/releases/download/models-v1/tokenizer.json"
print -r -- "探测文件:tokenizer.json (9.5 MB)"
print -r -- "-- 镜像 --"
command curl -sL --max-time 25 -o /dev/null \
-w " ${GITHUB_PROXY_MIRROR}\n 速度 %{speed_download} B/s HTTP %{http_code}\n" \
"${GITHUB_PROXY_MIRROR}${probe_gh}"
print -r -- "-- 直连 --"
command curl -sL --max-time 15 -o /dev/null \
-w " https://github.com\n 速度 %{speed_download} B/s HTTP %{http_code}\n" \
"$probe_gh"
;;
help|-h|--help|usage|-help)
_gp_help
;;
*)
print -ru2 -- "github_proxy: 未知参数 '$cmd'"
print -ru2 -- ""
_gp_help
return 2
;;
esac
}
# 让本 shell 内的脚本能判断开关状态:GITHUB_PROXY 故意「不导出」,
# 免得它被子进程继承(曾导致状态文件为 off 时新终端仍判为开启)。
export -n GITHUB_PROXY 2>/dev/null
unset GITHUB_PROXY
# 同步状态 + Homebrew 接管(静默执行,加载时不打印任何东西)
if _gp_enabled; then
GITHUB_PROXY=1
_gp_brew_enable 2>/dev/null
else
GITHUB_PROXY=0
_gp_brew_disable 2>/dev/null
fi附录 B:自动生成的文件长什么样
主脚本会在 ~/.local/state/github-proxy/ 下生成运行时文件。它们不需要你提供,但值得先看清楚——这毕竟是一个会往你磁盘写可执行文件的操作。下面以本机为例(家目录用 ~ 简写)。
B.1 bin/brew-curl(19 行)
#!/bin/bash
# 由 github_proxy.zsh 自动生成,请勿手改
MIRROR="https://gh-proxy.com/"
REAL="/usr/bin/curl"
MARKER="~/.local/state/github-proxy/debug"
LOG="~/.local/state/github-proxy/brew.log"
args=()
for a in "$@"; do
case "$a" in
https://github.com/*|https://raw.githubusercontent.com/*|https://codeload.github.com/*|https://gist.github.com/*|https://objects.githubusercontent.com/*)
args+=("${MIRROR}${a}") ;;
*) args+=("$a") ;;
esac
done
if [ -e "$MARKER" ]; then
if [ -f "$LOG" ] && [ "$(wc -c < "$LOG" 2>/dev/null || echo 0)" -gt 1048576 ]; then mv -f "$LOG" "$LOG.1" 2>/dev/null; fi
{ printf '== %s\n' "$(date +%T)"; for a in "${args[@]}"; do printf ' %s\n' "$a"; done; } >> "$LOG" 2>/dev/null
fi
exec "$REAL" "${args[@]}"要点:
MIRROR与REAL在生成时就写死——brew 会把非HOMEBREW_前缀的环境变量过滤掉(见 4.4 ①),靠环境变量传参行不通。- 只改写白名单里的 5 个 GitHub 域名,其它 URL(S3、CDN 等)原样放行。
exec "$REAL"用绝对路径,避免包装器递归调用自己。- 日志只在
debug标记存在时写入,且超过 1 MB 自动轮转成brew.log.1,忘记关也不会撑爆磁盘。
B.2 bin/brew-git(35 行)
#!/bin/bash
# 由 github_proxy.zsh 自动生成,请勿手改
MIRROR="https://gh-proxy.com/"
REAL="/opt/homebrew/bin/git"
MARKER="~/.local/state/github-proxy/debug"
LOG="~/.local/state/github-proxy/brew.log"
sub=""
for a in "$@"; do
case "$a" in -*) continue ;; *) sub="$a"; break ;; esac
done
if [ "$sub" = "push" ]; then
if [ -e "$MARKER" ]; then
if [ -f "$LOG" ] && [ "$(wc -c < "$LOG" 2>/dev/null || echo 0)" -gt 1048576 ]; then mv -f "$LOG" "$LOG.1" 2>/dev/null; fi
{ printf '== %s (push, 原样放行)\n' "$(date +%T)"; for a in "$@"; do printf ' %s\n' "$a"; done; } >> "$LOG" 2>/dev/null
fi
exec "$REAL" "$@"
fi
# git 原生重写规则:覆盖 remote.origin.url 这类"写在配置里"的地址
cfg=()
for h in github.com raw.githubusercontent.com codeload.github.com gist.github.com; do
cfg+=(-c "url.${MIRROR}https://${h}/.insteadOf=https://${h}/")
done
args=()
for a in "$@"; do
case "$a" in
https://github.com/*|https://raw.githubusercontent.com/*|https://codeload.github.com/*|git@github.com:*)
args+=("${MIRROR}${a}") ;;
*) args+=("$a") ;;
esac
done
if [ -e "$MARKER" ]; then
if [ -f "$LOG" ] && [ "$(wc -c < "$LOG" 2>/dev/null || echo 0)" -gt 1048576 ]; then mv -f "$LOG" "$LOG.1" 2>/dev/null; fi
{ printf '== %s\n' "$(date +%T)"; for a in "${args[@]}"; do printf ' %s\n' "$a"; done; } >> "$LOG" 2>/dev/null
fi
exec "$REAL" "${cfg[@]}" "${args[@]}"与 curl 版的两点差异:push 直接 exec 原样放行,写操作绝不经过镜像;地址白名单里除 https 之外还认 git@github.com: 这种 SSH 形式。
B.3 另外三个文件
state:3 字节文本,内容on或off。debug:0 字节标记文件,存在即记录、删除即停止。brew.log/brew.log.1:纯文本追加日志,形如:
== 10:23:15
--disable
--fail
--location
https://gh-proxy.com/https://github.com/p0deje/Maccy/releases/download/2.7.1/Maccy.app.zipgithub_proxy debug off 只删除标记文件,日志保留供事后查看;想彻底清空直接删掉 brew.log 即可。