brew install 或 upgrade 下载缓慢时,手动下载安装包

摘要

  • brew install、brew upgrade 下载很慢或中途断开时,可以自己把同一个安装包下完,放到 Homebrew 这次要读的缓存文件上,再重新执行原来的命令。下面用本机升级 gh 2.102.0 作例子。Homebrew 7.0.8,缓存目录是 ~/Library/Caches/Homebrew。这次 GitHub 上的源码包下到一半就断了:curl: (18) Transferred a partial file。
  • 可以自己把同一个压缩包下完,再放到 Homebrew 这次要读的缓存文件上。放进去的必须是 formula 里那个 URL 的文件,SHA-256 也要一致。文件名以 brew --cache --build-from-source gh 的输出为准,不能自己编哈希,也不能拷进 $(brew --cellar)/gh。
  • gh 2.102.0 在当前 API 里没有 bottle,所以这次下的就是源码包 https://github.com/cli/cli/archive/refs/tags/v2.102.0.tar.gz,校验和是 08bf0ef8b4409893889175e0f5279d30d6edd96465816042c0d7c72eac598158。本机缓存文件名是 downloads/838ae966d01d820dbc2c41ab1f82d72064fe030ca19254fb2ff3aa993768b18d--cli-2.102.0.tar.gz。前半段是这条 URL 的 SHA-256,后半段来自下载响应里的文件名。
  • 校验通过后再执行原来的 brew upgrade gh 或 brew install gh。Homebrew 会接着做自己的校验和编译。缓存和 Cellar 的差别见 MacOS软件包管理器--brew。
  • Cask 用同一套缓存目录。路径改成 brew --cache --cask <token>,放进去的是这个 cask 的 url 指向的那个文件。本机 Firefox 是 dmg,codex 是 tar.gz,claude-code 没有后缀。例子在文末。

失败时的日志

  • 下载停在源码包,formula 脚本本身已经拿到了:

1
2
3
4
✘ Formula gh (2.102.0)                                          Downloading    12.8MB/-------
Error: Failed to download resource "gh (2.102.0)"
Download failed: https://github.com/cli/cli/archive/refs/tags/v2.102.0.tar.gz
curl: (18) Transferred a partial file
  • curl: (18) 表示收到的长度和响应里声明的不一致。这次传到本地的是半截文件,不能拿去安装。浏览器、带断点续传的 curl,或者换一条网络,都可以把同一个地址下完整。

缓存文件名从哪来

  • 先看缓存目录,再看这个 formula 从源码构建时要用的那个文件:

1
2
brew --cache
brew --cache --build-from-source gh
  • 本机这两条的结果:

1
2
3
/Users/hanqf/Library/Caches/Homebrew

/Users/hanqf/Library/Caches/Homebrew/downloads/838ae966d01d820dbc2c41ab1f82d72064fe030ca19254fb2ff3aa993768b18d--cli-2.102.0.tar.gz
  • 若设置过 HOMEBREW_CACHE,目录会不同,以 brew --cache 为准。

  • 文件名分两段,中间是 --。838ae966d01d820dbc2c41ab1f82d72064fe030ca19254fb2ff3aa993768b18d 是 formula URL 的 SHA-256,不是压缩包内容的校验和。cli-2.102.0.tar.gz 是 GitHub 返回的文件名:仓库是 cli/cli,归档名用仓库名而不是 formula 名 gh。官方说明里写成 <url-hash>--<formula>-<version>,见 Pre-download a file for a formula。本机实际后缀是 cli-2.102.0.tar.gz。

  • gh 2.102.0 没有 bottle,所以不加 --build-from-source 时,brew --cache gh 打出来的也是这个源码包路径。有 bottle 的 formula 不一样:不加这个参数时路径指向 bottle,源码包必须加 --build-from-source。把源码包放到 bottle 的路径上,安装时对不上。

  • 中断的传输会留在同一目录,文件名多一个 .incomplete。那是半截文件。完整包要放在没有这个后缀的路径上。

自己下载

  • 地址就是日志里的那一条:

1
https://github.com/cli/cli/archive/refs/tags/v2.102.0.tar.gz
  • 浏览器下到 ~/Downloads 即可。用 curl 时打开断点续传:

1
2
3
4
5
mkdir -p ~/Downloads
curl -fL --retry 5 --retry-all-errors \
-C - \
-o ~/Downloads/gh-2.102.0.tar.gz \
'https://github.com/cli/cli/archive/refs/tags/v2.102.0.tar.gz'
  • -C - 从已有文件的末尾接着下,服务器要支持 Range。本地这个文件叫 gh-2.102.0.tar.gz 只是为了好认,和缓存文件名不必相同。下一步 mv 会按缓存路径改名。

放进缓存并核对校验和

  • 校验和从当前 API 里的 formula 读,版本以报错里的 2.102.0 为准:

1
brew info --json=v2 gh | python3 -c 'import json,sys; u=json.load(sys.stdin)["formulae"][0]["urls"]["stable"]; print(u["url"]); print(u["checksum"])'
  • 本机输出:

1
2
https://github.com/cli/cli/archive/refs/tags/v2.102.0.tar.gz
08bf0ef8b4409893889175e0f5279d30d6edd96465816042c0d7c72eac598158
  • brew cat gh 若还停在旧版本,里面的 sha256 对不上这次的包。安装走的是 API 里的 2.102.0。

  • 挪到缓存路径,再算内容的 SHA-256:

1
2
3
4
CACHE_PATH="$(brew --cache --build-from-source gh)"
rm -f "${CACHE_PATH}.incomplete"
mv ~/Downloads/gh-2.102.0.tar.gz "$CACHE_PATH"
shasum -a 256 "$CACHE_PATH"
  • shasum 的结果要和上面的 08bf0ef8b4409893889175e0f5279d30d6edd96465816042c0d7c72eac598158 一致。不一致就删掉 $CACHE_PATH,重新下载。Homebrew 自己校验失败时也会提示删掉这个文件。

继续安装

  • 还没装过:

1
brew install gh
  • 已安装、这次是升级:

1
brew upgrade gh
  • 缓存里的文件校验通过后,Homebrew 不再去下这个 tar.gz,接着解压、编译、装进 Cellar。这一步仍然要本地编译。Intel 上不再提供 bottle 的说明见 MacOS软件包管理器--brew 文末「从 4.5 到 7.0」。

其他 formula

  • 每次都先跑 brew --cache --build-from-source <formula>,把文件放到它打印的那一条路径上。

  • 日志里的 URL 若是 ghcr.io 上的 bottle,放进去的应是那个 bottle,路径用 brew --cache --force-bottle <formula>。源码包不能顶替 bottle。

  • brew --cache 只打印 formula 自己的那一个文件。formula 里若还有 resource,每个资源有自己的 URL、缓存路径和 SHA-256,要分别放下。gh 2.102.0 的 resource 是空的,这次只需要日志里的那一个 tar.gz。

  • 想先看 Homebrew 准备下的地址,可以执行 brew fetch --build-from-source gh。它下成功时会打出 SHA-256。下不完时,仍用上一节的 brew info --json=v2 读校验和,自己下完再放进缓存。

Cask

  • 缓存目录仍是 $(brew --cache)/downloads。文件名还是两段,中间是 --。前半段是 cask 里那条 URL 的 SHA-256,后半段是安装包文件名。路径用下面这条。--cask 与 --build-from-source、--force-bottle 互斥,formula 那两个参数在这里不能用:

1
brew --cache --cask firefox
  • 本机结果:

1
/Users/hanqf/Library/Caches/Homebrew/downloads/474e2ff1fe5964341f94193795bc51e051af0d70d4098fe5990dbef7e2adfcc2--Firefox 158.0.dmg
  • 474e2ff1fe5964341f94193795bc51e051af0d70d4098fe5990dbef7e2adfcc2 是 URL 字符串 https://download-installer.cdn.mozilla.net/pub/firefox/releases/158.0/mac/zh-CN/Firefox%20158.0.dmg 的 SHA-256。跳转后的地址不参与这段哈希。Firefox 158.0.dmg 来自 URL 路径,其中的 %20 在文件名里是空格。

  • 后缀跟 URL 路径最后一段走。本机 codex 0.162.1 是 tar.gz:

1
brew --cache --cask codex
  • 本机结果:

1
/Users/hanqf/Library/Caches/Homebrew/downloads/9be8bf161f612c15e0003d4c5b13944e869bbb3992e85437a43e38f1cd61d5c9--codex-package-x86_64-apple-darwin.tar.gz
  • URL 是 https://github.com/openai/codex/releases/download/rust-v0.162.1/codex-package-x86_64-apple-darwin.tar.gz。前半段 9be8bf161f612c15e0003d4c5b13944e869bbb3992e85437a43e38f1cd61d5c9 是这条 URL 的 SHA-256。内容校验和是 894ac20afe819d4fa2a4be8cd27c54fd606338b86036e8ed9b6c9978c2dc7aed。这台是 Intel,包名里是 x86_64。缓存里的这份文件已对上该校验和。

  • 本机 claude-code 2.1.292 的 URL 末尾没有扩展名,缓存文件也没有后缀:

1
brew --cache --cask claude-code
  • 本机结果:

1
/Users/hanqf/Library/Caches/Homebrew/downloads/6cbbd982529e2b9557970f25aa883d5a16ee85318b6e298d3d2ab41983eb3984--claude
  • URL 是 https://downloads.claude.ai/claude-code-releases/2.1.292/darwin-x64/claude。前半段 6cbbd982529e2b9557970f25aa883d5a16ee85318b6e298d3d2ab41983eb3984 是这条 URL 的 SHA-256。内容校验和是 a9739a215728ce72435885fedb19d1317ee1ccec61e246fbfb3acaf01689c473。缓存里的这份文件已对上该校验和,文件类型是 Mach-O 可执行文件。文件名就叫 claude,不要另加 .zip、.dmg 或 .tar.gz。

  • 地址和内容校验和从当前 API 读。语言和 CPU 已经按本机选好:系统语言是 zh-Hans-CN,所以 URL 里是 zh-CN;这台是 Intel,Firefox 用的是 mac 目录下的包。换语言或换架构后,URL、SHA-256 和缓存路径都会变。

1
brew info --json=v2 --cask firefox | python3 -c 'import json,sys; c=json.load(sys.stdin)["casks"][0]; print(c["url"]); print(c["sha256"])'
  • 本机输出:

1
2
https://download-installer.cdn.mozilla.net/pub/firefox/releases/158.0/mac/zh-CN/Firefox%20158.0.dmg
d0daa923ac5962e1ef0f3f94700e52f2d4ccbba79ae42faa3d76e5bfe5b74370
  • brew cat firefox 若还停在旧版本,里面的 sha256 对不上这次的包。安装走的是 API 里的 158.0。

  • 自己下载。~/Downloads 里的文件名可以自定,下一步 mv 会按缓存路径改名:

1
2
3
4
5
mkdir -p ~/Downloads
curl -fL --retry 5 --retry-all-errors \
-C - \
-o ~/Downloads/Firefox-158.0.dmg \
'https://download-installer.cdn.mozilla.net/pub/firefox/releases/158.0/mac/zh-CN/Firefox%20158.0.dmg'
  • 放进缓存并核对。路径里有空格,引用时要加引号。中断留下的半截文件仍是同一路径加上 .incomplete:

1
2
3
4
CACHE_PATH="$(brew --cache --cask firefox)"
rm -f "${CACHE_PATH}.incomplete"
mv ~/Downloads/Firefox-158.0.dmg "$CACHE_PATH"
shasum -a 256 "$CACHE_PATH"
  • shasum 的结果要和 d0daa923ac5962e1ef0f3f94700e52f2d4ccbba79ae42faa3d76e5bfe5b74370 一致。不一致就删掉 $CACHE_PATH,重新下载。

  • 还没装过:

1
brew install --cask firefox
  • 已安装、这次是升级:

1
brew upgrade --cask firefox
  • 校验通过后,Homebrew 不再去下这个 dmg,接着挂载并装进 Caskroom,应用放到 /Applications。Firefox 的 auto_updates 为 true,装好之后由应用自己更新,brew upgrade 默认不会再下新的 dmg。缓存和 Caskroom 的差别见 MacOS软件包管理器--brew。

  • 放进去的是 cask url 指向的那一个文件。Firefox 是 dmg,codex 是 tar.gz,claude-code 是没有后缀的可执行文件。解压或安装之后的 .app、以及已经链到 $(brew --prefix)/bin 的二进制,对不上这个缓存文件,也不要拷进 $(brew --caskroom)。

  • JSON 里的 sha256 若是 no_check,这个 cask 没有内容校验和。官方 cask 会跳过校验。本机 steam 就是这样,缓存路径仍以 brew --cache --cask steam 为准。

  • 第三方 tap 的 cask 同样先跑 brew --cache --cask <token>。本机已装的 redis 8.10.2 缓存文件是 downloads/54bde208450678f731ccfb0d806ece50048b1cd3c3d416a648a14a359fc8f37b--redis-oss-8.10.2-x86_64.zip,内容 SHA-256 为 08334ca85583bc81323be65d22455b0d36d21c3e149c588c911f80284cd8c6b4,与 brew info --json=v2 --cask redis 里的一致。

  • url_specs 里若有 cookie、referer 或 header,自己下载时要带上同样的字段,否则拿到的可能是另一份内容。Firefox 这份 url_specs 是空的。

  • 想让 Homebrew 自己下载并打印 SHA-256,可以执行 brew fetch --cask firefox。下不完时,仍用上面的 brew info --json=v2 --cask 读校验和,自己下完再放进缓存。