MacOS系统上,升级python3.12时,超级慢,而且最后还失败了。看了日志,发现是用curl从raw.githubusercontent.com上下载Python安装包超时了。

解决方案一:开启VPN工具,穿越围墙

解决方案二:使用mirror.ghproxy.com加速raw.githubusercontent.com

穿墙有风险,操作需谨慎,这里我采用的是方案二。

1. 查看curl路径

which curl
# /usr/bin/curl

2. 编写自定义脚本,并赋予执行权限

ln -s `which curl` /usr/local/bin/curl
[ -d ~/.local/bin ] || mkdir -p ~/.local/bin/
cd ~/.local/bin
touch curl
chmod +x curl
# 注:PATH要配置到.bashrc或.zshrc里,别的窗口才能起作用
export PATH=$HOME/.local/bin:$PATH
vi curl
which curl
# ~/.local/bin/curl

3. 脚本内容

#!/usr/bin/env python
import os
import re
import sys

try:
    from typing import List  # NOQA:F401
except ImportError:
    pass

RE_SKIP = re.compile(r'\.(zip|rar|7z|apk|ipa|exe|msi|m3u|m3u8|mp4|mp3)$')


def rewrite_url(i, scheme):
    # type: (str, str) -> str
    proxy = "mirror.ghproxy.com"
    domains = ("raw.githubusercontent.com", "gist.githubusercontent.com")
    # TODO: use personal Cloudflare Worker instead.
    j = i[len(scheme) :]
    for domain in domains:
        if j.startswith(domain):
            if not RE_SKIP.search(j):
                i = scheme + proxy + '/' + i
            break
    return i


def gen_cmd(sys_argv):
    # type: (List[str]) -> str
    args = []
    scheme = "https://"
    for i in sys_argv[1:]:
        if i.startswith(scheme):
            i = rewrite_url(i, scheme)
        elif " " in i:
            i = repr(i)
        args.append(i)
    tool = "/usr/local/bin/" + sys_argv[0].split("/")[-1]
    cmd = tool + " " + " ".join(args)
    return cmd


def main():
    # type: () -> int
    sys_argv = sys.argv
    if "--dry" in sys_argv:
        sys_argv = [i for i in sys_argv if i != "--dry"]
        print("-->", gen_cmd(sys_argv))
        return 0
    return os.system(gen_cmd(sys_argv))


if __name__ == "__main__":
    sys.exit(main())

4. 测试效果

curl https://raw.githubusercontent.com/Homebrew/homebrew-core/a775cbd0967da13128293d71fb26431fdedee6fb/Formula/m/mpdecimal.rb

5. 重新执行升级命令(这时候就很快了,不到一分钟就下载完毕,十分钟内就完成升级)

brew upgrade python@3.12

注: wget也可以用类似的方法加速

################# 2024.02.08 补充:

如果有外网服务器,还可以配个中转请求:

1. 本地curl文件内容如下(需export JUMPER_IP='<服务器IP地址>')

#!/usr/bin/env python
import os
import sys

try:
    from typing import List  # NOQA:F401
except ImportError:
    pass


def gen_cmd(sys_argv):
    # type: (List[str]) -> str
    args = []
    scheme = "https://"
    origin, target = "raw.githubusercontent.com", "raw.gitmirror.com"
    domains = ("objects.githubusercontent.com", "github.com")
    redirect = os.getenv("JUMPER_IP")
    host = "http://{}:9337/".format(redirect)
    for i in sys_argv[1:]:
        if i.startswith(scheme):
            j = i[len(scheme) :]
            if j.startswith(origin):
                i = i.replace(origin, target)
            elif redirect:
                for domain in domains:
                    if j.startswith(domain):
                        i = host + j
                        break
        elif " " in i:
            i = repr(i)
        args.append(i)
    tool = "/usr/local/bin/" + sys_argv[0].split("/")[-1]
    cmd = tool + " " + " ".join(args)
    return cmd


def main():
    # type: () -> int
    sys_argv = sys.argv
    if "--dry" in sys_argv:
        sys_argv = [i for i in sys_argv if i != "--dry"]
        print("--> " + gen_cmd(sys_argv))
        return 0
    return os.system(gen_cmd(sys_argv))


if __name__ == "__main__":
    sys.exit(main())

2. 服务器上的app.py文件如下:

#!/usr/bin/env python
import re

# pip install httpx orjson loguru sanic gunicorn 'uvicorn[standard]'
from httpx import AsyncClient
from loguru import logger
from orjson import dumps, loads
from sanic import Sanic, raw

app = Sanic("Jumper", dumps=dumps, loads=loads)


@app.route("", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
async def index(request):
    return raw(b"Welcome. Example>> http :8000/http.127.0.0.1:9000/home")


@app.route("/<full:path>", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
async def handler(request, full: str):
    host, url = full.lstrip("/"), ""
    if m := re.search(r"(https?)[^0-9a-zA-Z]+(.+)", host):
        scheme, host = m.groups()
    else:
        scheme = "https"
    try:
        domain, url = host.split("/", 1)
    except ValueError:
        domain = host
    base_url = scheme + "://" + domain
    target_path = url
    if qs := request.query_string:
        target_path += "?" + qs
    if not target_path:
        target_path = base_url
    if not target_path.startswith("/") and not target_path.startswith("http"):
        target_path = "/" + target_path
    logger.debug(f"{base_url=}; {target_path=}")
    async with AsyncClient(
        base_url=base_url, follow_redirects=True, timeout=20
    ) as client:
        method, body = request.method, request.body
        r = await client.request(method, target_path, content=body)
        if r.status_code == 302 and (next_url := r.headers.get("location")):
            r = await client.request(method, next_url, content=body)
    return raw(r.content, status=r.status_code)


if __name__ == "__main__":  # pragma: no cover
    app.run(debug=True, host="0.0.0.0")

3. 后台启动服务:

gunicorn app:app --bind 0.0.0.0:9337 --worker-class uvicorn.workers.UvicornWorker --daemon

4. 本地brew对应的install文件修改如下:

cd $(brew --prefix)/Homebrew/Library/Homebrew
grep -rn Downloading *.rb
# download_strategy.rb:426:
vi download_strategy.rb # 给download的url增加前缀
git diff

增加这几行:

       if (domain = Homebrew::EnvConfig.artifact_domain)
         url = url.sub(%r{^https?://#{GitHubPackages::URL_DOMAIN}/}o, "#{domain.chomp("/")}/")
+      elsif url.start_with?("https://cdn.")
+        puts "Leave #{url} to be itself."
+      elsif !url.start_with?("https://mirrors.")
+        url = "http://your-server-ip-or-domain/" + url
       end

       ohai "Downloading #{url}"

 即除了mirrors和cdn开头的域名,全都使用自建域名加速

      elsif url.start_with?("https://cdn.")
        puts "Leave #{url} to be itself."
      elsif !url.start_with?("https://mirrors.")
        url = "https://your.domain.com/" + url

################################

2024.03.06 补充 (Updated@2024.08.29)

Thanks to https://github.com/hunshcn/gh-proxy

步骤4可以用如下Python脚本来实现:

#!/usr/bin/env python
"""
This script is used to improve download speed for `brew install`

Usage::
    $ python pad_brew_download_url.py
"""

import os
import subprocess

HOST = "https://mirror.ghproxy.com/"
PY_HOST = "https://mirrors.huaweicloud.com/python/"
PAD = """
      elsif (url.start_with?("https://cdn.") || url.start_with?("https://desktop.docker.com"))
        puts "Leave #{url} to be itself."
      elsif (url.start_with?("https://ftpmirror.") || url.start_with?("https://ftp.gnu.org"))
        puts "Skip #{url} padding."
      elsif url.start_with?("https://www.python.org/ftp/python/3.")
        url = "%s" + url[34,url.length]
      elsif !url.start_with?("https://mirror")
        url = "%s" + url
"""


def say_done():
    # type: () -> None
    try:
        from rich.console import Console
    except ImportError:
        print("\nDone~\n")
    else:
        console = Console()
        console.log("[bold magenta]Done.[/bold magenta]", ":vampire:")


def capture_output(cmd):
    # type: (str) -> str
    try:
        r = subprocess.run(cmd, shell=True, capture_output=True)
    except (TypeError, AttributeError):  # For python<=3.6
        with os.popen(cmd) as p:
            return p.read().strip()
    else:
        return r.stdout.decode().strip()


def remove_old_pad(text, s):
    # type: (str, str) -> str
    s_index = text.index(s)
    pre = text[:s_index]
    lines = pre.splitlines()
    length = len(lines)
    if_index = length
    for i in range(length - 1, -1, -1):
        line = lines[i]
        if line.strip().startswith("if "):
            if_index = i
            break
    elif_index = 0
    start = if_index + 2
    for i, one in enumerate(lines[start:], start):
        if one.strip().startswith("elsif "):
            elif_index = i
            break
    if elif_index:
        cut = "\n".join(lines[:elif_index])
        if pre.endswith("\n"):
            cut += "\n"
        text = cut + text[s_index:]
    return text


def main():
    # type: () -> None
    brew_root = capture_output("brew --prefix")
    folder = "Homebrew/Library/Homebrew"
    name = "download_strategy.rb"
    file = os.path.join(brew_root, folder, name)
    with open(file) as f:
        text = f.read()
    pad = (PAD % (PY_HOST, HOST)).lstrip("\n")
    if pad in text:
        print("{} already in {}\nNothing to do!".format(HOST, file))
        return

    bak_file = file + ".bak"
    if not os.path.exists(bak_file):
        with open(bak_file, "w") as fp:
            fp.write(text)
        print("Create backup file at: {}".format(bak_file))
    s = '      end\n\n      ohai "Downloading #{url}"'
    text = remove_old_pad(text, s)
    new_text = text.replace(s, pad + s)
    with open(file, "w") as f:
        f.write(new_text)
    print("Insert\n{} into {}".format(pad, file))
    say_done()


if __name__ == "__main__":
    main()

GitHub 加速计划 / br / brew
40.4 K
9.47 K
下载
🍺 The missing package manager for macOS (or Linux)
最近提交(Master分支:1 个月前 )
90a90b30 3 个月前
47b1cab7 3 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐