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>
This commit is contained in:
wangjia
2026-06-13 17:53:50 +08:00
parent ed5eabea58
commit 3c6a8a517a
6 changed files with 1576 additions and 63 deletions
+81
View File
@@ -0,0 +1,81 @@
#!/usr/bin/env bash
# gen-poc-config.sh — 渲染 REALITY 客户端 PoC 配置(tsk_SLCsjNgtmng3
#
# 用法: ./gen-poc-config.sh [--out <output.json>]
#
# 从环境变量读取参数(须全部提供,无 --干跑 模式):
# SERVER_HOST EC2 IP(如 18.136.60.128
# SERVER_PORT Xray VLESS 监听端口(如 11443
# REALITY_UUID 客户端 UUID
# REALITY_PUBLIC_KEY REALITY 公钥
# REALITY_SHORT_ID REALITY shortId
# REALITY_SNI TLS SNI(如 www.apple.com
#
# 可选:
# CLASH_API_PORT Clash API 端口(默认随机 49152-65535
# CLASH_API_SECRET Clash API secret(默认随机 32 字节 hex
#
# 产物默认写到 /tmp/pangolin-poc-config.json(权限 0600)。
#
# 注意:REALITY 私钥等参数绝不入库,仅在运行时从 EC2 secrets/ 或 Bitwarden 取。
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TMPL="${SCRIPT_DIR}/reality_client.config.json.tmpl"
OUT="${1:---out}"
if [[ "${OUT}" == "--out" ]]; then
OUT="${2:-/tmp/pangolin-poc-config.json}"
shift 2 2>/dev/null || true
fi
# ── 参数检查 ──────────────────────────────────────────────────────
_require() {
local name="$1"
if [[ -z "${!name:-}" ]]; then
printf '✗ 环境变量 %s 未设置\n' "$name" >&2
printf ' 示例: export %s=<value>\n' "$name" >&2
exit 1
fi
}
_require SERVER_HOST
_require SERVER_PORT
_require REALITY_UUID
_require REALITY_PUBLIC_KEY
_require REALITY_SHORT_ID
REALITY_SNI="${REALITY_SNI:-www.apple.com}"
# 生成随机 Clash API 端口和 secret(若未指定)
if [[ -z "${CLASH_API_PORT:-}" ]]; then
CLASH_API_PORT="$(python3 -c 'import random; print(random.randint(49152,65535))')"
fi
if [[ -z "${CLASH_API_SECRET:-}" ]]; then
CLASH_API_SECRET="$(python3 -c 'import secrets; print(secrets.token_hex(32))')"
fi
# ── 渲染模板 ──────────────────────────────────────────────────────
printf '==> 渲染配置 → %s\n' "$OUT"
sed \
-e "s|__SERVER_HOST__|${SERVER_HOST}|g" \
-e "s|__SERVER_PORT__|${SERVER_PORT}|g" \
-e "s|__UUID__|${REALITY_UUID}|g" \
-e "s|__REALITY_PUBLIC_KEY__|${REALITY_PUBLIC_KEY}|g" \
-e "s|__REALITY_SHORT_ID__|${REALITY_SHORT_ID}|g" \
-e "s|__REALITY_SNI__|${REALITY_SNI}|g" \
-e "s|__CLASH_API_PORT__|${CLASH_API_PORT}|g" \
-e "s|__CLASH_API_SECRET__|${CLASH_API_SECRET}|g" \
"${TMPL}" > "${OUT}"
chmod 600 "${OUT}"
printf '✓ 已写入: %s\n' "$OUT"
printf ' Clash API: http://127.0.0.1:%s (secret len=%d)\n' \
"$CLASH_API_PORT" "${#CLASH_API_SECRET}"
printf ' Server: %s:%s\n' "$SERVER_HOST" "$SERVER_PORT"
printf '\n提示: 使用此配置测试前,请确认 sing-box 有 TUN 权限:\n'
printf ' sudo sh -c "echo \"%%(whoami)s ALL=(root) NOPASSWD: /path/to/sing-box\" >> /etc/sudoers.d/pangolin"\n'