ghlink 0.4.15__tar.gz

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.
Files changed (57) hide show
  1. ghlink-0.4.15/LICENSE +21 -0
  2. ghlink-0.4.15/MANIFEST.in +9 -0
  3. ghlink-0.4.15/PKG-INFO +436 -0
  4. ghlink-0.4.15/README.md +405 -0
  5. ghlink-0.4.15/assets/ghlink-icon-128.png +0 -0
  6. ghlink-0.4.15/assets/ghlink-icon.ico +0 -0
  7. ghlink-0.4.15/assets/ghlink-icon.png +0 -0
  8. ghlink-0.4.15/config.example.json +46 -0
  9. ghlink-0.4.15/ghlink.egg-info/PKG-INFO +436 -0
  10. ghlink-0.4.15/ghlink.egg-info/SOURCES.txt +55 -0
  11. ghlink-0.4.15/ghlink.egg-info/dependency_links.txt +1 -0
  12. ghlink-0.4.15/ghlink.egg-info/entry_points.txt +2 -0
  13. ghlink-0.4.15/ghlink.egg-info/top_level.txt +1 -0
  14. ghlink-0.4.15/packaging/brew/ghlink.rb +83 -0
  15. ghlink-0.4.15/packaging/windows/ChineseSimplified.isl +417 -0
  16. ghlink-0.4.15/packaging/windows/ghlink.iss +150 -0
  17. ghlink-0.4.15/packaging/windows/ghlink.spec +137 -0
  18. ghlink-0.4.15/packaging/windows/ghlink_entry.py +8 -0
  19. ghlink-0.4.15/packaging/windows/ghlink_tray_entry.py +14 -0
  20. ghlink-0.4.15/packaging/windows/ghlink_watch_entry.py +16 -0
  21. ghlink-0.4.15/pyproject.toml +75 -0
  22. ghlink-0.4.15/scripts/linux_smoke.sh +191 -0
  23. ghlink-0.4.15/scripts/macos_smoke.sh +192 -0
  24. ghlink-0.4.15/setup.cfg +4 -0
  25. ghlink-0.4.15/src/ghlink/__init__.py +4 -0
  26. ghlink-0.4.15/src/ghlink/assets/ghlink-icon-128.png +0 -0
  27. ghlink-0.4.15/src/ghlink/assets/ghlink-icon.ico +0 -0
  28. ghlink-0.4.15/src/ghlink/assets/ghlink-icon.png +0 -0
  29. ghlink-0.4.15/src/ghlink/builtin_github520.py +56 -0
  30. ghlink-0.4.15/src/ghlink/config.py +73 -0
  31. ghlink-0.4.15/src/ghlink/github520.py +257 -0
  32. ghlink-0.4.15/src/ghlink/hosts_manager.py +259 -0
  33. ghlink-0.4.15/src/ghlink/lock.py +142 -0
  34. ghlink-0.4.15/src/ghlink/main.py +491 -0
  35. ghlink-0.4.15/src/ghlink/notifier.py +48 -0
  36. ghlink-0.4.15/src/ghlink/platform_adapter.py +209 -0
  37. ghlink-0.4.15/src/ghlink/probe.py +119 -0
  38. ghlink-0.4.15/src/ghlink/resolver.py +205 -0
  39. ghlink-0.4.15/src/ghlink/service.py +1014 -0
  40. ghlink-0.4.15/src/ghlink/state.py +68 -0
  41. ghlink-0.4.15/src/ghlink/tray.py +688 -0
  42. ghlink-0.4.15/tests/conftest.py +27 -0
  43. ghlink-0.4.15/tests/test_config.py +56 -0
  44. ghlink-0.4.15/tests/test_domain_health.py +202 -0
  45. ghlink-0.4.15/tests/test_e2e.py +299 -0
  46. ghlink-0.4.15/tests/test_github520.py +88 -0
  47. ghlink-0.4.15/tests/test_hosts_manager.py +71 -0
  48. ghlink-0.4.15/tests/test_lock.py +57 -0
  49. ghlink-0.4.15/tests/test_notifier.py +25 -0
  50. ghlink-0.4.15/tests/test_probe.py +58 -0
  51. ghlink-0.4.15/tests/test_resolver.py +82 -0
  52. ghlink-0.4.15/tests/test_service.py +371 -0
  53. ghlink-0.4.15/tests/test_state.py +78 -0
  54. ghlink-0.4.15/tests/test_tray.py +193 -0
  55. ghlink-0.4.15/tests/test_v043_regression.py +115 -0
  56. ghlink-0.4.15/tests/test_v044_regression.py +80 -0
  57. ghlink-0.4.15/tests/test_v049_regression.py +126 -0
ghlink-0.4.15/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mason Lee
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,9 @@
1
+ include README.md
2
+ include LICENSE
3
+ include config.example.json
4
+ recursive-include tests *.py
5
+ recursive-include packaging/debian *.sh
6
+ recursive-include packaging/windows *.iss *.spec *.py *.isl
7
+ recursive-include packaging/brew *.rb
8
+ recursive-include scripts *.sh
9
+ recursive-include assets *.png *.ico
ghlink-0.4.15/PKG-INFO ADDED
@@ -0,0 +1,436 @@
1
+ Metadata-Version: 2.4
2
+ Name: ghlink
3
+ Version: 0.4.15
4
+ Summary: Self-healing connectivity for GitHub: monitors reachability, auto-resolves fresh IPs and updates hosts to restore access seamlessly. Zero third-party dependencies, cross-platform.
5
+ Author-email: Mason Lee <liwmj@outlook.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/liwmj/ghlink
8
+ Project-URL: Repository, https://github.com/liwmj/ghlink
9
+ Project-URL: Documentation, https://github.com/liwmj/ghlink/blob/master/README.md
10
+ Project-URL: Releases, https://github.com/liwmj/ghlink/releases
11
+ Keywords: github,dns,hosts,self-healing,network,connectivity
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: MacOS
17
+ Classifier: Operating System :: Microsoft :: Windows
18
+ Classifier: Operating System :: POSIX :: Linux
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.8
21
+ Classifier: Programming Language :: Python :: 3.9
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Topic :: System :: Networking
26
+ Classifier: Topic :: System :: Systems Administration
27
+ Requires-Python: >=3.8
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Dynamic: license-file
31
+
32
+ <div align="center">
33
+ <img src="assets/ghlink-icon-128.png" width="96" alt="ghlink"/>
34
+ <h1>ghlink — GitHub 链路自愈工具</h1>
35
+ <p>当 GitHub 网络不稳定时,自动获取可用 IP 并替换 hosts,实现无感自愈。</p>
36
+ </div>
37
+
38
+ <p align="center">
39
+ <a href="https://github.com/liwmj/ghlink/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/liwmj/ghlink/ci.yml?label=CI&style=flat&logo=github" alt="CI"/></a>
40
+ <a href="https://github.com/liwmj/ghlink/releases"><img src="https://img.shields.io/github/v/release/liwmj/ghlink?label=%E7%89%88%E6%9C%AC&style=flat&logo=github" alt="版本"/></a>
41
+ <a href="LICENSE"><img src="https://img.shields.io/github/license/liwmj/ghlink?label=License&style=flat" alt="License"/></a>
42
+ <a href="docs/DESIGN.md"><img src="https://img.shields.io/badge/platform-macOS%20%7C%20Windows%20%7C%20Linux-lightgrey?style=flat" alt="平台"/></a>
43
+ <a href="docs/"><img src="https://img.shields.io/badge/docs-passing-brightgreen?style=flat" alt="文档"/></a>
44
+ <a href="https://github.com/liwmj/ghlink/releases"><img src="https://img.shields.io/github/downloads/liwmj/ghlink/total?label=%E4%B8%8B%E8%BD%BD&style=flat" alt="下载"/></a>
45
+ </p>
46
+
47
+ ---
48
+
49
+ ## 目录
50
+
51
+ - [为什么需要 ghlink](#为什么需要-ghlink)
52
+ - [核心特性](#核心特性)
53
+ - [工作原理](#工作原理)
54
+ - [架构设计](#架构设计)
55
+ - [快速开始](#快速开始)
56
+ - [配置说明](#配置说明)
57
+ - [运行与退出码](#运行与退出码)
58
+ - [状态文件](#状态文件)
59
+ - [测试与验证](#测试与验证)
60
+ - [跨平台支持](#跨平台支持)
61
+ - [路线图](#路线图)
62
+ - [开发记录](#开发记录)
63
+ - [License](#license)
64
+
65
+ ---
66
+
67
+ ## 为什么需要 ghlink
68
+
69
+ 在中国大陆等网络环境下,GitHub 的 DNS 解析经常被污染或返回不可达 IP,导致 `git clone`、`git push`、网页访问频繁超时(TCP 443 连接失败、TLS 握手超时)。
70
+
71
+ 常见的解决方式:
72
+ - **手动改 hosts**:需要人工找 IP、改文件、刷 DNS,IP 失效后又要重来
73
+ - **代理工具**:需要额外部署、配置,且不是所有场景都适用
74
+
75
+ **ghlink 的思路**:监控 GitHub 连通性 → 不稳定时自动获取可用 IP → 写入 hosts → 自检确认 → 失败自动回滚,全程无人工干预。
76
+
77
+ ---
78
+
79
+ ## 核心特性
80
+
81
+ - 🔍 **三层探测**:TCP 443 建连 → TLS 握手(SNI)→ HTTP HEAD,真实反映 GitHub 可用性
82
+ - 🔄 **自动自愈**:连续 3 轮失败(默认)触发切换,写入新 IP 后自检,失败立即回滚
83
+ - 🌐 **多源 IP 获取**:阿里/腾讯/Cloudflare/Google 四个 DoH 源 + 系统 DNS + 本地缓存 + GitHub520 社区列表,多数票 + TCP 443 预检,单源故障自动剔除;**动态解析失败自动回退 GitHub520 静态列表**(v0.4.0:首装全量写,预检过排前)
84
+ - 🧠 **目标域名健康度管理**(v0.2 + v0.4.0):非核心域名(如 codeload/fastly)长期不可达自动降级——**降级不从 hosts 删除,改用 GitHub520 静态 IP 兜底**(v0.4.0 李工定),动态恢复后自动重新纳入;核心域名(github.com / api.github.com)永不降级优先保证切换成功
85
+ - 🛡️ **安全红线**:写入前备份 hosts、写入后自检、自检失败回滚——**宁可不变,不能改坏**
86
+ - ⏱️ **冷却防抖**:切换成功后 15 分钟冷却期,避免 IP 抖动导致频繁切换
87
+ - 🧵 **防重入锁**:跨平台(flock / msvcrt / PID 文件),避免定时任务并发执行
88
+ - 🔔 **飞书告警**:切换、降级、回滚事件实时通知(飞书 Webhook 已实现;钉钉 / 企业微信 / Telegram / 通用 Webhook 规划中),冷却期去重,发送失败不阻断主流程
89
+ - 💻 **跨平台**:macOS / Windows / Linux 一套代码,平台差异收敛到单一适配层
90
+ - 📦 **零第三方依赖**:纯 Python 标准库实现,运行无需任何第三方包
91
+
92
+ ---
93
+
94
+ ## 工作原理
95
+
96
+ ```mermaid
97
+ flowchart LR
98
+ A[定时调度<br>cron / launchd / 任务计划] --> B[监控层 探测<br>TCP443 + TLS + HTTP HEAD]
99
+ B -->|连续 3 轮失败| C[IP 通路自稳<br>DoH×4 + 系统DNS + 缓存 + 预检]
100
+ C --> D[替换层 自愈<br>备份 → 写入 → 刷DNS → 自检]
101
+ D -->|自检失败| E[回滚 restore_hosts<br>坏配置不留场]
102
+ D -->|自检通过| F[verifying 确认<br>连续 2 轮成功 → normal]
103
+ F --> B
104
+ B -->|正常| A
105
+ ```
106
+
107
+ **状态机**:`normal → switching → verifying → normal`,异常路径进入 `degraded`。
108
+
109
+ **v0.4.0 降级语义(李工 2026-08-22 定)**:动态解析失败但有 GitHub520 静态兜底 → **仍写静态段**(首装/断网场景 hosts 必有可用条目);动态失败且无任何兜底候选 → 才不写、保持系统默认 DNS(宁缺毋滥,只告警不破坏)。
110
+
111
+ ---
112
+
113
+ ## 架构设计
114
+
115
+ 详细设计文档见 [docs/DESIGN.md](docs/DESIGN.md),包含:
116
+
117
+ - 模块划分与职责(9 个核心模块)
118
+ - 平台差异适配策略(hosts 路径 / 提权 / DNS 刷新)
119
+ - 状态文件 Schema v1
120
+ - 失败场景与降级路径
121
+ - 代码审查记录:[docs/REVIEW-v0.1.md](docs/REVIEW-v0.1.md)
122
+
123
+ ```
124
+ src/ghlink/
125
+ ├── config.py # 配置加载 / 深合并 / 缺省回退
126
+ ├── platform_adapter.py # 平台差异唯一出口(hosts / 权限 / 刷DNS / 备份回滚)
127
+ ├── probe.py # TCP443 + TLS + HTTP HEAD 三层探测
128
+ ├── resolver.py # 多 DoH + 系统 DNS 多数票 + 443 预检 + 缓存兜底
129
+ ├── hosts_manager.py # 段落式写入 / 备份 / 自检 / 回滚
130
+ ├── state.py # 状态文件原子写
131
+ ├── notifier.py # 飞书通知(Webhook,冷却去重,失败不阻断;多渠道规划中)
132
+ ├── lock.py # 跨平台防重入锁(flock / msvcrt / PID 文件)
133
+ └── main.py # 单轮执行闭环(探测→判定→自愈→确认)
134
+ ```
135
+
136
+ ---
137
+
138
+ ## 默认行为(值守独立于托盘,2026-08-17 李工新口径)
139
+
140
+ ghlink 的语义模型(替代 2026-08-16 版「托盘=值守总开关」):
141
+
142
+ - **值守独立于托盘**:`ghlink enable` 注册平台定时任务(1 分钟粒度后台探测 + 自愈)即开启值守,**不启动托盘也能值守**(命令行模式)。
143
+ - **托盘 = UI 载体**:托盘用于展示状态(图标颜色/菜单)与便捷操作(复制 IP/开关自启)。启动/退出托盘不改变值守状态,值守由 enable 独立管理。
144
+ - **Windows 便捷联动**(启动逻辑层面,非代码逻辑):为方便 Windows 用户,启动托盘时同步启动值守;退出托盘时同步退出值守。Windows 托盘在 ⇒ 值守在(联动行为);命令行 `ghlink enable` 仍可独立值守。
145
+ - **值守 enable**:底层是系统定时任务(Windows schtasks / macOS LaunchDaemon / Linux systemd timer,1 分钟粒度),`ghlink enable` 注册、`ghlink disable` 移除、`ghlink status` 查看。
146
+
147
+ 关键语义:**值守在 = 平台任务注册 + 心跳正常**;托盘只是显示器,不是开关。安装后默认不自启(需手动 `ghlink enable` 或勾选「开机自动启动托盘」)。
148
+
149
+ ## 快速开始
150
+
151
+ ### 环境要求
152
+
153
+ - Python 3.8+(纯标准库,无第三方依赖)
154
+ - 写 hosts 需要管理员/root 权限
155
+
156
+ ### 安装(各系统)
157
+
158
+ **macOS(Homebrew tap,推荐)**
159
+
160
+ ```bash
161
+ # 方式一:信任 tap 后安装(Homebrew 4.x 起第三方 tap 默认不可信,必须先 trust)
162
+ brew tap liwmj/tap
163
+ brew trust liwmj/tap # 或 brew trust --formula liwmj/tap/ghlink
164
+ brew install --cask ghlink
165
+
166
+ # 方式二:若已 tap 但 install 报 Refusing to load cask ... untrusted tap
167
+ brew trust --cask liwmj/tap/ghlink && brew install --cask ghlink
168
+ ```
169
+
170
+ > tap 仓库:liwmj/homebrew-tap(2026-08-23 由 homebrew-ghlink 更名,多包通用 tap)
171
+
172
+ **macOS 卸载(Cask,v0.4.14 起)**
173
+
174
+ ```bash
175
+ brew uninstall --cask ghlink # 自动执行 ghlink uninstall:停任务 + 还原 hosts + 删配置 + 自清 sudoers 规则
176
+ brew uninstall --cask ghlink --zap # 二次兜底清理全部残留(彻底卸载推荐)
177
+ ```
178
+
179
+ > v0.4.14 起:brew 不再用 `sudo -E` 包装卸载脚本(macOS 默认 sudoers 未开 setenv,
180
+ > `sudo -E` 必报 "not allowed to preserve the environment",v0.4.13 及之前会卡死在此),
181
+ > 改由 ghlink uninstall 内部以普通 sudo 自提权执行。若手动配置过 /etc/sudoers.d/ghlink
182
+ > (v0.4.5 起的 NOPASSWD 窄放行),卸载会自动清理该规则。
183
+
184
+ **macOS 托盘提权配置(可选,v0.4.14 收紧版)**
185
+
186
+ 托盘「启用值守」走 `sudo -n ghlink enable/disable`,免密提权需手动配置 sudoers
187
+ (窄放行 + env_keep 白名单替代 !env_reset):
188
+
189
+ ```bash
190
+ sudo tee /etc/sudoers.d/ghlink <<'EOF'
191
+ # ghlink 托盘提权窄放行(v0.4.14 收紧:env_keep 白名单替代 !env_reset)
192
+ <用户名> ALL=(root) NOPASSWD: /usr/local/bin/ghlink
193
+ Defaults!/usr/local/bin/ghlink env_keep += "GH_TOKEN"
194
+ Defaults!/usr/local/bin/ghlink env_keep += "HTTP_PROXY HTTPS_PROXY NO_PROXY ALL_PROXY"
195
+ EOF
196
+ sudo visudo -c # 校验语法
197
+ ```
198
+
199
+ > 将 `<用户名>` 替换为 macOS 登录用户名;卸载时 ghlink uninstall 会自动清理该规则。
200
+
201
+ **Windows(安装向导 / 裸 exe)**
202
+
203
+ ```bash
204
+ # 方式一:安装向导(推荐,含托盘依赖与开机自启选项)
205
+ # 从 https://github.com/liwmj/ghlink/releases 下载 ghlink-installer-vX.Y.Z.exe 双击安装
206
+
207
+ # 方式二:裸 exe(绿色版,无需安装)
208
+ # 下载 ghlink.exe(CLI)+ ghlink-tray.exe(托盘)放同一目录直接运行
209
+ ```
210
+
211
+ **Linux(apt / .deb)**
212
+
213
+ ```bash
214
+ # 方式一:apt 仓库(Debian/Ubuntu,v0.2.18 起用 Pages 固定 URL,写一次永久生效)
215
+ echo "deb [trusted=yes] https://liwmj.github.io/ghlink/apt/ ./" | sudo tee /etc/apt/sources.list.d/ghlink.list
216
+ sudo apt update && sudo apt install ghlink
217
+ # 以后每版发完直接 sudo apt upgrade 拿最新,无需改 sources.list
218
+
219
+ # 方式二:.deb 直接安装
220
+ wget https://github.com/liwmj/ghlink/releases/download/v0.2.18/ghlink_0.2.18-1_all.deb
221
+ sudo dpkg -i ghlink_*.deb
222
+ ```
223
+
224
+ **PyPI(任意系统,发布打通后生效)**
225
+
226
+ ```bash
227
+ # PyPI 发布打通后可直接安装(当前版本请优先使用上方各平台安装包)
228
+ pip install ghlink
229
+ ```
230
+
231
+ **源码(任意系统,零第三方依赖)**
232
+
233
+ ```bash
234
+ git clone https://github.com/liwmj/ghlink.git && cd ghlink
235
+ cp config.example.json config.json
236
+ # 运行:python3 -m ghlink.main run [config.json]
237
+ # 托盘:pip install pystray Pillow 后 python3 -m ghlink.main tray
238
+ ```
239
+
240
+ ### 配置
241
+
242
+ ```bash
243
+ cp config.example.json config.json
244
+ # 编辑 config.json:
245
+ # - notify.feishu_webhook:飞书群机器人 Webhook 地址(配置后启用告警,空=关闭)
246
+ # - notify.enabled:告警开关(默认 true)
247
+ # - probe.targets:探测域名清单(默认 github.com / api.github.com 等)
248
+ # 注:钉钉 / 企业微信 / Telegram / 通用 Webhook 等渠道规划中,后续版本支持
249
+ ```
250
+
251
+ ### 手动运行一次
252
+
253
+ ```bash
254
+ # Linux / macOS(需 root 写 hosts)
255
+ sudo python3 -m ghlink.main config.json
256
+
257
+ # Windows(管理员命令行)
258
+ python -m ghlink.main config.json
259
+ ```
260
+
261
+ 退出码 `0` = 正常(探测通过或冷却期跳过),`1` = 降级/告警。
262
+
263
+ ### 默认行为(v0.2.x)
264
+
265
+ - **安装后默认不自启**(2026-08-14 李工定规):不注册值守任务、托盘不随登录启动
266
+ - **开启自启**:托盘右键菜单「启用值守」开关,或命令行 `ghlink enable`(注册 1 小时粒度定时任务,v0.2.18 起)
267
+ - **关闭自启**:托盘右键「停用值守」,或 `ghlink disable`
268
+ - 托盘(Windows/macOS):状态图标/悬停摘要/右键开关/气泡通知;Linux 为纯 CLI
269
+
270
+ ### 定时调度(1 小时粒度,v0.2.18 起)
271
+
272
+ **Linux(crontab)**:
273
+ ```bash
274
+ 0 * * * * cd /opt/ghlink && sudo python3 -m ghlink.main config.json >> /var/log/ghlink.log 2>&1
275
+ ```
276
+
277
+ **macOS(launchd)**:
278
+ ```xml
279
+ <?xml version="1.0" encoding="UTF-8"?>
280
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
281
+ <plist version="1.0">
282
+ <dict>
283
+ <key>Label</key><string>com.ghlink.daemon</string>
284
+ <key>ProgramArguments</key>
285
+ <array>
286
+ <string>/usr/bin/python3</string>
287
+ <string>/opt/ghlink/src/ghlink/main.py</string>
288
+ <string>/opt/ghlink/config.json</string>
289
+ </array>
290
+ <key>StartInterval</key><integer>3600</integer>
291
+ <key>RunAtLoad</key><true/>
292
+ </dict>
293
+ </plist>
294
+ ```
295
+
296
+ **Windows(任务计划程序)**:创建基本任务 → 触发器设为「重复任务间隔 1 小时」→ 操作设为 `python C:\ghlink\src\ghlink\main.py C:\ghlink\config.json`,勾选「使用最高权限运行」。
297
+
298
+ ### GitHub520 社区 IP 集成(v0.2.18 + v0.4.0)
299
+
300
+ ghlink 周期拉取 [GitHub520](https://github.com/521xueweihan/GitHub520) 社区 hosts(默认 1 小时刷新),为非核心 GitHub 域名(raw/objects/gist 等)补充社区 IP,覆盖自愈盲区。
301
+
302
+ **v0.4.0 语义(李工 2026-08-22 三点定)**:
303
+ - **首装全量写**:enable/首次 run 初始化时写入 GitHub520 全量 IP(含核心域名静态兜底,预检过的排前——hosts 取首个命中),后续轮次才走动态优化
304
+ - **降级用静态 IP 兜底不删除**:非核心域名动态降级后保留 GitHub520 静态段,动态恢复自动加回
305
+ - **动态失败仍写静态段**:DoH 全源失败但有 GitHub520 兜底时照写,不再空手 degraded
306
+ - **内置快照兜底**:拉取失败且缓存空时用内置最新快照(首装断网也能直接用)
307
+ - 写入前 TCP 可达性抽检防坏 IP 入场;拉取失败自动回退本地缓存 → 内置快照
308
+
309
+ **hosts 段落管理(v0.4.0,李工 12:35 点 3)**:
310
+ - ghlink 全部修改收敛在独立段落 `# ghlink Start/End`(含 `# ghlink520` 子段),段落外内容零改动,不影响用户其他 DNS 配置
311
+ - **段落插到 hosts 文件最前**(first-match-wins 优先命中),避免用户预存条目在段落前遮蔽 ghlink 写入
312
+ - **预存条目冲突检测**:enable 时扫描段落外 GitHub 生态域名预存条目 → 命中则告警 + 自动备份原 hosts(hosts_backup_dir),用户记录不动
313
+ - **卸载即恢复**:卸载/disable 移除 ghlink 段落即恢复原状(备份可回滚)
314
+
315
+ ---
316
+
317
+ ## 配置说明
318
+
319
+ | 配置段 | 字段 | 默认值 | 说明 |
320
+ |--------|------|--------|------|
321
+ | `probe` | `targets` | github.com 等 8 域名 | 探测域名清单(v0.2.18 扩域:含 raw/objects/gist/githubassets) |
322
+ | `probe` | `timeout_sec` | 15 | 单域名探测超时 |
323
+ | `probe` | `core_targets` | github.com / api.github.com | 核心域名(永不降级,优先保证切换成功) |
324
+ | `probe` | `degrade_after_rounds` | 3 | 非核心域名连续失败 N 轮 → 降级(1h 粒度 ≈ 3h) |
325
+ | `probe` | `recover_rounds` | 2 | 降级域名连续成功 N 轮 → 恢复纳入 |
326
+ | `trigger` | `consecutive_failures` | 3 | 连续失败 N 轮触发切换(1h 粒度 ≈ 3h) |
327
+ | `trigger` | `cooldown_min` | 180 | 切换后冷却分钟数(1h 粒度核算) |
328
+ | `trigger` | `verify_success_rounds` | 2 | 自愈后连续成功轮数恢复 normal |
329
+ | `github520` | `enabled` | true | GitHub520 社区 IP 集成开关(v0.2.18) |
330
+ | `github520` | `url` | raw.hellogithub.com/hosts | 社区 hosts 拉取源 |
331
+ | `github520` | `refresh_min` | 60 | 社区 IP 刷新周期(1 小时) |
332
+ | `github520` | `core_first` | true | 核心域名 ghlink 自愈优先(v0.4.0:首装含核心域名静态兜底,动态成功时动态段优先) |
333
+ | `resolver` | `doh_sources` | 阿里/腾讯/CF/Google | DoH 源 URL 列表 |
334
+ | `resolver` | `cache_ttl_sec` | 3600 | 本地 IP 缓存有效期 |
335
+ | `resolver` | `max_candidates` | 5 | 候选 IP 上限 |
336
+ | `notify` | `feishu_webhook` | 空 | 飞书群机器人 Webhook 地址(配置后启用告警;钉钉/企微/Telegram/通用 Webhook 规划中) |
337
+ | `notify` | `enabled` | true | 告警开关 |
338
+
339
+ ---
340
+
341
+ ## 运行与退出码
342
+
343
+ | 码 | 含义 |
344
+ |----|------|
345
+ | 0 | 正常(探测通过 / 锁占用跳过 / 冷却期跳过 / 切换成功) |
346
+ | 1 | 降级(提权失败 / 写入失败 / 自检失败回滚 / 无可用 IP / 告警触发) |
347
+ | 2 | 配置 / 参数错误 |
348
+
349
+ **降级语义(v0.4.0)**:动态失败但有 GitHub520 静态兜底 → 写静态段(首装/断网可用);无任何可用候选才不写——任何写入都经 TCP 预检,自检不过立即回滚,只记录状态并可选告警。
350
+
351
+ ---
352
+
353
+ ## 状态文件
354
+
355
+ 默认 `ghlink_status.json`(schema v1):
356
+
357
+ ```json
358
+ {
359
+ "schema_version": 1,
360
+ "state": "normal",
361
+ "probe": {
362
+ "targets": { "github.com": {"ok": true} },
363
+ "consecutive_failures": 0
364
+ },
365
+ "current_ip": "140.82.112.3",
366
+ "verify_success": 0,
367
+ "switched_at": null,
368
+ "last_error": null,
369
+ "history": []
370
+ }
371
+ ```
372
+
373
+ | 状态 | 含义 |
374
+ |------|------|
375
+ | `normal` | 健康,无需干预 |
376
+ | `switching` | 已触发切换,正在写入 hosts |
377
+ | `verifying` | 已写入,等待连续成功确认 |
378
+ | `degraded` | 降级:保持原配置,已告警 |
379
+
380
+ ---
381
+
382
+ ## 测试与验证
383
+
384
+ - **单元/集成测试**:`tests/` 目录,60 个用例覆盖配置/探测/解析/hosts/状态/锁/通知/全链路/域名健康度
385
+ - **运行测试**:`python -m pytest tests/ -v`
386
+ - **真机冒烟**:已完成三平台验证(2026-08-14):
387
+ - Linux(Ubuntu):注入故障 → 触发切换 → 写入 → 自检 → 回滚 → 锁接管 → 冷却防抖全链路通过
388
+ - Windows(Server 2022):正常路径无感跳过 / E-004 降级(全源不可达保持原配置)/ 注入 127.0.0.1 → 自动切换 20.205.243.168 / 2 轮确认恢复 / 残留锁接管 + 并发跳过
389
+ - macOS:本机故障注入全链路冒烟(正常路径/切换/回滚/锁/冷却)通过
390
+ - **验证记录**:三平台(macOS / Windows / Linux)60 用例全绿 + 真机冒烟报告
391
+
392
+ ---
393
+
394
+ ## 跨平台支持
395
+
396
+ | 平台 | 单测 | 真机冒烟 | 备注 |
397
+ |------|------|----------|------|
398
+ | macOS 13+ | ✅ 60 passed | ✅ 已完成 (2026-08-14) | Intel / Apple Silicon |
399
+ | Windows 10/11 / Server | ✅ 60 passed | ✅ 已完成 (2026-08-13) | UAC 提权 / ipconfig flushdns |
400
+ | Linux (Ubuntu/Debian) | ✅ 60 passed | ✅ 已完成 | resolvectl / 备份恢复 |
401
+
402
+ ---
403
+
404
+ ## 路线图
405
+
406
+ - [x] **v0.1.0**(2026-08-13):核心自愈闭环 + 三平台单测全绿 + 双平台真机冒烟
407
+ - [x] **v0.2.0**(2026-08-14):目标域名健康度管理(长期不可达域名自动降级,核心域名优先切换)+ 三平台真机冒烟闭环 + 生产就绪(Production/Stable)
408
+ - [x] **v0.2.1**(2026-08-14):默认不自启(李工定规)+ 平台安装包发布(Windows installer / macOS brew / Linux deb)
409
+ - [x] **v0.3.1**(2026-08-22):deb 包内 Version 动态注入 + 版本号随 tag 同步(跟进人 Linux 回归发现修复)
410
+ - [x] **v0.4.0**(2026-08-22,李工 12:35 三点 + 12:44 终裁):首装全量写 GitHub520 兜底(含核心域名静态 IP,预检过排前)+ 降级保留静态 IP 不删除 + 多源回退链(DoH→GitHub520→内置快照)+ hosts 块前移优先命中 + 预存条目检测告警
411
+ - [ ] **v0.4.x**:历史切换统计与报表
412
+
413
+ ---
414
+
415
+ ## 开发记录
416
+
417
+ - 2026-08-13:立项(PJ-002)→ 架构设计 → 核心 9 模块实现 → 代码审查(P0/P1/P2 全修复)→ 测试套件合入(51 用例全绿)→ 三平台验证(macOS/Linux/Windows 51 passed)→ Linux + Windows 真机冒烟完成 → Release v0.1.0
418
+ - 2026-08-13:Windows 真机冒烟完成,三平台矩阵全绿
419
+ - 2026-08-14:v0.2.0/v0.2.1 发布(目标域名健康度管理 + 默认不自启 + 多平台安装包);三平台真机冒烟闭环
420
+ - 2026-08-15:按 P-006 v1.21 对齐(ruff 代码规范 + CI 门禁 + README 徽章参数化/mermaid 原理图 + 测试矩阵)
421
+ - 2026-08-22:v0.3.0 发布(4 位 tag 清理 + CI tag 3 位校验 + 三平台卸载删配置 + 内置 GitHub520 兜底);v0.3.1 发布(deb Version 动态注入 + 版本号同步);v0.4.0 开发(首装全量写兜底/降级保留静态 IP/hosts 块前移/多源回退链,李工 12:35 三点定调)
422
+ - 2026-08-22:分支治理(GitHub Flow 合并即删铁律入信息表 v1.23,清理 23 个历史分支 + 2 个 stale PR)
423
+ - 立项评估:A 级(优化 GitHub 网络链路,直接影响开发/同步效率与稳定性)
424
+
425
+ ---
426
+
427
+ ## Contributors
428
+
429
+ <!-- 早期手动头像墙,有外部贡献者后切换 all-contributors 自动化(P-006 v1.15) -->
430
+ <a href="https://github.com/liwmj"><img src="https://github.com/liwmj.png?size=40" width="40" height="40" alt="liwmj"></a>
431
+
432
+ ---
433
+
434
+ ## License
435
+
436
+ [MIT](LICENSE)