面向 Windows(Git Bash / PowerShell 皆可)。两层互补,各自解决实测确认不同的故障面:
- 不改 Clash 配置。本仓库只管理自己的 hosts 自动化和下载/克隆工具。
- GitHub 主站与关键资源域不再固定静态 IP:
github.com、gist.github.com、api.github.com、codeload.github.com、raw.githubusercontent.com、objects.githubusercontent.com、avatars.githubusercontent.com、github.githubassets.com一律使用当前 DNS。一次探测成功不能代表下一刻仍可用;写入全局 hosts 会把瞬时波动放大成整机访问故障。运行node accel.mjs会重建本工具标记块并移除旧的 GitHub 静态钉。 - 下载:健康镜像并发竞速 → 全部失败后官方 URL 无代理直连重试。直连回退会清除本次子进程的代理环境变量,并使用 curl
--noproxy '*',不改变 Clash 或系统代理设置;只在完整文件成功后才替换输出文件。 - 克隆:镜像入口探测/重试 → 官方 GitHub URL 直连重试。直连回退清除代理环境变量、显式清空 Git 的代理配置,而且不添加
insteadOf镜像重写。若目标目录在运行前已存在,工具拒绝覆盖,不会因镜像失败删除已有文件。 - 边界说明:镜像回退只适用于本工具的下载与
git clone。浏览器页面的透明故障切换必须由浏览器/系统路由层实现;在不改变 Clash、且浏览器仍配置为 GitHub 直连的前提下,任何仓库脚本都无法保证运营商链路、DNS 或 GitHub 上游永不故障。此次改动的目标是移除本工具主动造成的坏钉,并为本工具执行的下载/克隆补齐最终直连路线。
关于权限(2026-10-13 实测更新):写
C:\Windows\System32\drivers\etc\hosts的内容是可行的—— 前提是 hosts 文件上有一条本用户的显式 ACE(本机现状:jingjiang:(M),另有目录继承的 SYSTEM/Administrators 项)。drivers\etc\目录本身不可写(用户在该目录无法创建/删除文件),所以"临时文件 + 原子 rename"的记录方式在这台机器上 全部不可用(实测:同目录建临时文件 EACCES;跨目录 rename 到 hosts EPERM;.NETFile.Replace失败时会把目标移走, 已永久禁用)。现行写入策略:写前备份 → 直接写 → 写后逐字节读回(读回不符会重写一次,仍不符则从备份恢复并中止本轮)。 如果 hosts 文件整个缺失(重建需要提权),工具会拒绝继续并提示运行repair-hosts.ps1(自动静默提权,从backups/最近一份恢复;本机ConsentPromptBehaviorAdmin=0实测可用)。install-task.ps1会先探测可写性:不可写且当前不是管理员时直接报错,不会静默装出一个"跑得正常、但加速从未生效"的任务。
| 层 | 文件 | 解决什么 |
|---|---|---|
| A 自适应 hosts | accel.mjs(+ ip / probe / hostsio 三个模块) |
头像、raw、归档包、npm/jsDelivr、PyPI、SourceForge、cdnjs/unpkg 等能靠 IP 救的通道:候选 IP 逐条本机 TLS 实测,只有确实更快才写入 |
| B 竞速取件 | gh.mjs |
下载/git clone 并发竞速镜像;镜像全失败时强制尝试官方地址直连。不是浏览器透明代理 |
设计取向是宁可不写,也不写坏:写错的 hosts 条目比不写更糟(浏览器全站受影响),所以每条固定都要过"多轮实测 + 写入后真实解析复核"。复核若发现真实解析落到了没钉的那一族,先按族补钉(用另一族实测可用的候选)再复核第二轮,补不上才整域撤销。该判定(planAfterLeak)可用 node accel.mjs --selftest 离线回归。
v2(2026-10-13)把这套守护做细了:落地复核 = 系统解析整链检查 + 每族 --resolve 直检,任一项失败先重试 1 次再判决;某一族直检失败只撤那一族(旧版只能整域撤)。判定拆进三个可离线回归的模块:ip.mjs(net.isIP 严格校验 + v6 前缀归一)、probe.mjs(解析/判定纯函数)、hostsio.mjs(备份+直写+读回的安全提交)。
进 A 层的域 = 「换 IP 救得回来」+「有可信候选源」两个条件都满足,不是列得越多越好。GitHub 自有核心域由代码级安全规则排除静态 hosts 固定,以免短暂成功的地址在下一轮失效:
| 分类 | 域 | 候选来源 |
|---|---|---|
| GitHub 核心域 | github.com、api.github.com、codeload.github.com、raw/objects/avatars/githubassets |
不静态钉 hosts;动态 DNS 负责解析,gh.mjs 负责下载/克隆镜像竞速与官方直连回退 |
| Cloudflare 前置 | cdn.jsdelivr.net、registry.npmjs.org、sourceforge.net、downloads.sourceforge.net、cloudflare.com、cdnjs.cloudflare.com、unpkg.com |
Cloudflare ips-v4/ips-v6(官方)+ DoH |
| Fastly 前置 | pypi.org、files.pythonhosted.org |
Fastly public-ip-list(官方)+ DoH |
实测收益:pypi.org 系统解析 5847ms → 钉到 2a04:4e42::223 后 285ms(≈20×);
files.pythonhosted.org 2659ms → 185ms。
为什么没有 Google / YouTube / Wikipedia / archive.org / huggingface / docker / reddit(同日审计结论,
证据在 _tmp/multi-site-accel-2026-10-13/findings.md):本机线路上这些域双重受阻——
① DNS 被污染(www.google.com 被解析到 Facebook 段 69.171.235.22,连 www.example.com 都拿到 Cloudflare 段);
② 真实 IP 直连也不通(经代理 DoH 拿到真 IP 后逐个 --resolve 实测:Google/Wikipedia/archive/huggingface/docker/reddit
全部超时或连接重置)。hosts 层对它们无效,硬写只会把用户导到连不上的 IP。这类域要靠代理或反代(B 层机制),
不是 A 层的职责。
:: 1. 手动跑一轮,先看判定不动文件(纯只读:不建锁、不备份、不写 hosts、不刷 DNS、不做落地复核)
node accel.mjs --dry -v
:: 2. 正式写入(自动备份原 hosts,只替换标记块)
node accel.mjs
:: 3. 装每 10 分钟一轮的无窗口计划任务(会先探测 hosts 可写性)
powershell -NoProfile -ExecutionPolicy Bypass -File install-task.ps1
:: 下载 release / 源码包
node gh.mjs https://github.com/git-lfs/git-lfs/releases/download/v3.6.1/git-lfs-windows-amd64-v3.6.1.zip
:: 克隆(直连 clone 在这类线路上普遍失败)
node gh.mjs clone --depth 1 https://github.com/octocat/Hello-World.git
:: 离线自测(不联网、不落盘)
node accel.mjs --selftest
node ip.mjs --selftest
node probe.mjs --selftest
node hostsio.mjs --selftest
node gh.mjs --selftest
node pool.mjs --selftest
node discover.mjs --selftest
node manifest.mjs --selftest
node relay/test/middleware.test.mjs
:: 回滚
node accel.mjs --revert
schtasks /delete /tn QoderAccel /f
:: 应急修复(hosts 文件缺失或损坏时;自动提权从最近备份恢复)
powershell -NoProfile -ExecutionPolicy Bypass -File repair-hosts.ps1依据当日完整审计与一次真实事故的教训(细节见仓库提交记录):
- 写入安全:只能"直写"(见上"关于权限");安全来自流程——写前备份、写后读回、失败自恢复、缺失守卫。
- 复核增强:整链 + 分族两层;失败重试 1 次再判决;分族失败只撤该族。
- 形式校验收口:所有 IP 入口走
ip.mjs(net.isIP严格;v6 前缀按规范化 /64 归组)。 - 状态治理:
pool-state.json按"45 天未被观测"裁剪(pool.state_max_age_days),防无限膨胀。 - 候选源:新增 ineo6/hosts(GitLab 活跃源)为第三提示源;
/meta限流时用meta-cache.json上次成功结果兜底(TTL 7 天)。 - 回退:
node accel.mjs --revert(移除标记块);应急repair-hosts.ps1(缺失恢复);代码层面git revert。
无常驻进程:全部行为由计划任务短跑触发(wscript → run-hidden.vbs → node accel.mjs,跑完即退)。
唯一需要守住的数字是单轮进程内存峰值,用 maxRSS(Windows = PeakWorkingSet)量,不用 heapUsed
(本工具大量用 Buffer 存 curl 输出,只看堆会严重低估):
| 进程 | 实测峰值(2026-10-13) | 预算 |
|---|---|---|
accel.mjs(选路 + 落地复核 + 入口体检) |
52.5 MB | 100 MB |
gh.mjs doctor(入口体检) |
47.6 MB | 100 MB |
node bench.mjs :: 跑一轮并打印两个进程的峰值 + 运行期文件体积
node bench.mjs --check :: 同上,超预算时退出码非 0(可接计划任务/CI)内存与状态文件体积是联动的:pool-state.json(历史观测)由 pool.state_max_age_days(默认 45 天)裁剪,
candidates.json / meta-cache.json 每轮按 TTL 重写——这三个文件就是内存的主要来源,
bench 会把它们的当前体积一并打印出来便于追踪。
不想 clone 的话,下载滚动发行包:
https://github.com/jingjiangze/github-accel/releases/download/accel-latest/accel.zip
每次 push 到 main 由 .github/workflows/pack.yml 自动重建并覆盖(Release accel-latest,标 prerelease;同时留一份 workflow artifact)。
包内容 = pack.mjs 里的白名单,逐个列文件、缺一个就让 CI 红掉。为什么不用黑名单:
「只含运行文件」用黑名单迟早会漏——这个仓库就漏过 manifest-cache.json。
| 进包(20 个) | 不进包 |
|---|---|
accel.mjs、ip.mjs、probe.mjs、hostsio.mjs、gh.mjs、manifest.mjs、discover.mjs、pool.mjs、bench.mjs、config.json、manifest-seed.json、install-task.ps1、repair-hosts.ps1、run-hidden.vbs、box/run-probe.ps1、box/install-task.ps1、relay/(wrangler.toml + functions/ + public/)、README.md、LICENSE |
.github/(CI)、.gitignore、relay/test/(测试),以及所有运行期产物(logs/、backups/、report.json、proxy-state.json、candidates.json、pool-state.json、manifest-cache.json、meta-cache.json、dist/、run.lock、relay-key) |
node pack.mjs --selftest :: CRC32 标准向量 + ZIP 读写往返 + 白名单断言(不联网)
node pack.mjs --out dist/accel.zip :: 本地打一份
node pack.mjs --verify dist/accel.zip :: 校验「包内条目 == 白名单」(含 CRC 与解压往返)时间戳固定成 1980-01-01,所以同样内容打出同样字节:sha256 稳定,「包变了」只可能因为文件变了。
pack.mjs 是零依赖的(压缩用内置 zlib,CRC32 自己实现——Node 20 还没有 zlib.crc32,而 CI 用 Node 20);
CI 里除了自研读取器,还会用 unzip -t 这个独立实现再复验一遍。
GitHub Release 直链(PR 合并后自动滚动覆盖):
https://github.com/jingjiangze/github-accel/releases/download/accel-latest/accel.zip
raw.githubusercontent.com 被墙的线路上更稳的 jsDelivr 镜像直链(同一份 zip,由 pack.yml 推到 pkg 分支,
每次 push 到 main 自动 force-push 覆盖,所以也跟着滚动更新):
https://cdn.jsdelivr.net/gh/jingjiangze/github-accel@pkg/accel.zip
两条直链指向同一份字节(sha256 一致),选能通的即可。下载走竞速:
:: 用本项目自己的竞速器下载发行包(多入口竞速,比裸 curl 稳)
node gh.mjs https://github.com/jingjiangze/github-accel/releases/download/accel-latest/accel.zip -o accel.zip包解压后就是一个零依赖的 Node 脚本目录,不写注册表、不留常驻进程。安装 = 解压到任意目录 +(可选)装计划任务。
- 选一个安装目录(随便放哪都行,例如
D:\tools\accel或C:\Users\你\accel)。建议避开带空格/中文的路径, 也别放进Program Files(写入 hosts 不需要管理员,但放系统目录反而容易权限纠缠)。 - 把
accel.zip解压到那个目录(保留内部目录结构,install-task.ps1/run-hidden.vbs要和accel.mjs同级)。 - 装好 Node.js 20+(只依赖内置
fetch/zlib,无需npm install)。 - 首次先 dry-run 看判定、不写文件:
cd 你选的目录 node accel.mjs --dry -v - 确认无误后正式写入(自动备份原 hosts,只替换
# >>> accel-start >>>标记块):node accel.mjs
- (可选)热更新:装一个无窗口计划任务,按你想要的间隔自动重测重写 hosts + 刷新清单。
任务名默认
:: 默认每 10 分钟一轮 powershell -NoProfile -ExecutionPolicy Bypass -File install-task.ps1 :: 或:每天一轮(最省占用,推荐) powershell -NoProfile -ExecutionPolicy Bypass -File install-task.ps1 -IntervalMinutes 1440 :: 或:每 6 小时一轮 powershell -NoProfile -ExecutionPolicy Bypass -File install-task.ps1 -IntervalMinutes 360
QoderAccel(config.json的task_name),想换名加-TaskName 你的名字。 装之前脚本会探测 hosts 是否可写:当前用户能写就 Limited 不提权;不能写且非管理员会直接报错,不会装一个「跑十年什么都不干」的死任务。 - 回滚:
node accel.mjs --revert schtasks /delete /tn QoderAccel /f
把下面这段交给一个能跑 PowerShell + 有网络 + 装了 Node 的 agent 即可(它会自己选目录、下载、装热更新):
# Agent 安装 accel(无交互):选目录 -> 镜像源下载 -> 解压 -> 装每天一轮热更新
$InstallDir = "C:\tools\accel" # 让 agent/用户改这一行选安装目录
$Node = (Get-Command node -ErrorAction SilentlyContinue).Source
if (-not $Node) { Write-Error "请先装 Node.js 20+ (https://nodejs.org)"; exit 1 }
if (-not (Test-Path $InstallDir)) { New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null }
# 优先 jsDelivr 镜像直链;失败回退 GitHub release 直链
$Zip = Join-Path $env:TEMP "accel.zip"
$urls = @(
"https://cdn.jsdelivr.net/gh/jingjiangze/github-accel@pkg/accel.zip",
"https://github.com/jingjiangze/github-accel/releases/download/accel-latest/accel.zip"
)
$ok = $false
foreach ($u in $urls) {
try { Invoke-WebRequest -Uri $u -OutFile $Zip -UseBasicParsing -TimeoutSec 60; if ((Get-Item $Zip).Length -gt 1000) { $ok = $true; break } }
catch { Write-Warning ("下载失败: " + $u) }
}
if (-not $ok) { Write-Error "两个直链都下载失败"; exit 1 }
Expand-Archive -Path $Zip -DestinationPath $InstallDir -Force
Set-Location $InstallDir
# 先 dry-run 确认不会写坏,再正式写入
node accel.mjs --dry -v
node accel.mjs
# 装每天一轮热更新(最小占用);要更频繁改 -IntervalMinutes
powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $InstallDir "install-task.ps1") -IntervalMinutes 1440
Write-Output ("accel 已装到 " + $InstallDir + ",任务 QoderAccel 每天一轮")要点:① 安装目录由 $InstallDir 一处控制,agent 可提示用户选择;② 下载优先走 jsDelivr 镜像、回退 GitHub;
③ 解压后先 --dry 再正式写入;④ 热更新间隔由 -IntervalMinutes 决定(1440=每天、360=每 6h、默认 10);
⑤ 全部零依赖、无常驻进程、不写系统目录之外任何东西。
这些是本仓库所有判定的来源,都是同一台机器同一时段的实测数字,不是转述:
- 社区列表的"最快 IP"是境外 Runner 测的。GitHub520 给的 4 个关键条目在本线路 0/4 可用(
20.205.243.166主页拿到 200 后 body 卡死、185.199.111.133对 objects 直接失败、59.24.3.173对 gist 失败)。同一个列表换一条线路结论就变,所以候选只当作"待测集合"。 - 干扰针对
github.com这条业务本身,与 IP 无关。同一台主机20.205.243.165上只换 SNI:codeload.github.com→ 301 正常,github.com→ TLS 完成后不回 body。所以主页 / release 首跳 /git clone无法靠固定 IP 修,必须走反代。 - 只写 IPv4 的 hosts 会被 AAAA 抢先。给
raw.githubusercontent.com固定 IPv4 后,真实请求仍走2606:50c0:8003::154并超时。本工具因此对候选同时探 A/AAAA 记录,两族都实测可用才写,否则跳过。 - 反代入口需要内容级体检。仅看 HTTP 200 会把自建拦截页当"通"(某入口对 2.4KB 样本回 200 但只有 543 字节的错误页,另一入口小文件正常、5MB 样本直接断流)。
gh.mjs doctor用固定内容标记 + 大文件带宽样本两级判定,并按 MB/s 排序选路。
早期版本把候选 IP 写死在 config.json 的 static_ips,每轮再把第三方列表里的所有 IP 探一遍。两个问题:
列表会过期(同一份 GitHub520 换条线路结论就变),而且每轮都从头随机——昨天好用的 IP 今天可能连试都不试。
现在分两层:
发现层(discover.mjs,默认 6 小时刷一次,accel.mjs 到期自动触发)
| 来源 | 用途 | 传输 |
|---|---|---|
GitHub GET /meta |
按服务(web / api / git / pages)取官方 CIDR,每个 /24 抽一个样本 | 必须直连——实测经代理 403(CF 出口被 GitHub 拉黑) |
Cloudflare ips-v4 / ips-v6 |
给 CF 前置的域(jsDelivr、npm registry) | 直连 |
| 多 DoH(Cloudflare + Google) | 当前真实 A/AAAA 答案,配额不够时优先保它 | 经代理 |
| GitHub520 / GitHub-IP-hosts | 只当 hint,不直接写 hosts | 直连 |
产物是 candidates.json。官方网段只做抽样、不扫全段——这是 CloudflareSpeedTest 控成本的做法。
每个源都带自己的 via(direct / proxy),因为"哪个源能走哪条路"在本机并不一致。
健康池(pool.mjs → pool-state.json):每个(域,IP)留历史样本,按
可用率 35 + 延迟P50 20 + 延迟P95 10 + 速度P50 20 + 速度P10 5 + 抖动 5 + 新鲜度 5 评分,
再分进 active / reserve / explorer / quarantine 四个桶(连续失败 3 / 5 / 8 次 → 30 分钟 / 2 小时 / 24 小时冷却)。
每轮只探 pool.probe_per_round(默认 8)个:其中 explore_ratio(默认 15%)留给没试过的,其余给历史好的;
同 /24(v6 同 /64)最多 max_per_prefix(默认 2)个,避免一撮候选落在同一个故障域。
速度直接从已有探测的 bytes/ms 推导、不额外下载,且只有响应体 ≥ speed_min_bytes(默认 64 KiB)才计入——
小响应算出来的"速度"其实是延迟倒数。
node discover.mjs --force -v :: 手动刷新候选
node pool.mjs show :: 看各域分桶与评分
node pool.mjs show github.com :: 只看某个域--dry 不触发发现、也不写池子(严格只读)。
config.json 的 proxies 是手挑的主清单,会随时间烂掉(入口挂掉、或变成返回 200 的拦截页),
而只有本机实测才知道哪条线路快。清单只解决另一件事——「这个入口本身还活着吗」——它跟线路无关,
所以可以放到中立出口去做:
.github/workflows/proxy-manifest.yml定期(默认 6 小时)在 GitHub Actions 上跑node manifest.mjs, 对manifest-seed.json(人工维护的「待测集合」)∪config.json的proxies/proxies_explore逐个做 内容级体检(必须取回约定 canary 内容)+ 带宽样本(真拉够一段数据),结果写成proxies.json, 发布到manifest分支(单提交 + force-push,天然滚动更新,不留历史)。- 本机
gh.mjs按config.json的manifest段取这份清单(先 jsDelivr 镜像、再 raw 兜底,全失败退回本地缓存), 把ok=true的入口并进候选池、标成[清]。这些入口每轮都会在本机实测(内容级 + 带宽样本), 开着manifest.race(默认开)就参与三路竞速——竞速排名用的是本机这次实测的数字, runner 的远程速度数字从不参与;关掉manifest.race则退回「只低频测量、不参与竞速」。 候选池(proxies_explore)因为样本可能是一小时前的,仍不参与竞速(见下方)。
判定与 gh.mjs doctor 同源:ok = 取回约定内容 且 大文件样本过了 bulk_min_bytes / bulk_min_bps。
只回 200 没有约定内容的拦截页、或只回几百字节错误页的,都进不了 live。
为什么不做成「Actions 直接产出 hosts 里的 IP」:IP 的优劣强依赖线路,境外 runner 测出来的「最快 IP」 在本线路可能 0 可用(README 开头第 1 条就是这件事的实测)。清单因此只筛入口,不发 IP。
runner 在数据中心,排名对它没有意义。真正像用户线路的是那台常开的盒子(山东联通,出口 222.175.202.106):
box/run-probe.ps1在盒子上常驻:计划任务AccelProbe(开机自启 + 30 分钟看门狗、S4U +HighestAvailable、-WindowStyle Hidden、ExecutionTimeLimit=PT0S,全部无窗口静默),每 6 小时一轮 —— 先从仓库拉最新脚本 → 跑manifest.mjs→ 把结果推到manifest-box分支(单提交 force-push,与 CI 同形) → 再跑node accel.mjs把本机这条线路的最优 hosts 钉进盒子自己的 hosts(见下「盒子也应用 hosts」)。- 不在盒子上手抄文件:脚本从仓库拉,改
box/run-probe.ps1就等于改了盒子上的行为。 - 推送用一把只属于本仓库的 write deploy key(盒子侧
D:\accel\.ssh\box_deploy);拉取是公开读,不需要凭据。 要停用:GitHub → Settings → Deploy keys 删掉那一把(盒子上的任务也可以直接schtasks /End /TN AccelProbe)。 - 盒子侧实测:
raw.githubusercontent.com直连 000(被墙),但github.com/api.github.com/ 各加速入口都 200 —— 所以「体检走加速入口」在盒子上成立,清单本身则从manifest-box分支读。
本机 gh.mjs 把两个观测点的清单取并集(盒子在前,见 config.json 的 manifest.urls;被 max_entries
截断时留下的是盒子的入口)。doctor 会把来源打印成 box-shandong(4) + runner(8),一眼看出哪份来自谁。
盒子不只是观测点——它跑在真实国内线路上,所以同一套 accel.mjs 在盒子上也能产出针对盒子这条线的最优 hosts 钉,写进盒子自己的 C:\Windows\System32\drivers\etc\hosts 的 # >>> accel-start >>> 块。这样盒子这台常开机器访问 GitHub 也走加速,且随线路自动重测重写。
- 触发:复用
AccelProbe任务的同一次 6 小时循环(box/run-probe.ps1第 4 步Apply-Hosts),不新增任务、不新增 SSH 依赖。盒子每轮先git pull最新脚本,所以这里改box/run-probe.ps1就等于改了盒子行为。 - 写文件前先可写性预检:盒子若没有管理员写 hosts 的权限,
accel.mjs直接 no-op(写日志、不碰文件),不会把任务跑挂。 hosts块与本机独立:盒子钉的是盒子线路实测最优值,本机钉的是本机线路最优值,二者不共享——这正是「IP 优劣强依赖线路」这条结论的应用。- 审计:盒子侧
D:\accel\logs\hosts-*.log记录每轮 apply 的退出码与accel.mjs输出。 - 停用 hosts 应用:删掉
box/run-probe.ps1里的Apply-Hosts调用(或node accel.mjs --revert回滚盒子 hosts 块)。
装/重装盒子任务(一次性,只在重建时用):
ssh wbssh 'powershell -NoProfile -NonInteractive -Command -' < box/install-task.ps1产物形态(proxies.json):
{
"schema": 1,
"generated_at": "2026-10-10T11:26:28.020Z",
"runner": { "os": "Linux", "run_url": "https://github.com/.../actions/runs/..." },
"canary": { "url": "https://raw.githubusercontent.com/521xueweihan/GitHub520/main/hosts", "token": "GitHub520 Host Start", "bulk_url": "..." },
"thresholds": { "bulk_min_bytes": 262144, "bulk_min_bps": 131072 },
"counts": { "probed": 40, "ok": 4, "soft": 2, "dead": 34 },
"live": ["https://gh-proxy.com/", "https://ghfast.top/", "..."],
"entries": [{ "url": "https://gh-proxy.com/", "ok": true, "soft": true, "content": true, "range": true, "code": 200, "ms": 727, "bulk_bytes": 4208487, "bps": 1849072 }]
}node manifest.mjs --selftest :: 离线自测(打桩 fetch,不联网)
node manifest.mjs --out dist/proxies.json :: 在本机跑一遍(走的是本线路,仅用于自检)
node gh.mjs doctor --force --explore :: 看清单入口的本机实测(标 [清])节拍(重要):GitHub 的 schedule 在本账号上是饥饿的(实测 */10 五小时只命中一次),
所以 cron 只当兜底;真正的触发是 push 与手动 workflow_dispatch。需要外部节拍时,用带 workflow 权限的
token 打 repository_dispatch(type=manifest),不要指望 GitHub 的 cron。
config.json 的 manifest 段:enabled 开关;urls 清单地址(依次尝试);ttl_h 本地缓存有效期
(默认 6 小时);cache 本地缓存文件名(已 gitignore);max_entries 最多并进几个(默认 24,防止清单
膨胀把每轮体检拖长);only_ok 只取 ok=true 的(默认 true)。另有 manifest_probe_ms(单次探测上限)
与 bulk_max_bytes(带宽样本最多拉多少)供这一层使用。清单只加候选,改主清单 proxies 仍由用户决定。
config.json 的关键项:
discovery:候选发现——各来源的 URL 与via(direct/proxy)、refresh_h(默认 6 小时)、sample_per_24(每个 /24 抽几个)、max_per_domain、cf_domains(CF 前置的域)、gh_domains(域 → GitHub 服务名)。pool:健康池——probe_per_round(每轮每域探几个)、explore_ratio、min_samples、max_per_prefix、history_samples、active_ok_rate、score_weights、cooldown、speed_min_bytes。sources:候选 IP 列表源,jsDelivr 优先、raw.githubusercontent.com兜底(实测后者会间歇 5xx/超时)。现在只当 hint。domains:每个域名的探测路径canary、可接受状态码expect、strict(多轮必须全通过)、min_bytes(取回多少字节才算真拿到,github.com设 8192 以排除"200 + 头几 KB 后卡死"的假健康节点)、max_ms(慢到离谱的候选直接不写,github.com设 4000)。github.com必须strict。probe.win_ratio:比现状快多少才写入(默认 0.85,即至少快 15%)。probe.retries/budget_ms:每 IP 探测轮数与单轮上限。probe.min_bytes:全局默认的最小取回体积(默认 1),按域可用domains.<域>.min_bytes覆盖。deny:永不固定的域名。proxies:主入口清单,每轮体检都会测。gh.mjs doctor --force重新体检。proxies_explore:候选池,只做低频测量、不参与竞速(它的样本可能是一小时前的,拿它选路等于用陈旧数据)。proxy_explore_every控制节奏(默认 6 轮 ≈ 1 小时);doctor --explore可强制探一次,测出可用的会点名提示你加进proxies。manifest:反代入口的动态清单(Actions 在中立出口体检出的 live 入口,本机每轮再实测一遍,标[清])。见下文专节。manifest.race(默认true):开时[清]入口参与三路竞速(用本机这次实测排名);关则退回只测不参与竞速。manifest.ttl_h/cache/max_entries/only_ok/urls:取源与缓存策略。
proxy_ttl_s:入口体检缓存(默认 60 秒)。proxy_probe_concurrency:入口体检并发数(默认 4)。proxy_history_samples:每个入口保留多少轮历史样本,用于算稳定性/分位数。proxy_score_weights/proxy_cooldown/proxy_freshness_ms:入口评分权重、连续失败后的冷却阶梯(3 次→30 分钟、5 次→2 小时、8 次→24 小时)与新鲜度衰减常数。proxy_race_diversity:竞速取三路时尽量落在不同 host(最稳 / 本轮最快 / 样本最少的探索位),避免三个入口同属一个故障域。bulk_min_bytes/bulk_min_bps:大文件样本的最低体积与最低速度;过不了只能算SOFT(内容标记对但带宽不达标),带宽门槛全灭时竞速才退化为只用SOFT。race:并发竞速参数——max同开入口数、first_byte_ms首字超时、speed_limit_bps/speed_time_ms停滞判定、retries_per_entry续传次数。clone:same_retry原地重试、low_speed_limit/low_speed_time对应http.lowSpeedLimit/Time。
产物:logs/(每轮判定)、report.json(逐域明细,含每个 IP 的实测毫秒)、backups/(原 hosts,保留 5 份)、proxy-state.json(入口状态 v2:每轮的原始结果 + 历史样本、评分、失败串与冷却期)。
-
所有取件入口(下载与
clone)都过同一道闸门:只接受 GitHub 相关 host(github.com、*.githubusercontent.com、*.github.com),非 GitHub 域一律拒绝——它不是通用代理;URL 内嵌账密、oauth2:、x-access-token、ghp_*/github_pat_*,以及?access_token=/?authorization=之类的查询参数直接拒绝外送。私有仓库请自建反代或走代理节点。 -
自建
relay/的密钥走在 URL 路径里,因此它会出现在 Cloudflare 侧的请求日志与统计中,知道完整 URL 的人即可使用该入口(限 GitHub 域)。当作"你自己知道的一串地址"来管理,需要轮换时改RELAY_KEY重写一次 secret 再部署。 -
密钥不落盘:
gh.mjs只在内存里展开真实 relay 地址,proxy-state.json、report.json、doctor/list输出与竞速日志里一律是别名__RELAY__/<REDACTED>;旧版本已经写下的真实 URL 在读取时会被迁移成别名。 -
hosts 只替换
# >>> accel-start >>> ... # <<< accel-end <<<标记块,其余行原样保留;每轮先备份。 -
Clash/mihomo 的 TUN 或系统代理任一开启后,内核不读系统 hosts(
use-system-hosts: false),A 层会被架空——此时应把 GitHub 域名规则改为直连组,或只依赖 B 层。
公开反代看得到你要下载的完整 URL,还会透传请求头。relay/ 是自带的 Cloudflare Pages Functions
端点,用来把这层暴露收回去:
- 路径
/r/<密钥>/https://github.com/...;密钥用定长比较,不匹配直接 403; - 域名白名单只放行 GitHub 相关 host,
example.com之类一律 403 —— 它不是开放代理; - 跳转逐跳校验:用
redirect: 'manual'自己跟跳(上限 5 跳),每一跳都重新过白名单。redirect: 'follow'只校验了第一跳,而 release 资产本来就 302 到release-assets.githubusercontent.com——第二跳不校验就等于开了个"允许重定向的开放代理"; - 透传
Range/If-Range/If-None-Match/If-Modified-Since,响应侧回传Content-Range/Accept-Ranges/Content-Disposition/ETag/Last-Modified。不透传Range的话,客户端设计好的"停滞换源续传"在 relay 这条路上会退化成从 0 重下; - 默认剥离客户端的
Authorization/Cookie(只有显式设RELAY_ALLOW_AUTH=1才透传); - 不做
api.github.com中转:CF 出口 IP 访问 api 一律 403(GitHub 拉黑 CF 段),这是实测结论。
部署(需要自己的 CF 账号,全程可删):
cd relay
npx wrangler pages project create gh-accel-relay --production-branch main
npx wrangler pages deploy --branch mainRELAY_KEY 以 secret 形式写进 Pages 环境变量,本地副本放 relay-key(已 gitignore,
仓库是公开的,密钥不入库)。配好后 config.json 的 relay.base 指向
https://<项目名>.pages.dev/r/KEY/,gh.mjs 会自动把它插到候选入口最前面参与测速排序。
坑(踩过的):Pages 的 functions/ 必须在项目根(和 wrangler.toml 同级)。把它放进部署目录
public/ 里时,wrangler 会静默打印 No Functions. Shimming...,所有请求直接返回静态页——
密钥闸门形同虚设。本仓库的结构是 relay/wrangler.toml + relay/functions/ + relay/public/。
同一条线路的入口带宽会在一次请求的量级上翻转,实测(同一 5.2MB 文件,间隔几十秒):
体检排名 ghfast 3.69 → gh.llkk 3.53 → gh-proxy 2.86 → relay 2.36 MB/s
按排名单走 gh-proxy 3.45 MB/s
三路并发竞速 6.68 MB/s(胜出者:体检只排第 2 的 gh.llkk.cc)
所以 gh.mjs 不消费"上次测速第一名":每次请求同时开 race.max 个入口,
first_byte_ms 内拿不到字节的踢掉,增速低于 speed_limit_bps 判停滞并带 Range 换源续传,
先拿完整的胜出,其余立即终止、.part 清掉。体检缓存也从 15 分钟降到 proxy_ttl_s(默认 60s)。
选谁去竞速也不再看"最近一次谁最快":每个入口保留历史样本,按 稳定性 30% + 速度P50 25% + 速度P10 15% + 首字节延迟 15% + 失败率 10% + 新鲜度 5% 打分,连续失败进入冷却阶梯(3 次→30 分钟、5 次→2 小时、8 次→24 小时), 再取"最稳 / 本轮最快 / 样本最少的探索位"三路并尽量落在不同 host 上——避免三个入口同属一个故障域。 这正是"体检排名"和"长期可用"会分叉的地方:入口 A 跑出 3.7/2.4/6.1 MB/s,入口 B 稳定在 3.0/3.2/3.1 MB/s, 只看最近一次 A 赢,长期看 B 才该排前面。
2026-10-07 实测(18 个候选入口,各 3 轮):只有 ghfast.top、gh-proxy.com、ghproxy.net 过了内容级体检。
其余要么连接失败(code=0,多数是 4 秒内直接连不上),要么回了 200 却没有约定内容
(gitproxy.click、gh-proxy.net 这类——正是"只看状态码会被骗"的例子),ghgo.net 回的是 468。
所以主清单精简成这 3 个加自建 relay,死掉的那批移进 proxies_explore 低频复查,不再每轮都去撞。
clone 同源问题:GitHub 按 User-Agent 决定回 pkt-line 还是 HTML,所以先并发探
info/refs 只留真 pkt-line 的入口,再克隆;schannel 随机握手失败(实测约 1/3 概率)会原地重试一次
(clone.same_retry),并用 http.lowSpeedLimit/Time 让断流快速暴露而不是干等。
实测 cli/cli 43MB 浅克隆 9.2s,同期直连要么 21s 连不上要么 invalid index-pack 断流。
这台机器 enable_tun_mode: true 时,系统 DNS 由 mihomo 接管(返回 fake-ip),而 mihomo
不读系统 hosts(use-system-hosts: false),所以 hosts 层要生效必须让目标域名从 fake-ip
放出来(dns.fake-ip-filter + use-hosts: true),并按域写 DIRECT 规则。实测结果:
| 配置 | raw | objects | 备注 |
|---|---|---|---|
| 原始(全走隧道) | 2/3,1.1–2.2s | 1/3,2.15s | |
| GitHub 静态域写死 DIRECT | 1/3,成功时 0.70s | 0/3 | 本线路对 objects/gist/assets 没有任何可用直连 IP |
| 回滚后(同一份原始配置) | 3/3,0.20s | 3/3,0.19s | 与改动无关,只是线路自己变好了 |
第三行是重点:同一份配置几分钟内从 1/3 变 3/3,所以静态分流方案的收益无法与波动区分,
而代价是全局流量路径的风险。要真做自适应分流,正解是 mihomo 的 fallback 组(DIRECT ↔ 节点,60s 体检)。
用户提出「hosts 侦测到 Clash 后自动写入当前节点」。逐条实测:
| 设想/做法 | 实测结论 |
|---|---|
| 读 Clash 当前节点 | ✅ 能。TCP 控制器常被关,只有命名管道(\\.\pipe\verge-mihomo-*,必须枚举——配置里写的名字已过期)。用 node:http 的 socketPath 读最稳 → 见 clash.mjs。注意 /proxies 不返回节点 server(安全设计),所以"读节点 IP"不可靠。 |
| 把节点 IP 写进 hosts | ❌ 不成立。节点是 Cloudflare IP、只认 SNI=订阅域;把它的 IP 当目标域名地址,实测 3/3 失败(连接重置 / TLS 握手失败)。hosts 只能做「域名→真实服务器 IP」,做不了「域名→代理」。 |
让 mihomo 采纳 hosts 钉(dns.use-system-hosts: true) |
❌ 实测无效。置 true 后问 mihomo 自己的 DNS(/dns/query):api.github.com 返回 20.205.243.168,而 hosts 钉的是 20.27.177.116 → 没采纳。与上面"顶层 hosts: 不被采用"是同一条结论的两面。 |
| 加自定义组 | ✅ 走"脚本档"可以(profiles/Script.js 里改 config['proxy-groups'])。补一条上面那条的边界:Merge 档加 proxy-groups 会被内核校验拒,脚本档不会被拒。 |
因此本工具与 Clash 的正确关系是"各管各的、互不干扰":
- hosts 层(本工具) 只服务直连流量(不吃系统代理的 CLI / 软件)。
- Clash 层 服务走代理的流量,用它自己的 DNS;它不会用 hosts 的钉,也无法让它用。
- 唯一有意义的协作是路由层面:把 github 家族规则指到「直连优先」组,让走代理的 github 也走直连(避开烂节点)。
这需要改 Clash 配置(见下),不属于本仓库的职责,本仓库只提供
clash.mjs侦测 + 报告。
尝试过并已回滚(2026-10-13,证据留档免得再踩):给 Clash 加 GH直连优先 fallback 组
([DIRECT, 🚀 节点选择])+ 前置 github 规则,让走代理的 github 改走直连。回滚原因(两条实测):
- 没有收益:mihomo 不用 hosts 钉(见上表第 3 行),所以"直连优先"拿不到 accel 的优选 IP。
- 引入回归:mihomo 的
fallback对DIRECT不做有效体检(实测now=DIRECT而alive=false, 健康检查Timeout也不切换),于是 github 被强制直连;github.com 直连不通时, 连git push经代理都失败(本机实测回归,回滚后立即恢复)。
→ 结论:不要用 fallback 组把域从节点改到 DIRECT。让订阅的规则保持原样;
hosts 与 Clash 的正确关系就是"各管各的"(hosts 服务直连流量,Clash 服务代理流量)。
本仓库只提供 clash.mjs 侦测 + 报告,不碰你的代理配置。
| 场景 | 结果 |
|---|---|
api.github.com 经代理 |
200 / 0.18s(走 GH 组 → DIRECT) |
github.com 经代理 |
超时 / TLS 失败(该域当前未被钉,且直连本身也抖动) |
www.google.com 经代理 |
200 / 6.4s(走节点,符合预期) |
| GH 组健康检查(探测 github.com) | 本次 Timeout → 按设计回落节点 |
直连 api.github.com |
200 / 0.19s(hosts 钉生效) |
- creazyboyone/FastGithub —— "加速器式"本地加速的形态参照(改 hosts / 本地反代一体化)。本项目是零依赖脚本层,不常驻进程、不改系统证书。
- ittuann/GitHub-IP-hosts —— 候选 IP 列表源之一,本仓库将其与 GitHub520 互为候选源。
- 521xueweihan/GitHub520 —— 候选 IP 列表源之二。
- hunshcn/gh-proxy —— 反代入口的自建方案(Cloudflare Workers),需要私有入口时用它。
以上游项目的文档/数据改进,均以 PR 形式提交并等待审查,不在本地改动别人的项目。
MIT