ghprobe 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,166 +1,164 @@
1
- # ghprobe 默认配置(用户可用 --config 覆盖,键名一致即可)
2
- #
3
- # ⚠️ 关于升级:`ghprobe install` 会把本文件**整份**复制成用户配置
4
- # (%LOCALAPPDATA%\ghprobe\config.yaml),并同时存一份**快照**
5
- # (config.default.snapshot.yaml,用来判断你到底改过哪些键)。
6
- # 加载时做**三方合并**:
7
- # · 你**没改过**的键(值仍等于快照) -> 跟随本文件的最新值 ← 默认值的改进自动到达
8
- # · 你**改过**的键 -> 保留你的值
9
- # · 你自己加的键 / 本文件新增的键 -> 都保留
10
- # 所以升级后不需要手动同步配置。想看有哪些差异:`ghprobe config-diff`。
11
- # (本文件里的注释只是说明,不参与合并 —— 合并看的是键值。)
12
- # 目标域名族:每域名独立探测、独立写 hosts
13
- domains:
14
- - github.com
15
- - codeload.github.com
16
- - objects.githubusercontent.com
17
- - raw.githubusercontent.com
18
- - gist.githubusercontent.com
19
- - avatars.githubusercontent.com
20
-
21
- # DoH 端点(全部 IP 直连,绕开解析 DoH 域名的死锁;默认只放国内实测可达端点)
22
- # sni 填服务域名:TLS 握手按它验证书(RFC 6066 不允许拿 IP 当 SNI)
23
- doh:
24
- - { name: alidns1, host: 223.5.5.5, sni: dns.alidns.com, path: /dns-query }
25
- - { name: alidns2, host: 223.6.6.6, sni: dns.alidns.com, path: /dns-query }
26
- - { name: dnspod, host: 1.12.12.12, sni: doh.pub, path: /dns-query }
27
-
28
- # 内置候选 IP 池:补足 DoH 拿不到的候选。
29
- # 🔴 池子必须够宽,否则会出现「明明有活 IP,工具却报 stuck」——见下面 azure 段的说明。
30
- ipPools:
31
- # 种子池:**最稳定的入口,建议不要删**。属于 GitHub 自租段 140.82.112.0/20,多年未变。
32
- # 两个用途:① github.com / codeload 的兜底候选;
33
- # ② 拉 api.github.com/meta 更新 auto 池时的**直连入口**(该域名 DNS 在国内被污染,
34
- # 只能靠这些 IP + SNI 直连;而且这一步恰恰发生在「还没有可用 IP」的时候)。
35
- seed:
36
- - 140.82.112.3
37
- - 140.82.112.4
38
- - 140.82.113.3
39
- - 140.82.113.4
40
- - 140.82.114.3
41
- - 140.82.114.4
42
- - 140.82.116.3
43
- - 140.82.116.4
44
- - 140.82.121.3
45
- # Fastly 段:服务 githubusercontent 系(raw / objects / gist / avatars),多年不变
46
- fastly: [185.199.108.133, 185.199.109.133, 185.199.110.133, 185.199.111.133]
47
- # GitHub 自租段:服务 github.com / codeload.github.com 主站。
48
- # 🔴 2026-10 实测:这些 IP **在分钟级抖动** —— 同一批 IP 几分钟前全灭、现在 9/20 可用,
49
- # 且「活的是哪几个」每次都不一样。所以池子必须宽(靠 tcpMs 短超时兜住成本),
50
- # 只留少量 IP 必然出现「明明有活 IP 却报 stuck」。
51
- # ⚠️ 这一池**会过期**(2026-10-11 实测:24 个里有 4 个已不在官方 web 段)——
52
- # 它是「离线兜底」,真正跟得上变化的是下面 poolUpdate 自动维护的 `auto` 池。
53
- azure:
54
- # Azure 段(亚太/欧洲)
55
- - 20.200.245.247
56
- - 20.201.28.151
57
- - 20.207.73.82
58
- - 20.217.135.5
59
- - 20.233.83.145
60
- - 20.248.137.48
61
- - 20.26.156.215
62
- - 20.29.134.23
63
- - 20.87.245.0
64
- - 20.205.243.166
65
- - 20.205.243.167
66
- - 20.205.243.168
67
- - 4.237.22.34
68
- - 20.27.177.113
69
- - 4.208.26.197
70
- # 140.82.112.0/20(美国,web/api 走 .3 / .4)
71
- - 140.82.112.3
72
- - 140.82.112.4
73
- - 140.82.113.3
74
- - 140.82.113.4
75
- - 140.82.114.3
76
- - 140.82.114.4
77
- - 140.82.116.3
78
- - 140.82.116.4
79
- - 140.82.121.3
80
-
81
- # 域名 -> 只探哪些池(不配 = 用全部池)。
82
- # 两个池互不相干:185.199.x.x 永远不给 github.com 服务,20.x 也不给 raw.githubusercontent.com
83
- # 服务。分开能省掉近一半候选(冷启动最明显)。池名写错时自动退回「用全部池」。
84
- # auto = poolUpdate 自动从 api.github.com/meta 拉下来的最新网段(缓存不存在时该池为空,自动跳过)。
85
- # 顺序有意义:靠前的池先占 maxPoolIps 名额。
86
- domainPools:
87
- github.com: [auto, seed, azure]
88
- codeload.github.com: [auto, seed, azure]
89
- objects.githubusercontent.com: [fastly]
90
- raw.githubusercontent.com: [fastly]
91
- gist.githubusercontent.com: [fastly]
92
- avatars.githubusercontent.com: [fastly]
93
-
94
- # 候选池自动更新:从 GitHub 官方 api.github.com/meta 拉最新网段,缓存到用户目录,供 `auto` 池使用。
95
- # 为什么需要:GitHub 的 Azure 段在变,内置 azure 池会过期(实测 24 个里 4 个已失效)。
96
- # 🔴 api.github.com/meta 未认证限 **60 次/小时** —— 所以 intervalHours 只能大(默认每天 1 次),
97
- # 绝不要调到 1 以下。
98
- # ⚠️ 若 api.github.com 被 SNI 层封锁(换 IP 也救不回),**失败后同样等满一个周期再试**,
99
- # 不会反复重试 —— 那种情况下继续重试没有意义,用现有池 + DoH 继续工作即可。
100
- # 手动触发:`ghprobe update-pool`(不受周期限制);自动触发:每次 health(到点才真去拉)。
101
- # 缓存文件:%LOCALAPPDATA%\ghprobe\pool.cache.json(自动生成,不用手改)
102
- poolUpdate:
103
- enabled: true
104
- intervalHours: 24 # 更新间隔(小时);成功与失败同一档
105
- source: "https://api.github.com/meta"
106
- host: api.github.com # TLS SNI(实际连的是 ipPools.seed 里的 IP)
107
- sections: [web, api] # 取 meta 里的哪些段
108
- maxIps: 48 # 缓存里最多保留几个 IP
109
- perBigNet: 4 # 大网段(/20、/22)每个采样几个 IP(不能全展开,/20 = 4096 个)
110
- timeoutMs: 8000
111
-
112
- # 本地网络检查:本机断网时直接跳过本轮(不探测、不更新池、不写 hosts)。
113
- # 为什么需要:断网时所有候选必然全灭,跑一轮纯属浪费,日志还会被「stuck / 全灭」刷屏,
114
- # 把「本机没网」和「GitHub 被墙」两种情况混在一起,反而看不出真故障。
115
- # 两步:① 有没有「已启用且带非回环 IPv4」的网卡(零成本);② TCP 连一下基准目标。
116
- # 基准目标必须是**国内任何正常网络都该能连上**的(默认阿里公共 DNS 的 DoH 端点),
117
- # 它只用来判断「本机能不能出去」,与 GitHub 是否可达无关。
118
- networkCheck:
119
- enabled: true
120
- probe: { host: 223.5.5.5, port: 443, timeoutMs: 2000 }
121
-
122
- # 被主机名黑名单掐掉的域名走兜底链:主候选全灭时改探测这些替身域名,把能通的那个写进 hosts。
123
- #
124
- # 两种写法都支持:
125
- # 简写 raw.githubusercontent.com: [cdn.jsdelivr.net]
126
- # 完整 raw.githubusercontent.com:
127
- # - { domain: my.mirror, path: "/gh/torvalds/linux@master/README", mustContain: ["Linux kernel"] }
128
- # 完整写法**自带探测判据** —— 第三关用它验证「这个 IP 真的在提供这份内容」。
129
- # 为什么判据必须跟着配置走:不同替身服务的 URL 形态不同,CDN 映射是
130
- # `/gh/<user>/<repo>@<ref>/<path>`,代理是 `/<完整原始 URL>`,写死在代码里就没法让用户自己加替身。
131
- #
132
- # 🔴 兜底域名本身也可能被封:实测 2026-10-11,`cdn.jsdelivr.net` 在当前网络下 TCP 连得上但 TLS 被掐
133
- # (被识别的是 TLS ClientHello 的形态,不是 IP)。所以这里给**多个候选**,探测时挑当下能通的那个。
134
- # ⚠️ `ghproxy.net` / `gh-proxy.com` 是**第三方 GitHub 代理**:请求会经过它们的服务器。
135
- # 介意的话把这两条删掉,只留 `cdn.jsdelivr.net` 即可。
136
- fallbacks:
137
- raw.githubusercontent.com:
138
- - cdn.jsdelivr.net
139
- - { domain: ghproxy.net, path: "/https://raw.githubusercontent.com/torvalds/linux/master/README", mustContain: ["Linux kernel"] }
140
- - { domain: gh-proxy.com, path: "/https://raw.githubusercontent.com/torvalds/linux/master/README", mustContain: ["Linux kernel"] }
141
-
142
- # 超时:tcpMs 只管 TCP 握手阶段(死 IP 快速淘汰),握手成功后自动切到 tlsMs。
143
- # 黑洞 IP 会一直挂到超时,所以这两段必须分开 —— 否则每加一个候选都要多等 tlsMs。
144
- timeouts: { dohMs: 5000, tcpMs: 2500, tlsMs: 6000, httpMs: 6000 }
145
- probe:
146
- maxCandidates: 8 # DoH 每域名最多取几个候选
147
- maxPoolIps: 32 # 内置池每域名最多补几个。**与 DoH 各自限额,不共享总上限**(见 probe.js)
148
- maxPerDomain: 4 # 每域名最多保留几个可用 IP(前 maxEntriesPerDomain 个会写进 hosts)
149
- concurrency: 10 # 全局同时握手的 socket 上限(跨域名共享)
150
- # hosts 写入:每域名写几条候选。
151
- # 🔴 2026-10-11 实测推翻旧假设「Windows 同一域名只取第一条」—— hosts 里同名多条会被
152
- # 解析器**全部返回**(保持文件顺序),且 curl / 浏览器会**逐个尝试**:黑洞 IP 排在前
153
- # 也能 fallback 到后面的活 IP(实测 http=200 / 0.22s)。所以多写 1 条备选 = 抖动时
154
- # 客户端自动兜底,把「断最长 1 分钟」变成「多等一个连接超时」。
155
- # 不要贪多:每多一个死 IP 就多叠一个连接超时;2 条是收益/代价的平衡点。1 = 退回旧行为。
156
- hosts:
157
- maxEntriesPerDomain: 2
158
- serve: { host: 127.0.0.1, port: 8930 }
159
- # 调度:固定频率健康检查,不做指数退避(哨兵一轮 <2s,退避省不下开销反而拉长最坏发现延迟)
160
- schedule:
161
- # 哨兵间隔(分钟):只测已写入 IP(3 条并发,健康时 ~1s),异常才触发全量重排。
162
- # 🔴 取 1 是刻意的:这是「IP 死了到自动换掉」的**最坏等待**。5 分钟时代码本身只要 20s 就能修好,
163
- # 但用户平均要多等 2.5 分钟 —— 卡在调度间隔上而不是卡在探测上。1 分钟是 Task Scheduler
164
- # 重复间隔的下限(New-ScheduledTaskTrigger 最小 1 分钟),已经到底了。
165
- healthMin: 1
166
- absentCooldownHours: 1 # 缺失域名冷却(小时):SNI 层封锁的域名全量也救不回,冷却内不重试
1
+ # ghprobe 默认配置(用户可用 --config 覆盖,键名一致即可)
2
+ #
3
+ # ⚠️ 关于升级:`ghprobe install` 会把本文件**整份**复制成用户配置
4
+ # (%LOCALAPPDATA%\ghprobe\config.yaml),并同时存一份**快照**
5
+ # (config.default.snapshot.yaml,用来判断你到底改过哪些键)。
6
+ # 加载时做**三方合并**:
7
+ # · 你**没改过**的键(值仍等于快照) -> 跟随本文件的最新值 ← 默认值的改进自动到达
8
+ # · 你**改过**的键 -> 保留你的值
9
+ # · 你自己加的键 / 本文件新增的键 -> 都保留
10
+ # 所以升级后不需要手动同步配置。想看有哪些差异:`ghprobe config-diff`。
11
+ # (本文件里的注释只是说明,不参与合并 —— 合并看的是键值。)
12
+ # 目标域名族:每域名独立探测、独立写 hosts
13
+ domains:
14
+ - github.com
15
+ - codeload.github.com
16
+ - objects.githubusercontent.com
17
+ - raw.githubusercontent.com
18
+ - gist.githubusercontent.com
19
+ - avatars.githubusercontent.com
20
+
21
+ # DoH 端点(全部 IP 直连,绕开解析 DoH 域名的死锁;默认只放国内实测可达端点)
22
+ # sni 填服务域名:TLS 握手按它验证书(RFC 6066 不允许拿 IP 当 SNI)
23
+ doh:
24
+ - { name: alidns1, host: 223.5.5.5, sni: dns.alidns.com, path: /dns-query }
25
+ - { name: alidns2, host: 223.6.6.6, sni: dns.alidns.com, path: /dns-query }
26
+ - { name: dnspod, host: 1.12.12.12, sni: doh.pub, path: /dns-query }
27
+
28
+ # 内置候选 IP 池:补足 DoH 拿不到的候选。
29
+ # 🔴 池子必须够宽,否则会出现「明明有活 IP,工具却报 stuck」——见下面 azure 段的说明。
30
+ ipPools:
31
+ # 种子池:**最稳定的入口,建议不要删**。属于 GitHub 自租段 140.82.112.0/20,多年未变。
32
+ # 两个用途:① github.com / codeload 的兜底候选;
33
+ # ② 拉 api.github.com/meta 更新 auto 池时的**直连入口**(该域名 DNS 在国内被污染,
34
+ # 只能靠这些 IP + SNI 直连;而且这一步恰恰发生在「还没有可用 IP」的时候)。
35
+ seed:
36
+ - 140.82.112.3
37
+ - 140.82.112.4
38
+ - 140.82.113.3
39
+ - 140.82.113.4
40
+ - 140.82.114.3
41
+ - 140.82.114.4
42
+ - 140.82.116.3
43
+ - 140.82.116.4
44
+ - 140.82.121.3
45
+ # Fastly 段:服务 githubusercontent 系(raw / objects / gist / avatars),多年不变
46
+ fastly: [185.199.108.133, 185.199.109.133, 185.199.110.133, 185.199.111.133]
47
+ # GitHub 自租段:服务 github.com / codeload.github.com 主站。
48
+ # 🔴 2026-10 实测:这些 IP **在分钟级抖动** —— 同一批 IP 几分钟前全灭、现在 9/20 可用,
49
+ # 且「活的是哪几个」每次都不一样。所以池子必须宽(靠 tcpMs 短超时兜住成本),
50
+ # 只留少量 IP 必然出现「明明有活 IP 却报 stuck」。
51
+ # ⚠️ 这一池**会过期**(2026-10-11 实测:24 个里有 4 个已不在官方 web 段)——
52
+ # 它是「离线兜底」,真正跟得上变化的是下面 poolUpdate 自动维护的 `auto` 池。
53
+ azure:
54
+ # Azure 段(亚太/欧洲)
55
+ - 20.200.245.247
56
+ - 20.201.28.151
57
+ - 20.207.73.82
58
+ - 20.217.135.5
59
+ - 20.233.83.145
60
+ - 20.248.137.48
61
+ - 20.26.156.215
62
+ - 20.29.134.23
63
+ - 20.87.245.0
64
+ - 20.205.243.166
65
+ - 20.205.243.167
66
+ - 20.205.243.168
67
+ - 4.237.22.34
68
+ - 20.27.177.113
69
+ - 4.208.26.197
70
+ # 140.82.112.0/20(美国,web/api 走 .3 / .4)
71
+ - 140.82.112.3
72
+ - 140.82.112.4
73
+ - 140.82.113.3
74
+ - 140.82.113.4
75
+ - 140.82.114.3
76
+ - 140.82.114.4
77
+ - 140.82.116.3
78
+ - 140.82.116.4
79
+ - 140.82.121.3
80
+
81
+ # 域名 -> 只探哪些池(不配 = 用全部池)。
82
+ # 两个池互不相干:185.199.x.x 永远不给 github.com 服务,20.x 也不给 raw.githubusercontent.com
83
+ # 服务。分开能省掉近一半候选(冷启动最明显)。池名写错时自动退回「用全部池」。
84
+ # auto = poolUpdate 自动从 api.github.com/meta 拉下来的最新网段(缓存不存在时该池为空,自动跳过)。
85
+ # 顺序有意义:靠前的池先占 maxPoolIps 名额。
86
+ domainPools:
87
+ github.com: [auto, seed, azure]
88
+ codeload.github.com: [auto, seed, azure]
89
+ objects.githubusercontent.com: [fastly]
90
+ raw.githubusercontent.com: [fastly]
91
+ gist.githubusercontent.com: [fastly]
92
+ avatars.githubusercontent.com: [fastly]
93
+
94
+ # 候选池自动更新:从 GitHub 官方 api.github.com/meta 拉最新网段,缓存到用户目录,供 `auto` 池使用。
95
+ # 为什么需要:GitHub 的 Azure 段在变,内置 azure 池会过期(实测 24 个里 4 个已失效)。
96
+ # 🔴 api.github.com/meta 未认证限 **60 次/小时** —— 所以 intervalHours 只能大(默认每天 1 次),
97
+ # 绝不要调到 1 以下。
98
+ # ⚠️ 若 api.github.com 被 SNI 层封锁(换 IP 也救不回),**失败后同样等满一个周期再试**,
99
+ # 不会反复重试 —— 那种情况下继续重试没有意义,用现有池 + DoH 继续工作即可。
100
+ # 手动触发:`ghprobe update-pool`(不受周期限制);自动触发:每次 health(到点才真去拉)。
101
+ # 缓存文件:%LOCALAPPDATA%\ghprobe\pool.cache.json(自动生成,不用手改)
102
+ poolUpdate:
103
+ enabled: true
104
+ intervalHours: 24 # 更新间隔(小时);成功与失败同一档
105
+ source: "https://api.github.com/meta"
106
+ host: api.github.com # TLS SNI(实际连的是 ipPools.seed 里的 IP)
107
+ sections: [web, api] # 取 meta 里的哪些段
108
+ maxIps: 48 # 缓存里最多保留几个 IP
109
+ perBigNet: 4 # 大网段(/20、/22)每个采样几个 IP(不能全展开,/20 = 4096 个)
110
+ timeoutMs: 8000
111
+
112
+ # 本地网络检查:本机断网时直接跳过本轮(不探测、不更新池、不写 hosts)。
113
+ # 为什么需要:断网时所有候选必然全灭,跑一轮纯属浪费,日志还会被「stuck / 全灭」刷屏,
114
+ # 把「本机没网」和「GitHub 被墙」两种情况混在一起,反而看不出真故障。
115
+ # 两步:① 有没有「已启用且带非回环 IPv4」的网卡(零成本);② TCP 连一下基准目标。
116
+ # 基准目标必须是**国内任何正常网络都该能连上**的(默认阿里公共 DNS 的 DoH 端点),
117
+ # 它只用来判断「本机能不能出去」,与 GitHub 是否可达无关。
118
+ networkCheck:
119
+ enabled: true
120
+ probe: { host: 223.5.5.5, port: 443, timeoutMs: 2000 }
121
+
122
+ # 被主机名黑名单掐掉的域名走兜底链:主候选全灭时改探测这些替身域名,把能通的那个写进 hosts。
123
+ #
124
+ # 两种写法都支持:
125
+ # 简写 raw.githubusercontent.com: [cdn.jsdelivr.net]
126
+ # 完整 raw.githubusercontent.com:
127
+ # - { domain: my.mirror, path: "/gh/torvalds/linux@master/README", mustContain: ["Linux kernel"] }
128
+ # 完整写法**自带探测判据** —— 第三关用它验证「这个 IP 真的在提供这份内容」。
129
+ # 为什么判据必须跟着配置走:不同替身服务的 URL 形态不同,CDN 映射是
130
+ # `/gh/<user>/<repo>@<ref>/<path>`,代理是 `/<完整原始 URL>`,写死在代码里就没法让用户自己加替身。
131
+ #
132
+ # 🔴 兜底域名本身也可能被封:实测 2026-10-11,`cdn.jsdelivr.net` 在当前网络下 TCP 连得上但 TLS 被掐
133
+ # (被识别的是 TLS ClientHello 的形态,不是 IP)。多候选机制就是为这种情况准备的。
134
+ # 🔴 默认只保留公共 CDN:**不预置任何第三方代理 / 镜像域名**——本工具定位「无代理」,
135
+ # 预置代理域名既与该定位矛盾,也有法律风险。需要更多兜底请自行添加(示例见 README 第 6 节)。
136
+ fallbacks:
137
+ raw.githubusercontent.com:
138
+ - cdn.jsdelivr.net
139
+
140
+ # 超时:tcpMs 只管 TCP 握手阶段(死 IP 快速淘汰),握手成功后自动切到 tlsMs。
141
+ # 黑洞 IP 会一直挂到超时,所以这两段必须分开 —— 否则每加一个候选都要多等 tlsMs。
142
+ timeouts: { dohMs: 5000, tcpMs: 2500, tlsMs: 6000, httpMs: 6000 }
143
+ probe:
144
+ maxCandidates: 8 # DoH 每域名最多取几个候选
145
+ maxPoolIps: 32 # 内置池每域名最多补几个。**与 DoH 各自限额,不共享总上限**(见 probe.js)
146
+ maxPerDomain: 4 # 每域名最多保留几个可用 IP(前 maxEntriesPerDomain 个会写进 hosts)
147
+ concurrency: 10 # 全局同时握手的 socket 上限(跨域名共享)
148
+ # hosts 写入:每域名写几条候选。
149
+ # 🔴 2026-10-11 实测推翻旧假设「Windows 同一域名只取第一条」—— hosts 里同名多条会被
150
+ # 解析器**全部返回**(保持文件顺序),且 curl / 浏览器会**逐个尝试**:黑洞 IP 排在前
151
+ # 也能 fallback 到后面的活 IP(实测 http=200 / 0.22s)。所以多写 1 条备选 = 抖动时
152
+ # 客户端自动兜底,把「断最长 1 分钟」变成「多等一个连接超时」。
153
+ # 不要贪多:每多一个死 IP 就多叠一个连接超时;2 条是收益/代价的平衡点。1 = 退回旧行为。
154
+ hosts:
155
+ maxEntriesPerDomain: 2
156
+ serve: { host: 127.0.0.1, port: 8930 }
157
+ # 调度:固定频率健康检查,不做指数退避(哨兵一轮 <2s,退避省不下开销反而拉长最坏发现延迟)
158
+ schedule:
159
+ # 哨兵间隔(分钟):只测已写入 IP(3 条并发,健康时 ~1s),异常才触发全量重排。
160
+ # 🔴 取 1 是刻意的:这是「IP 死了到自动换掉」的**最坏等待**。5 分钟时代码本身只要 20s 就能修好,
161
+ # 但用户平均要多等 2.5 分钟 —— 卡在调度间隔上而不是卡在探测上。1 分钟是 Task Scheduler
162
+ # 重复间隔的下限(New-ScheduledTaskTrigger 最小 1 分钟),已经到底了。
163
+ healthMin: 1
164
+ absentCooldownHours: 1 # 缺失域名冷却(小时):SNI 层封锁的域名全量也救不回,冷却内不重试
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghprobe",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "GitHub direct-connect optimizer: local DoH resolution + TLS-verified probing + hosts generation (no proxy)",
5
5
  "keywords": [
6
6
  "github",