wangjia
71a4cddf0b
sync(edge): jiu.conf 反向同步线上版本(OG 注入 + /downloads + /app/product 重写)
...
EC2 上的 jiu.conf 早于本次已含商品页反代后端注入微信 OG 标签、
/app/product 重写、/downloads 安装包下载,但从未同步回仓库。
本次以线上为准回灌,避免下次 deploy.sh 用旧版覆盖线上配置。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-16 08:38:27 +08:00
wangjia
5d4b484646
feat(alert): 统一告警出口 TG bot + runbook [tsk_9YMHMTfWJyNB]
...
新增 server/internal/alert 包(15G):
- 定义 Notifier 接口及 7 种 EventType(判封确认/补新失败/水位低/熔断/
探针失联/心跳缺失/故障态)
- TGNotifier:Bot API 发送,Critical 事件不去重,Warning/Info 事件
10min SETNX 去重窗口,失败重试 ≤2 次后降级至 LogNotifier
- LogNotifier:slog 结构化降级实现
- 单测:7 种事件模板 + runbook 锚点正确性;去重窗口内第二条被抑制;
TG 5xx 重试后 fallback 且 Notify() 返回 nil;
runbook 文件锚点与枚举一致性
接入 scheduler(替换旧的 NotifyFault 桩):
- detect/engine.go:故障态(Rule 5)→ EventTypeFault;
判封确认(Rule 3)→ EventTypeBlockConfirmed
- orchestrate/deps.go:Notifier 类型别名指向 alert.Notifier
- orchestrate/replacer.go:补新失败 → EventTypeReplenishFailed;
熔断触发 → EventTypeBreakerTripped
- probe/prober_agent.go:failCount ≥3 → EventTypeProbeAgentLost
- probe/store.go:新增 CheckHeartbeats() 供 15H 检测心跳缺失>90s
新增 docs/runbook-scheduler.md:7 节各含含义/先查什么/处置/升级条件,
锚点与代码枚举对应。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-16 00:52:26 +08:00
wangjia
3d9ccff6e9
feat(scheduler): 容量水位监控 + 熔断器 + 阈值热加载 [tsk_NPgPRxBGv0g9]
...
实现 15F 任务的三个核心组件:
capacity.go — CapacityMonitor(每 1-2 min 由 CapacityLoop 调用)
- 池水位检查:up/target < 70% 时发告警,10 min 内降频去重
- 探针失联检查:扫 probe:hb:{probeId},缺失发「探针失联」事件
- 驱动 Grayscale.Advance 推进养机档位
breaker.go — RedisBreaker(实现 Breaker 接口)
- 滑窗 ZSET sched:breaker:{tier}:{region},窗口 1h
- 阈值 N = ceil(池容量 × 30%),下限 3
- 窗口内计数 ≥ N → 置 tripped 标记位 + critical 告警
- Allow 恒 false 直到窗口滑出自动恢复,或管理员调 Reset
- Reset 清除 ZSET + trip flag,写 audit_log
config.go — SchedConfig + ConfigManager(全树阈值热加载)
- SchedConfig 集中定义 15D/15E/15F 所有阈值
- 来源:默认值 → YAML 文件 → 环境变量(三层叠加)
- 监听 SIGHUP,原子替换(atomic.Pointer[SchedConfig])
- 每次变更 diff 写 audit_log(AuditFn 回调)
测试(24 个用例,全绿):
- 窗口内第 N 次 Record 后 Allow 返回 false
- 窗口滑出后 Allow 自动恢复
- Reset 立即恢复 + audit 条目
- 下限 3 在小池(target=2)生效
- 65% 水位 → 告警;10 min 内不重复
- 探针心跳缺失 → 失联事件;存在 → 无告警
- YAML + env 覆盖 + SIGHUP Reload → 新阈值即时生效 + diff 审计
- 替换 stub 后 15E 全部集成测试仍绿
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-16 00:31:58 +08:00
wangjia
83dd5369ab
feat(M4/M5): URLTest 自动选线 + Kill-switch + 弹性重连 [tsk_xAQhC1xuCd8x]
...
M4 URLTest 自动选线:
- ClashApiClient.getGroupDelay() — GET /group/<name>/delay 触发按需测速
- ClashApiClient.extractUrltestResults() — 从 /proxies 响应解析 URLTest
组成员最新延迟,填充 stats 帧的 urltestResults 字段
- DesktopKernelProcess._startStatsPoll() — 每秒并行拉取连接统计 + URLTest
延迟(_clashApi.getProxies()),URLTest 失败不影响主统计
- DesktopVpnBridge.selectOutbound(tag) — Clash API PUT /proxies/proxy,
tag 可传节点 tag 或 urltest 组 tag(如 "auto-select")恢复自动
- DesktopVpnBridge.getActiveOutbound() — GET /proxies 读 proxy.now
- app/kernel/poc/reality_client.config.json.tmpl — 增加 urltest("auto-select")
+ selector("proxy") 出口组,route.final 改为 "proxy"
M5 Kill-switch + 弹性重连:
- DesktopVpnBridge.setKillSwitch({required bool on}) — 将 _killSwitchEnabled
写入 TUN inbound strict_route 字段(applyKillSwitchToConfig);内核运行中
触发静默重载(kill → start(lastConfig))
- DesktopVpnBridge.applyKillSwitchToConfig() — static,killSwitch=true ↔
TUN strict_route=true,killSwitch=false ↔ strict_route=false
- 自动退避重连:kernel 崩溃推 error → _scheduleReconnect() 退避序列
1s/2s/4s/8s/16s/30s(上限),stop() 取消,重连成功后重置计数器
- DesktopKernelProcess 意外退出清理 _clashApi(避免下次 spawn 残留)
- app/pangolin/test/killswitch_checklist.md — 三端故障注入验收清单
(Desktop ✅ ,iOS/Android 欠账说明)
新增测试(client/test/bridge/desktop_vpn_bridge_m4m5_test.dart):
- M4: extractUrltestResults 解析/空/无 history 边界
- M4: getGroupDelay HTTP 请求格式验证
- M4: selectOutbound / getActiveOutbound Clash API 调用断言
- M5: applyKillSwitchToConfig 字段覆盖 + 无 TUN 边界
- M5: setKillSwitch 状态注入 + 同值幂等
- M5: 崩溃→error 不崩 / 首次重连在退避延迟内触发 / stop 后不再重连
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-16 00:11:09 +08:00
wangjia
4783f3e32e
feat(nodes): 节点生命周期状态机 [tsk_aG4yOS90inF_]
...
实现 Lifecycle 类型及其全部公开方法,覆盖 6 个状态迁移动作:
MarkProbing / MarkUp / MarkDraining / MarkDown / MarkDestroyed /
MarkBlockedSuspect。每次合法迁移在同一个 SQL 事务中原子执行
UPDATE nodes + INSERT node_events + BumpVersion;MarkDestroyed
事务提交后异步调用 mtls.CRL.Revoke() 并清除 Redis 负载缓存与
命令队列键。配套集成测试覆盖 5 类场景(合法迁移、非法迁移、
并发写、后置钩子、blocked_suspect 幂等),全部通过。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-15 23:49:45 +08:00
wangjia
cadd527680
feat(backend): 挂载 /v1 API + 实现 nodes/connect 端到端
...
- 新增 internal/dpcred 包,统一 DeriveHy2Password + DefaultFlow
agentd 与 HTTP connect handler 共享同一实现
- 新增迁移 000011:nodes 表拆分 reality_prk 私钥 / reality_pbk 公钥
reality_short_id;修正 handler_grpc.go 使用私钥字段
- 新增迁移 000012:connect_credentials 持久化凭证
实现 CredentialsForNode 修复 agent 重连 resync 原先返回空的桩
- 扩展 NodeStore 接口:ListUp / EntitlementForUser /
PersistCredential / DeleteCredential;同步 grpc_test.go mock
- 新增 httpapi/nodes.go:GET /nodes、POST /nodes/id/connect
Hub.Push + PersistCredential + 渲染完整 sing-box client 配置 JSON
POST /nodes/id/disconnect
- 新增 httpapi/account.go:GET /me、GET /plans、GET /notices
- 新增 httpapi/clientconfig.go:BuildClientConfig 服务端渲染
- 重写 cmd/server/main.go:手写 chi public/protected 分组
nodes.Service/Hub 在 main 构造并共享;SMTPMailer/LogMailer
go build ./... && go vet ./... && go test ./... 全部通过
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-15 23:09:56 +08:00
maestro
d3a329c8e5
maestro(tsk_rtYVDLkmc5mo): scheduler 装配 + 选主 + 优雅退出
2026-06-15 22:46:41 +08:00
wangjia
ebc9c1e702
feat(scheduler): 装配 scheduler — 三 loop + Redis 选主 + 优雅退出 [tsk_rtYVDLkmc5mo]
...
## 内容
### server/internal/scheduler/deps.go
新增 scheduler 级接口文件,定义所有外部依赖接口:
- DetectEngine / OrchestrateReplacer / OrchestrateGrayscale(15D/15E)
- CapacityService + StubCapacityService(15F 存根)
- ProbeStateReader(15A)、ThirdPartyProber / TargetProvider(15C)
- Notifier + LogNotifier(15G)、StaticTargetProvider
### server/internal/scheduler/scheduler.go
Scheduler 主体实现:
- Config 结构体:注入所有依赖 + 可配置的选主时间参数(方便测试)
- Run(ctx) = 安装 SIGTERM/SIGINT 信号 + Start(ctx)
- Start(ctx) = 启动三 loop goroutine + 可选 15C 采集 goroutine + 30s 优雅退出
- runLoop: 非主实例每 5s 抢租约(SET NX PX 30000)
- runLeaderLoop: 主实例按 interval tick,defer 释放租约
- keepLease: 每 10s Lua 校验后续租(PEXPIRE),失败即退出
- releaseLease: Lua 校验后 DEL,加速接管
- capacityTick: 15F check → 15E grayscale.Advance → 15F decay → 探针失联告警
- runThirdPartyProber: 无主从约束全副本跑(理由注释在代码中)
### server/internal/scheduler/wiring.go
BuildStubConfig(): 用存根 LC/Prov 构建可运行的 Config,
供 server main 和集成测试使用;待 #5/#14 就绪后替换真实实现。
### server/internal/scheduler/scheduler_test.go (go test -race 全通过)
- TestLeaderElection: 两实例竞争,任意时刻只有一个在 tick
- TestFollowerTakeover: 主实例退出后备实例在 ≤LeaseTTL 内接管
- TestGracefulShutdown: ctx 取消后三个 leader key 被主动 DEL
- TestCapacityTickProbeDisconnect: 失联探针触发 Notifier
- TestE2EMockScenario: 端到端 mock 剧本
up→suspect(降权10)→confirmed→down + 替换队列 →
CreateNode→probing→activating(weight10+BumpVersion+grayscale)→
draining_old→done,全链 node_events/audit_log 对账
### server/cmd/server/main.go
- 新增 SCHED_ENABLED=true 开关,灰度启动 scheduler
- 复用 REDIS_ADDR/REDIS_PASSWORD,probe.Store 共享给 scheduler
- 通过 signal.NotifyContext 传递 SIGTERM,确保优雅退出
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-15 22:45:56 +08:00
wangjia
b35bfe10dc
merge: 自动更换编排 + 养机灰度 [tsk_mxEFSvKHX64G]
2026-06-15 22:05:22 +08:00
wangjia
7627c8a195
feat(scheduler): 自动更换编排 + 养机灰度 [tsk_mxEFSvKHX64G]
...
实现 15E 任务:Replacer 状态机(pending→creating→probing→activating→draining_old→done/failed)
+ Grayscale 权重梯度(10→25→50→75→100,每步 6h)。
关键设计:
- Redis JSON 记录(sched:replace:{uuid})+ SetNX 崩溃安全,防止重复 CreateNode
- 幂等 key:attempt 0 = uuid,retry N = uuid:retry:N
- probePass:国内 ≥2/3 ISP 通过 + 海外 L1 全通,连续 2 Tick 才晋级
- 探活超时 15min → failProbeAttempt → 轮换 provider;3 次失败 → failed + NotifyFault
- StubBreaker(15F 未就绪);NewReplacer(Config{}) 依赖注入
- 6 个集成测试全部通过(happy path / max-retry / crash recovery / queue idempotency / grayscale)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-15 21:26:07 +08:00
wangjia
fb267e1114
merge: iOS 端 PoC M3 + entitlement 申请 [tsk_F5r4Kt9m_wQU]
2026-06-15 20:56:46 +08:00
wangjia
be44ef1226
merge: iOS M3 PoC — PacketTunnelProvider + entitlement [tsk_nsobbj_rJdy0]
2026-06-15 20:56:46 +08:00
wangjia
2ebd8c4054
merge: Android M2 VPN 隧道 PangolinVpnService [tsk_w1NvZdmwGPyd]
2026-06-15 20:56:37 +08:00
wangjia
323e1da04d
merge: 桌面端 PoC M1 — sing-box 子进程 TUN 接线 [tsk_SLCsjNgtmng3]
2026-06-15 20:56:37 +08:00
wangjia
58e2bb0190
merge: gRPC server + hub + 跨实例 pub/sub [tsk_mF5RWPEwngai]
2026-06-15 20:56:28 +08:00
wangjia
5d54aa2fb2
merge: 判定引擎 signals+rules+streak [tsk_OYEiDCzM9_0Y]
2026-06-15 20:56:22 +08:00
wangjia
690f990722
merge: devices + 订阅校验中间件 [tsk_x7wrlA87orsY]
2026-06-15 20:56:14 +08:00
wangjia
2c182fcdcf
merge: probe ingest endpoint + Redis store [tsk_rBPr0Xuy10bz]
2026-06-15 20:55:32 +08:00
wangjia
bfecb4314c
merge: gRPC server + hub + 跨实例 pub/sub [tsk__58l3wTLvaSn]
2026-06-15 20:53:25 +08:00
wangjia
bf3b6fd210
merge: apierr + idgen + CONVENTIONS.md [tsk_nI2T8qpcoier]
2026-06-15 20:51:08 +08:00
wangjia
1648871a2a
docs(infra): 补全拓扑图 CSS;todo: 更新任务进度
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-15 19:26:08 +08:00
wangjia
b2cb20be47
chore(design): 删除 design/flutter fork,更新 .gitignore
...
删除 design/flutter/(21 个文件):
- Flutter token/组件的 canonical 实现已在 client/lib/
- Token 数值层改由 design/codegen/gen_flutter_tokens.mjs codegen
- 设计规格由 design/preview/ specimens + SKILL.md 表达
.gitignore 新增:
- client/android/local.properties、client/android/app/src/main/java/
- client/ios/Flutter/ephemeral/、client/pubspec.lock
- .claude/worktrees/(临时工作目录)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-15 19:11:01 +08:00
wangjia
b04cef3cc4
chore(design): 建立 token 单源管线,删除 design/flutter fork
...
- 新增 design/codegen/gen_flutter_tokens.mjs:
从 colors_and_type.css codegen 出 client/lib/pangolin_tokens.gen.dart
覆盖 PangolinColors/Spacing/Radius/Motion/Shadow(纯数值层)
- 重构 client/lib/pangolin_theme.dart:
删除手抄数值,import+export pangolin_tokens.gen.dart
保留实现层 PangolinScheme/PangolinText/PangolinTheme/PangolinContext
19 个引用方零改动,对外 API 完全不变
- 新增 web/usercenter/scripts/build-tokens.mjs:
仿 website 样板,prebuild/predev 自动从 css 生成 public/colors_and_type.css
去除 usercenter 手抄副本的漂移风险
- 更新 CLAUDE.md:补 token codegen 使用说明与单源模型
- 更新 design/SKILL.md:移除已删 flutter/ 引用,补 codegen 入口
验证:改 clay-500 → 三端 codegen 同步变化;flutter analyze 无新增 error。
注:design/flutter/ fork 手动删除(git rm 需用户执行)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-15 18:27:22 +08:00
wangjia
88757b2ac4
feat(detect): 判定引擎 signals+rules+streak (tsk_OYEiDCzM9_0Y)
...
新建 server/internal/scheduler/detect/ 包,实现任务 15D:
## 核心模块
**lifecycle.go**
- 定义 LifecycleService Go interface(ListNodes / TransitionStatus / SetWeight /
BumpVersion / GetLoad / GetLoadHistory),供 #5 真实实现,内含乐观锁语义(0 行
受影响即本周期放弃,幂等可重入)
- 提供 MockLifecycle 内存 stub(SetConflict / Events 等测试辅助方法)
**signals.go**
- computeSignals:读 SnapshotsByNode,归一化为 NodeSignal
- domesticFailISPs:按 ISP 聚合(stripPrefix "3rd-" 合并 15B/15C 同 ISP 视角),
L3 失败权重最高(L3 挂即记该 ISP 失败,即使 L1/L2 通),无数据 vantage 不计分母
- overseasOK:境外对照点全部 L1 通为真
- trafficDropPct:15min 前后半段在线数降幅百分比
- ProbeSnapshotter interface(解耦 probe.Store,便于 mock)
**rules.go**
- DetectConfig:全量阈值常量(DomesticFailNumerator/Denominator=2/3、
SuspectStreakMin=2、TrafficDropThreshold=80%、TrafficBaselineMin=20、
ConfirmedStreakMin=6、RecoverStreakMin=2、SuspectWeight=10)
- isSuspectTriggered:整数算术避免浮点误差(fail×den >= total×num)
- isFault / effectiveSuspectStreakMin / isTrafficWarning 规则助手
**streak.go**
- StreakStore:Redis hash detect:streak:{node}(fail_streak / suspect_streak /
recover_streak),TTL 2h(宽于 6×5min=30min),进程重启后自动恢复
**engine.go**
- Engine.Tick(ctx):列举 up/blocked_suspect 节点,按节点依序运行五条规则
- Rule 1 suspect:≥2/3 ISP 失败 + 境外正常 → 连续 2 周期 → up→blocked_suspect,
weight→10,写 probe:freq:{node}=60(1min 提频标记),BumpVersion
- Rule 2 流量预警:trafficDrop≥80% + 基线≥20 → suspect 门槛放宽至 1 周期
- Rule 3 confirmed:blocked_suspect 连续 6 周期 → blocked_confirmed → down(跳过
draining),入队 detect:replace:queue(JSON: nodeId + replacementUuid)
- Rule 4 recover:suspect 期间境内连续 2 周期恢复 → up(weight 交 15E warmup)
- Rule 5 fault:境内+境外同时失败 → 不迁移,调 Notifier.NotifyFault(log stub)
- Notifier interface + LogNotifier stub(15G 就绪前输出 slog.Warn)
**engine_test.go**(19 个测试,全部通过)
- 表驱动覆盖:恰好 2/3 失败、1/3 失败不触发、9min vs 10min streak、
流量预警 1 周期即 suspect、suspect 第 6 周期 confirmed、境内外同挂 fault 不迁移、
恢复 2 周期回 up、3rd- 前缀合并、L3 覆盖 L1/L2
- 乐观锁冲突:SetConflict → 放弃本周期 → 无副作用 → 解冲突后正常重试
- 进程重启:同一 miniredis,新 Engine 读取已存在 streak 继续计数不误清零
- 产出验证:replace queue JSON 结构、probe:freq 标记、weight 变更、事件 detail 字段
**migrations/000010_node_blocked_statuses.{up,down}.sql**
- 扩展 nodes.status ENUM 加入 blocked_suspect / blocked_confirmed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 20:15:13 +08:00
wangjia
7a1715bd09
merge: iOS 端 PoC M3 + entitlement 申请 [tsk_F5r4Kt9m_wQU]
...
解决合并冲突(原任务 tsk_nsobbj_rJdy0 分支 maestro/tsk_nsobbj_rJdy0):
两分支修改文件集合无重叠,手动 apply iOS PoC commit (e129f09 )。
变更内容:
- client/ios/PacketTunnel/PacketTunnelProvider.swift:完整 M3 实现
(startTunnel/stopTunnel/handleAppMessage/LibboxPlatformInterface)
- client/ios/PacketTunnel/MemoryMonitor.swift:新增 NE 进程内存打点
- client/ios/PacketTunnel/PacketTunnel.entitlements:NE + App Group 权限
- client/ios/Runner/Runner.entitlements:主 App NE + App Group 权限
- client/ios/Runner/VpnManager.swift:红线词修复 + App Group 缓存 + NEVPNStatus 订阅
- client/ios/Runner/Info.plist:NSVPNUsageDescription 红线词修复
- client/ios/PacketTunnel/Info.plist:CFBundleDisplayName 红线词修复
- client/ios/Runner.xcodeproj/project.pbxproj:
CODE_SIGN_ENTITLEMENTS 添加到全部 build config;MemoryMonitor 加入 Sources
- app/kernel/build-ios.sh:M3 内存裁剪决策记录
- doc/ne-entitlement-申请指引.md:新增 NE entitlement 申请完整指引
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 20:14:32 +08:00
wangjia
8ed1cfcc75
merge: gRPC server + hub + 跨实例 pub/sub [tsk_mF5RWPEwngai]
...
Resolve merge conflict between maestro/tsk__58l3wTLvaSn (gRPC nodes feature)
and main (admin backend + JWT config + OpenAPI routes):
- server/cmd/server/main.go: keep admin backend (startAdminIfConfigured) AND
add gRPC agent server (startGRPC) — both coexist as independent optional
listeners governed by their respective env vars
- server/internal/config/config.go: keep JWT fields (JWTPrivateKeyPath /
JWTKeyID / JWTPublicKeys) AND add gRPC fields (GRPCAddr / CAKeyPath /
CACertPath / GRPCCertPath / GRPCKeyPath)
- server/internal/nodes/: add all new files from tsk__58l3wTLvaSn
(hub.go, hub_test.go, load.go, handler_grpc.go, grpc_test.go,
service.go, store.go) — 18 tests, all passing
Test: go test ./internal/nodes/... → PASS (18/18)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 20:10:01 +08:00
wangjia
628440b3f9
style(pangolin): gofmt store/mysql.go(import 排序)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-13 18:52:30 +08:00
wangjia
20a1f8e2af
merge: 管理端最小后台 + TOTP 双因素 [tsk_SCMtcGF4F434]
...
以 main 现有 cmd/server 设计为准(保留 probe + /v1 路由),叠加分支新增:internal/admin/ 自包含后台(session/2FA/IP 白名单/审计/模板)、internal/totp/ + cmd/adminctl/(TOTP + 管理员创建 CLI)、cmd/server/main.go 的 startAdminIfConfigured()(opt-in:ADMIN_SECRET_KEY+DB_DSN 才起)。统一 getenvDefault 命名;go.mod 取 HEAD + go mod tidy(x/term 提为直接依赖);admins 迁移与 main 既有 000008_provision 撞号,重排为 000009_admins。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-13 18:47:38 +08:00
wangjia
03b00b9a0f
merge: config + store(UTC DSN) + 自动迁移 [tsk_zRA6fGU1JuHj]
...
以 main 现有 config 设计为准(FromEnv/*Config/单 DSN/RS256),移植分支新增的 store 层:mysql.go 的 Open(*config.Config) 改用 cfg.DSN、结构化强制 UTC + 会话时区断言;migrate.go + migrations/embed.go 提供 golang-migrate 嵌入式迁移;cmd/migrate/main.go 实现 migrate CLI(读 DB_DSN)。未采用分支的 Load()/PANGOLIN_ 前缀/MySQL 拆分字段(与 main 设计冲突)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-13 18:42:46 +08:00
wangjia
e91f0063e0
chore(pangolin): 还原批量合并误并入的 doc/todo WIP(回退到 WIP 前,WIP 抽回为未提交)
2026-06-13 18:06:53 +08:00
wangjia
3c6a8a517a
feat: 桌面端 PoC M1 — sing-box 子进程 TUN 接线 [tsk_SLCsjNgtmng3]
...
实现桌面端(macOS PoC)内核子进程管理,打通 sing-box TUN 模式全链路:
### client/lib/bridge/kernel_process.dart(完整实现,替换原有 stub)
- ClashApiClient: HTTP 客户端,支持 /connections / /proxies / /traffic(SSE) / /traffic(plain)
- Bearer Token 鉴权;getConnections 用于就绪探测与流量统计;getTraffic SSE 读首帧后断开
- KernelProcess 接口: 新增 statusStream / statsStream 至接口定义
- DesktopKernelProcess:
- spawn(configPath): 解析 Clash API 端口/secret → sudo sing-box run(macOS PoC)→
轮询 /connections 等待就绪(20s 超时)→ emit connecting→on
- kill(): SIGTERM + 等待 gracePeriod(5s) → SIGKILL → emit off
- 意外退出: emit error(UI 可一键重连,不崩溃)
- 统计轮询: 每秒 GET /connections,差分算 uploadSpeed/downloadSpeed
- 二进制解析: ENV > exe同目录 > macOS Bundle Resources > 开发目录 > /usr/local/bin
- 辅助函数: generateClashApiPort(高位随机)、generateClashApiSecret(32B hex)
### client/lib/bridge/desktop_vpn_bridge.dart(新文件)
- DesktopVpnBridge implements VpnBridge:
- start(configJson): injectClashApi(注入随机端口+secret)→ writeConfig(0600)→ kernel.spawn
- stop(): kernel.kill(5s)
- statusStream / statsStream: 代理 KernelProcess 事件流
- selectOutbound: Clash API PUT /proxies/{group}
- getActiveOutbound: 从 /proxies 读 now 字段
- configDirOverride: 测试注入支持
- injectClashApi: 静态方法,尊重已有 clash_api 配置,合并保留 experimental 其他字段
### client/test/bridge/kernel_process_test.dart(新文件)
- ClashApiClient 完整测试: headers / 解析 / PUT body / DELETE / 非 200 抛 HttpException
- generateClashApiPort / generateClashApiSecret 生成范围和格式测试
- DesktopVpnBridge.injectClashApi: 注入 / 尊重已有 / 保留字段 / 保留其他 experimental / 非法 JSON
- DesktopVpnBridge + FakeKernelProcess 集成: connecting→on / stop→off / statsStream / 意外退出 / spawn失败
### app/kernel/poc/(新目录)
- reality_client.config.json.tmpl: VLESS+REALITY+TUN 客户端配置模板
- TUN inbound: auto_route + strict_route(macOS kill-switch 基础保护)
- DNS: 防泄露(remote via VPN + cn 直连)
- experimental.clash_api: 随机端口 + secret 占位符
- gen-poc-config.sh: 渲染模板为可用 JSON(从环境变量读 REALITY 参数)
- README.md: 完整 PoC 接线与 M1 验收步骤
提权说明(macOS PoC):
· sudo 提权(开发机需配 /etc/sudoers.d/pangolin-singbox 或有 sudo 缓存)
· 正式版: SMJobBless Helper + 公证(BACKLOG-11D-HELPER)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 17:53:50 +08:00
wangjia
83c23f968a
feat(android): 实现 M2 Android VPN 隧道 — PangolinVpnService 接入 libbox [tsk_w1NvZdmwGPyd]
...
接线 11B libbox.aar + 11C Dart 桥接骨架,完成 Android 端 M2 PoC:
### PangolinVpnService(完整实现)
- `handleStart(configJson)` → 后台线程 → `Libbox.newBoxService(platformInterface, json)` → `start()`
- 内 inner class `PangolinPlatformInterface` 实现 libbox `PlatformInterface`:
- `openTun(TunOptions)`: 调用 `VpnService.Builder()` 配置地址/MTU/路由,`establish()` 取 TUN fd
- `autoDetectInterfaceControl(fd)`: `VpnService.protect(fd)` 防环路
- 安全 getter 兜底:TunOptions 方法名与 libbox.aar 不符时降级到 PoC 默认值
- 统计双轨:优先 libbox `CommandClient`(status/traffic)→ 失败时退回 `TrafficStats` 定时器
- 幂等 `doStop()`:commandClient.disconnect → boxService.close → tunPfd.close → stopForeground
- `onRevoke()` 推 error 状态再 doStop,不留假 on
### VpnEventBus
- 新增 `currentStatus` 字段(volatile),供 `getStatus()` 一次性查询
### MainActivity
- `VpnService.prepare()` 权限流程:未授权 → 弹 prepare Intent → `onActivityResult` 后启动
- `getStatus()` 从 `VpnEventBus.currentStatus` 返回真实状态
- 首次连接时引导 `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS`(后台存活)
### AndroidManifest.xml
- `android:foregroundServiceType="specialUse"`(Android 14 API 34 强制要求)
- 新增权限:`FOREGROUND_SERVICE_SPECIAL_USE`、`REQUEST_IGNORE_BATTERY_OPTIMIZATIONS`
### build.gradle
- `implementation files('../../../app/kernel/dist/android/libbox.aar')`(条件存在检查)
- `minSdkVersion` 提升至 21(gomobile -androidapi 21 要求)
### 新增文件
- `assets/poc_config.json`: PoC 单节点测试配置模板(含占位符 __NODE_HOST__ 等)
通知文案已过红线词扫描:「加速已开启」「正在连接…」「穿山甲加速」,无 VPN/翻墙等禁词。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 17:45:13 +08:00
wangjia
e129f094c2
feat(ios): M3 PoC — PacketTunnelProvider + entitlement + 内存监控 [tsk_nsobbj_rJdy0]
...
### 新增文件
- client/ios/PacketTunnel/MemoryMonitor.swift
周期打点 NE 进程内存(os_proc_available_memory iOS13+ / mach_task_basic_info 兼容
iOS12);每 10s 采样一次;availableMemory < 5MB 时输出 ⚠️ 警告;提供
summaryString() 供 handleAppMessage/getMemory IPC 返回。
- client/ios/PacketTunnel/PacketTunnel.entitlements
Extension 的 NE + App Group entitlement 文件;申请说明写入注释。
- client/ios/Runner/Runner.entitlements
主 App 的 NE + App Group entitlement 文件;申请说明写入注释。
- doc/ne-entitlement-申请指引.md
完整申请操作手册:Bundle ID 清单、Apple Developer Portal 步骤、
专项审批英文申请文案(已脱敏,无红线词)、审批 lead time 说明。
### 修改文件
- client/ios/PacketTunnel/PacketTunnelProvider.swift
完整 M3 实现(替换原骨架 TODO):
· startTunnel:解析 configJson(options/App Group 两路回退)→
buildNetworkSettings → setTunnelNetworkSettings → setupTunBridge
→ startLibbox → startPacketBridging → memoryMonitor.start()
· stopTunnel:memoryMonitor.stop() → stopLibbox → closeTunBridge
· handleAppMessage:JSON IPC 协议(getStatus/getMemory/selectOutbound)
· LibboxPlatformInterface 扩展(#if canImport(Libbox)):
openTun 返回 socketpair libbox 侧 fd,autoDetectInterfaceControl no-op,
writeLog 转 os.log,useProcFS 返回 false
· packetFlow 双向桥接(readFromPacketFlow/writeToPacketFlow 循环)
· parseTunAddress/parseDNSServers 从 JSON 提取网络参数
- client/ios/Runner/VpnManager.swift
· localizedDescription "穿山甲 Pangolin VPN" → "Pangolin 加速"(红线词修复)
· 新增 appGroup 常量(group.com.pangolin.pangolinVpn)
· start() 写入 App Group UserDefaults 缓存 configJson
- client/ios/Runner/Info.plist
NSVPNUsageDescription "穿山甲使用 VPN…" → "Pangolin 使用网络加速通道…"(红线词修复)
- client/ios/PacketTunnel/Info.plist
CFBundleDisplayName "穿山甲 Tunnel" → "Pangolin 加速通道"(红线词修复)
- client/ios/Runner.xcodeproj/project.pbxproj
· Runner + PacketTunnel 所有 build config 添加 CODE_SIGN_ENTITLEMENTS
· 新增 MemoryMonitor.swift 到 PacketTunnel Sources 构建阶段
· 新增 Runner.entitlements / PacketTunnel.entitlements / MemoryMonitor.swift
文件引用及分组
- app/kernel/build-ios.sh
添加 M3 内存裁剪决策记录:gVisor→grpc→QUIC 优先裁减顺序及内存估算,
等待真机 10min 压测结果后更新最终结论
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 17:44:11 +08:00
wangjia
ed5eabea58
merge: 第三方拨测 API 适配 [tsk_-WC9smQK7YRt]
2026-06-13 17:32:26 +08:00
wangjia
bf1717a369
merge: 控制面联调 M6 [tsk_nuoKSM4Vt-zK]
...
# Conflicts:
# .gitignore
# client/lib/widgets/home_shell.dart
# client/lib/widgets/server_tile.dart
# client/pubspec.yaml
2026-06-13 17:32:26 +08:00
wangjia
b89aeb0905
merge: app/kernel 构建管线(AAR/XCFramewor [tsk_d8KmVEzJTCjl]
...
# Conflicts:
# .gitignore
2026-06-13 17:32:26 +08:00
wangjia
b05d7ed416
merge: oapi-codegen 接线 + 生成代码入库 [tsk_Kp80nvHV2yLc]
...
# Conflicts:
# server/Makefile
# server/cmd/server/main.go
# server/go.mod
# server/go.sum
2026-06-13 17:32:25 +08:00
wangjia
605bfa1ec9
feat(nodes): gRPC server + Hub + Redis pub/sub cross-instance delivery (tsk__58l3wTLvaSn)
...
Implements AgentService gRPC server with all 6 RPCs (Enroll/Register/Heartbeat/
Subscribe/Ack/ReportUsage), Hub command routing with Redis ZSET at-least-once
persistence and cross-instance pub/sub delivery, LoadCache for node:load metrics,
NodeStore SQL interface + MySQL implementation, and full mTLS gRPC listener in main.
Integration tests: 18 tests covering full Enroll→Register→Heartbeat→Subscribe→Ack
flow, reconnect resume with last_command_id, and cross-instance pub/sub delivery
via bufconn + miniredis + mockNodeStore + real mTLS certificates.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 17:30:42 +08:00
wangjia
8de32eb611
merge: 弹性节点基建:Terraform + 一键更换 [tsk_6u0FxmbC7Yeq]
...
# Conflicts:
# .gitignore
# infra/cloud-init/node.yaml.tmpl
2026-06-13 17:30:37 +08:00
wangjia
6f6cb97509
merge: Flutter 逐屏还原 + iPad 断点 [tsk_gpPXi-icyeOE]
...
# Conflicts:
# client/lib/widgets/connect_button.dart
# client/lib/widgets/home_shell.dart
# todo/todo.html
# todo/todo.json
2026-06-13 17:30:36 +08:00
wangjia
4e2a444572
merge: usage 用量 + 广告解锁 [tsk_1taxhtV2k3RP]
...
# Conflicts:
# server/internal/apierr/apierr.go
2026-06-13 17:30:36 +08:00
wangjia
34c9909608
merge: auth 模块 [tsk_2PFfyviECIXh]
...
# Conflicts:
# server/go.mod
2026-06-13 17:29:49 +08:00
wangjia
a47121f99b
feat(probe): add third-party dial-test adapter for 15C (tsk_-WC9smQK7YRt)
...
Implements ProberAgent interface + Alibaba Cloud CloudMonitor Synthetic adapter:
- ProbeTarget / VantageResult types aligned with 15A schema (L3 always nil)
- AliyunSyntheticAgent: CreateSiteMonitor → poll → map to VantageResult
- ISP vantage uses "3rd-{ISP}" prefix; Redis key probe:{node}:CN:{region}:3rd-*
- Degradation: API error/rate-limit/timeout → skip vantage, log + counter (no write)
- RunOnce(ctx, targets) shape for 15H assembly wiring
- Credentials via cfg only (env-var injection, zero hardcoded secrets)
- 6 new tests (mock vendor server): normalization, rate-limit, timeout, Redis keys,
failed-target L1, no-credentials check; all 22 probe-package tests pass
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 17:21:43 +08:00
wangjia
9423069a0b
merge: maestro/tsk_iP5-_Ztq5THx [tsk_iP5-_Ztq5THx]
2026-06-13 17:04:18 +08:00
wangjia
b456a8816f
merge: maestro/tsk_V9IrIk2g5Q78 [tsk_V9IrIk2g5Q78]
2026-06-13 17:04:17 +08:00
wangjia
3bcf1d7b4c
merge: maestro/tsk_NU9JuUweHWMt [tsk_NU9JuUweHWMt]
2026-06-13 17:04:17 +08:00
wangjia
96040c802f
merge: maestro/tsk_3FIPC8lSnAfJ [tsk_3FIPC8lSnAfJ]
2026-06-13 17:04:17 +08:00
wangjia
c642daec42
merge: maestro/tsk_acMYQ-Z-EIF_ [tsk_acMYQ-Z-EIF_]
2026-06-13 17:04:17 +08:00
wangjia
5a6b4a2dbb
merge: maestro/tsk__R8M4jEw43JR [tsk__R8M4jEw43JR]
2026-06-13 17:04:17 +08:00
wangjia
5a485fbcf6
merge: maestro/tsk_FrRarw9YO8Nf [tsk_FrRarw9YO8Nf]
2026-06-13 17:04:17 +08:00