Skip to content

About

Windows GitHub 加速层:自适应 hosts 钉实测更快的 IP + 反代入口竞速取件,零依赖 Node、无常驻进程;入口清单由 GitHub Actions 与一台常开盒子两个观测点每 6 小时体检后自动发布(只筛入口、不发 IP),另含可选的自建 Cloudflare Pages 私有中转端点。

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

accel —— 本机实测选路的 GitHub 加速层(hosts + 反代)

面向 Windows(Git Bash / PowerShell 皆可)。两层互补,各自解决实测确认不同的故障面:

稳定性策略(2026-10)

  • 不改 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;.NET File.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(备份+直写+读回的安全提交)。

覆盖哪些域(2026-10-13 实测口径)

进 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

v2 重写要点(2026-10-13)

依据当日完整审计与一次真实事故的教训(细节见仓库提交记录):

  • 写入安全:只能"直写"(见上"关于权限");安全来自流程——写前备份、写后读回、失败自恢复、缺失守卫。
  • 复核增强:整链 + 分族两层;失败重试 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 脚本目录,不写注册表、不留常驻进程。安装 = 解压到任意目录 +(可选)装计划任务。

人类安装指南

  1. 选一个安装目录(随便放哪都行,例如 D:\tools\accel 或 C:\Users\你\accel)。建议避开带空格/中文的路径, 也别放进 Program Files(写入 hosts 不需要管理员,但放系统目录反而容易权限纠缠)。
  2. 把 accel.zip 解压到那个目录(保留内部目录结构,install-task.ps1 / run-hidden.vbs 要和 accel.mjs 同级)。
  3. 装好 Node.js 20+(只依赖内置 fetch / zlib,无需 npm install)。
  4. 首次先 dry-run 看判定、不写文件:
    cd 你选的目录
    node accel.mjs --dry -v
  5. 确认无误后正式写入(自动备份原 hosts,只替换 # >>> accel-start >>> 标记块):
    node accel.mjs
  6. (可选)热更新:装一个无窗口计划任务,按你想要的间隔自动重测重写 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 不提权;不能写且非管理员会直接报错,不会装一个「跑十年什么都不干」的死任务。
  7. 回滚:
    node accel.mjs --revert
    schtasks /delete /tn QoderAccel /f

给 Agent / 自动化安装指南

把下面这段交给一个能跑 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); ⑤ 全部零依赖、无常驻进程、不写系统目录之外任何东西。

为什么不做成"直接改 hosts 就完事"的加速器

这些是本仓库所有判定的来源,都是同一台机器同一时段的实测数字,不是转述:

  1. 社区列表的"最快 IP"是境外 Runner 测的。GitHub520 给的 4 个关键条目在本线路 0/4 可用(20.205.243.166 主页拿到 200 后 body 卡死、185.199.111.133 对 objects 直接失败、59.24.3.173 对 gist 失败)。同一个列表换一条线路结论就变,所以候选只当作"待测集合"。
  2. 干扰针对 github.com 这条业务本身,与 IP 无关。同一台主机 20.205.243.165 上只换 SNI:codeload.github.com → 301 正常,github.com → TLS 完成后不回 body。所以主页 / release 首跳 / git clone 无法靠固定 IP 修,必须走反代。
  3. 只写 IPv4 的 hosts 会被 AAAA 抢先。给 raw.githubusercontent.com 固定 IPv4 后,真实请求仍走 2606:50c0:8003::154 并超时。本工具因此对候选同时探 A/AAAA 记录,两族都实测可用才写,否则跳过。
  4. 反代入口需要内容级体检。仅看 HTTP 200 会把自建拦截页当"通"(某入口对 2.4KB 样本回 200 但只有 543 字节的错误页,另一入口小文件正常、5MB 样本直接断流)。gh.mjs doctor 用固定内容标记 + 大文件带宽样本两级判定,并按 MB/s 排序选路。

候选从哪来:发现(discover.mjs)+ 健康池(pool.mjs)

早期版本把候选 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 不触发发现、也不写池子(严格只读)。

反代入口的动态清单(manifest.mjs + GitHub Actions)

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),一眼看出哪份来自谁。

盒子也应用 hosts(热更新)

盒子不只是观测点——它跑在真实国内线路上,所以同一套 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 层。

私有中转端点(relay/,可选)

公开反代看得到你要下载的完整 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 main

RELAY_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 断流。

关于 Clash / TUN 的尝试(已回滚,结论记在这里免得再踩)

这台机器 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 体检)。

2026-10-13 复核:结论细化(含「hosts 侦测 Clash」的可行性)

用户提出「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 改走直连。回滚原因(两条实测):

  1. 没有收益:mihomo 不用 hosts 钉(见上表第 3 行),所以"直连优先"拿不到 accel 的优选 IP。
  2. 引入回归:mihomo 的 fallback 对 DIRECT 不做有效体检(实测 now=DIRECT 而 alive=false, 健康检查 Timeout 也不切换),于是 github 被强制直连;github.com 直连不通时, 连 git push 经代理都失败(本机实测回归,回滚后立即恢复)。

→ 结论:不要用 fallback 组把域从节点改到 DIRECT。让订阅的规则保持原样; hosts 与 Clash 的正确关系就是"各管各的"(hosts 服务直连流量,Clash 服务代理流量)。 本仓库只提供 clash.mjs 侦测 + 报告,不碰你的代理配置。

实测记录(2026-10-13,供复核)

场景 结果
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 形式提交并等待审查,不在本地改动别人的项目。

License

MIT

About

Windows GitHub 加速层:自适应 hosts 钉实测更快的 IP + 反代入口竞速取件,零依赖 Node、无常驻进程;入口清单由 GitHub Actions 与一台常开盒子两个观测点每 6 小时体检后自动发布(只筛入口、不发 IP),另含可选的自建 Cloudflare Pages 私有中转端点。

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages