wangjia
908f75de75
merge: maestro/tsk_VRzw-af__qWx [tsk_VRzw-af__qWx]
2026-06-13 17:04:17 +08:00
wangjia
b7aca4848a
merge: maestro/tsk_rcNKc3GQHBUd [tsk_rcNKc3GQHBUd]
2026-06-13 17:04:17 +08:00
wangjia
6f3ceba7a9
merge: maestro/tsk_eu2qVvni1HJR [tsk_eu2qVvni1HJR]
2026-06-13 17:04:17 +08:00
wangjia
e2646346a6
feat(web/usercenter): Next.js 用户中心静态导出 + mock/http 双数据层 (tsk_3FIPC8lSnAfJ)
...
新建 web/usercenter/:Next.js App Router + output:'export' 纯静态导出,
直接复用 design/ui_kits/usercenter React 源码(概览/订阅/兑换/邀请/设置)。
阶段 A(mock,本提交):
- 复刻五大页面,明/暗 × zh/en 四态;colors_and_type.css 原样链入;
顶栏主题切换 + 语言段控,移动端底部 Tab + 左右滑动切换。
- 设置页新增:偏好(语言/主题) + 设备管理(列表/移除/二次确认+刷新) +
TOTP 2FA(绑定二维码占位+密钥/验证/解禁),登录二段式 TOTP。
- 数据层 lib/api:ApiClient 抽象 + MockClient/HttpClient 双实现,构建期
NEXT_PUBLIC_API_MODE 切换;统一错误体 {code,message_zh,message_en} → 双语映射。
- 会话:access token 仅内存,refresh header token + localStorage,静默续期,
登出失效(取舍:静态导出无服务端 cookie 能力,详见 README)。
- 安全:构建期注入 SRI(sha384);_headers 严格 CSP + 安全基线;
红线词扫描(铁律13) CI 红线,零命中。
阶段 B(占位待联调):HttpClient 已写好域名池+退避重试+401 续期;
TOTP/登录二段式端点占位待 #1 契约增补;me/redeem/devices 切真实链路依赖 #2/#3/#4。
验证:npm run lint / redline / build 均通过,静态产物 out/ 无服务端依赖。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-13 15:23:18 +08:00
wangjia
6ec7cd8146
merge: maestro/tsk_V9IrIk2g5Q78 [proto 契约 + buf 工具链] (tsk_FrRarw9YO8Nf)
...
合并 proto 契约 + buf 工具链任务到 main(已含 apierr + idgen + CONVENTIONS.md):
- server/proto/pangolin/agent/v1/agent.proto: AgentService 6 个 RPC(Enroll/Register/
Heartbeat/Subscribe/Ack/ReportUsage)及全量消息类型,隐私红线注释保留
- server/buf.yaml: v2 格式,STANDARD lint + FILE breaking 规则
- server/buf.gen.yaml: remote protoc-gen-go v1.36.4 + protoc-gen-go-grpc v1.5.1 插件
- server/internal/pb/: 生成的 agent.pb.go + agent_grpc.pb.go 提交入库
- server/Makefile: 新增 proto / proto-lint / proto-breaking 目标
- server/tools.go: 新增 buf v1.70.0、protoc-gen-go、protoc-gen-go-grpc 工具依赖
- server/go.mod / go.sum: 合并 buf 工具链依赖;github.com/google/uuid 保留为 direct
依赖(idgen 直接 import);go 版本升至 1.25.10
冲突解决:两侧均修改 go.mod,以 proto 分支(超集)为基础,将 google/uuid 移至
直接依赖 require 块,保留 apierr/idgen 的已有文件不变。
go build ./... ✓ | go test ./internal/apierr/... ./internal/idgen/... ✓
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 15:11:11 +08:00
wangjia
d88c1ae647
merge: maestro/tsk_x7wrlA87orsY [devices + 订阅校验中间件] (tsk_VRzw-af__qWx)
...
手动合并 tsk_x7wrlA87orsY(设备管理 + 订阅校验中间件)到 main:
冲突解决:
- server/internal/apierr/apierr.go:保留 tsk_GXDoc3Cs07Rn 版本(New/StatusFor/
Middleware/ErrConflict/改善文档),并入 tsk_x7wrlA87orsY 新增的 ErrAccountBanned
及对应 StatusFor case(→ 403)。
新增文件(来自 tsk_x7wrlA87orsY):
- server/internal/devices/doc.go package 文档(替换占位 stub)
- server/internal/devices/context.go CtxKeyUserID / Plan / WithPlan / PlanFromCtx
- server/internal/devices/handler.go GET /v1/me/devices · DELETE /v1/me/devices/{id}
- server/internal/devices/middleware.go SubscriptionMiddleware · CheckDeviceQuota · RequirePaidTier
- server/internal/devices/service.go RegisterIfAbsent / DeleteDevice / ResolvePlan + 纯函数 resolveEffectivePlan
- server/internal/devices/store.go MySQL 数据访问层
- server/internal/devices/service_test.go 15 个单测(全通过)
- server/internal/devices/devices_integration_test.go testcontainers 集成测试
OpenAPI 更新(来自 tsk_x7wrlA87orsY):
- server/api/openapi.yaml:SubscriptionInfo.source 枚举补 free
- design/server/openapi.yaml:SubscriptionInfo.source 枚举补 admin, free
测试:go build ./... ✓;go test ./internal/apierr/... ✓(8 tests);
go test ./internal/devices/... ✓(15 tests)。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 15:09:51 +08:00
wangjia
c84b6ea7da
merge: maestro/tsk_iP5-_Ztq5THx [桥接 API 面 Channel 契约 + 三端原生骨架] [tsk_rcNKc3GQHBUd]
...
合并冲突补救:原分支从 merge base 7871512 分叉,仅修改 client/ 目录,
与 main 新增的 server/(apierr/idgen/CONVENTIONS.md) 完全不重叠,
无真实文件冲突,手动应用 feature branch 的全部 client/ 改动。
client 侧新增:
- lib/bridge/vpn_bridge.dart — Dart↔原生通道契约(冻结)
- lib/bridge/vpn_bridge_mock.dart — 假内核,UI 联调用
- lib/bridge/kernel_process.dart — 桌面子进程管理接口
- ios/PacketTunnel/{Info.plist,PacketTunnelProvider.swift} — NEPacketTunnel 骨架
- ios/Runner/VpnManager.swift — NETunnelProviderManager 封装
- android/.../PangolinVpnService.kt — VpnService 骨架
- android/.../VpnEventBus.kt — Application 级状态总线
- test/bridge/vpn_bridge_mock_test.dart — 桥接层单元测试
client 侧修改:
- android/.../MainActivity.kt — 注册三通道(MethodChannel + 2×EventChannel)
- android/.../AndroidManifest.xml — 声明 FOREGROUND_SERVICE + PangolinVpnService
- ios/Runner/AppDelegate.swift — 注册通道 + VpnManager 初始化
- ios/Runner/Info.plist — 新增 NSVPNUsageDescription
- ios/Runner.xcodeproj/project.pbxproj — 添加 PacketTunnel extension target
- lib/widgets/connect_button.dart — VpnStatus 迁移到 bridge/vpn_bridge.dart
- lib/widgets/home_shell.dart — error 状态 UI 分支补全
2026-06-13 14:58:38 +08:00
wangjia
52857d1d55
feat(1e): config PANGOLIN_ prefix + store.Open UTC DSN + go:embed migrations (tsk_zRA6fGU1JuHj)
...
- internal/config: rewrite Config with PANGOLIN_ prefix fields
(HTTPAddr, AdminAddr, GRPCAddr, MySQL DSN/fields, RedisAddr,
AutoMigrate, JWTSecret placeholder, WebhookSecret); Load() replaces
FromEnv(); missing required MySQL vars return named-field error.
- internal/store/mysql.go: single Open(cfg) entry point; uses
mysql.ParseDSN to structurally override ParseTime=true, Loc=UTC,
Collation=utf8mb4_unicode_ci, Params[time_zone]='+00:00'; asserts
SELECT @@session.time_zone=+00:00 after Ping (startup fatal).
- migrations/embed.go: //go:embed *.sql exposes var FS embed.FS.
- internal/store/migrate.go: MigrateUp/MigrateDown/MigrateVersion
backed by golang-migrate iofs source + mysql driver; ErrNoChange
treated as success.
- cmd/migrate/main.go: filled — up/down/version subcommands, reads
config.Load() + store.Open.
- cmd/server/main.go: startup sequence Load → store.Open (UTC assert)
→ MigrateUp (if PANGOLIN_AUTO_MIGRATE=true) → HTTP listen;
structured slog output at each step.
- internal/store/mysql_test.go: pure-function unit tests for buildDSN
(empty-fields case + conflicting params overridden case); both pass.
- internal/store/mysql_integration_test.go: //go:build integration;
testcontainers mysql:8 — UTC assertion + MigrateUp idempotency.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 14:52:27 +08:00
wangjia
6e99e32285
feat(admin): 管理端最小后台(独立监听 + 白名单 + 2FA)tsk_SCMtcGF4F434
...
实现「一个二进制三个监听」中的管理端:
- 独立监听 ADMIN_LISTEN(默认 127.0.0.1:9443,FromEnv 拒绝 0.0.0.0/:: 等公网绑定)
- 中间件链:mw_ipallow(CIDR 白名单,仅信任 RemoteAddr,默认内网段)→
mw_session(HttpOnly+Secure+SameSite=Strict cookie,Redis 30min 滑动 TTL)
- 登录双因素:argon2id 密码 + RFC6238 TOTP;失败限流与临时锁定;
TOTP 密钥 AES-GCM 加密入库;migration 000008 增补 admins 表;
首个管理员由 cmd/adminctl create 创建(终端输出 otpauth URI/Secret)
- 功能三块(html/template + embed 静态资源,原生 JS 二次确认):
1. 码批次:表单生成→明文仅在本次 CSV 下载出现(不落盘/不入日志);
批次列表 + 整批作废(复用 #3 codes,新增 Store.ListBatches/VoidBatch)
2. 节点操作:列表 + 近期 node_events;replace 经 #14 ProvisionService、
draining/up 经 #5 Lifecycle(二者未就绪→注入 stub,UI 置灰);二次确认 + CSRF
3. audit_log 查看:actor/action/target/时间范围过滤分页;同页查 node_events
- 所有写操作写 audit_log(actor=用户名,meta 不含明文);管理端仅记安全事件
(登录失败/锁定/白名单拦截),不记常规访问日志
- internal/totp 复用包(与 doc/05 用户中心 2FA 同算法,纯标准库)
测试:totp RFC6238 向量、argon2/AES 往返、白名单放行/拦截、会话滑动过期、
登录成功/密码错/TOTP 错/未知用户/锁定、批次 CSV 含明文且 audit 不泄露、
作废/节点操作的二次确认与 CSRF 缺失被拒、mock 断言 service 调用参数、审计过滤。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-13 14:49:32 +08:00
wangjia
25e0204471
merge: maestro/tsk_GXDoc3Cs07Rn [tsk_nI2T8qpcoier] apierr + idgen + CONVENTIONS.md
...
Apply changes from tsk_GXDoc3Cs07Rn (conflict rescue: original auto-merge
blocked by uncommitted changes on main; worktree was clean, no code conflicts).
Changes applied verbatim from b64c002 :
- server/CONVENTIONS.md: new file — mandatory coding conventions
- server/internal/apierr: add New(), StatusFor(), ErrUnauthorized/Forbidden/
NotFound/Conflict predefined errors, chi Middleware; add apierr_test.go (8 tests)
- server/internal/idgen: implement idgen.go (UUID v7 + Crockford Base32 moved
from codes); add idgen_test.go (12 tests)
- server/internal/codes/generator.go: refactor to delegate to idgen.*
- server/go.mod: move google/uuid from indirect to direct dependency
All tests pass: go test ./internal/apierr/... ./internal/idgen/... ./internal/codes/...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 14:45:46 +08:00
wangjia
30e73b31c2
merge: maestro/tsk_GXDoc3Cs07Rn [apierr + idgen + CONVENTIONS.md]
2026-06-13 14:42:47 +08:00
wangjia
dd060fd34a
feat(1d): wire oapi-codegen, generate types/server/client, add 501 stubs — tsk_Kp80nvHV2yLc
...
- Fix openapi.yaml: quote bearerAuth description that contained unquoted `: ` plain-scalar YAML
- Add server/api/cfg-{types,server,client}.yaml — oapi-codegen v2 config for three split outputs
- Add server/api/gen/gen.go — three //go:generate directives; run `make generate`
- Generate server/api/gen/{types,server,client}.gen.go from the contract (all 15 operations)
- Add github.com/oapi-codegen/runtime v1.4.1 to go.mod (runtime types needed by generated code)
- Add server/internal/httpapi/unimplemented.go — UnimplementedServer satisfies gen.ServerInterface;
every method writes HTTP 501 + {code,message_zh,message_en} JSON body (no dep on task-1f apierr)
- Update server/cmd/server/main.go — mount /v1 API via gen.HandlerFromMuxWithBaseURL; keep /healthz
- Update server/Makefile — `generate` target now runs `go generate ./...`;
add `check-generate` CI guard (regenerate + git diff --exit-code)
- Add server/internal/httpapi/unimplemented_test.go — 19 httptest cases covering all 15 API routes
(501+body), undefined routes (404), and /healthz isolation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 14:36:21 +08:00
wangjia
f76e1797c2
merge: maestro/tsk_rBPr0Xuy10bz [tsk_rBPr0Xuy10bz] 探针汇聚入口 + Redis 探针存储 (tsk_eu2qVvni1HJR)
...
Manually apply changes from maestro/tsk_rBPr0Xuy10bz that could not be
auto-merged due to uncommitted changes on main at merge time.
Added:
- server/internal/scheduler/probe/types.go – frozen schema types (ReportRequest,
VantagePoint, NodeReport, L1/L2/L3Result, ProbeSnapshot)
- server/internal/scheduler/probe/store.go – Redis Store: SaveReports, CheckAndMarkSeen,
SnapshotsByNode, AliveProbes with TTL constants and key-schema docs
- server/internal/scheduler/probe/ingest.go – IngestHandler (POST /probe/report),
HMAC-SHA256 auth + timestamp window + replay prevention via SetNX
- server/internal/scheduler/probe/ingest_test.go – 17 tests (auth failures, integration,
Store unit tests); all pass with miniredis
Updated:
- server/cmd/server/main.go – register /probe/report route when PROBE_SECRETS env is set
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 14:35:24 +08:00
wangjia
f97ae8186b
feat(client): Flutter 逐屏还原 + iPad ≥900 断点 (tsk_gpPXi-icyeOE)
...
对照 design/ui_kits mobile+tablet 原型逐屏补齐 Flutter 客户端:
- l10n 资源层(strings_zh/en,单显)+ Riverpod 状态层(连接状态机/免费额度/
节点选择/语言/主题),UI 与数据解耦,mock 数据接 API 不动 UI。
- 连接键严格三态(off 虚线轨道环 / connecting 旋转弧 / on 满环+计时+光晕),
状态来自 connectionProvider,点击只派发事件——禁止乐观显示。
- 节点页置顶「智能选择」推荐卡(clay 渐变 zap + 推荐胶囊,默认选中);
免费额度卡(剩余分钟+进度条 ≤3 分钟切 warning + 看广告解锁变绿)。
- Tab 左右滑动切换(手势竞技场仲裁,子页滚动不误触发,200ms 方向感知滑入)。
- iPad/宽屏 ≥900 LayoutBuilder 切侧栏分栏(导航行高 ≥48,连接页双栏/节点双列网格),
复用同一批原子组件,不 fork 页面。
- 语义 token 零硬编码;文案全部走 l10n;套餐数字对齐 §7;无支付表单/emoji 国旗。
- CI 红线词扫描扩展到 client/lib;新增 Flutter analyze+test CI 任务。
- 测试:连接状态机/额度/节点单测、连接键三态与卡片组件测试、
golden(连接键三态/推荐卡/额度卡 × 明暗)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-13 14:32:36 +08:00
maestro
3be8b2c9bd
maestro(tsk_d8KmVEzJTCjl): app/kernel 构建管线(AAR/XCFramework/桌面二进制)
2026-06-13 14:31:14 +08:00
wangjia
618332906c
feat(kernel): 搭建 app/kernel/ 构建管线 (AAR/XCFramework/桌面二进制)
...
新增 app/kernel/ 目录,包含四个文件:
- VERSION: 固定三个锚点 — sing-box v1.13.12、Go 1.24.3、
gomobile v0.0.0-20240604150348-70c4c5da7d30;禁止脚本内硬编码
- build-android.sh: gomobile bind → dist/android/libbox.aar
targets: arm64-v8a / armeabi-v7a / x86_64,-androidapi 21
build tags: with_quic,with_utls,with_clash_api,with_gvisor(对齐 SFA)
裁剪注释保留,幂等+--force 支持
- build-ios.sh: gomobile bind → dist/ios/Libbox.xcframework
targets: ios + iossimulator(device+sim 双 slice)
build tags 对齐 SFI;含 iOS NE 内存上限敏感说明(裁 grpc/gvisor 指引)
- fetch-desktop-bin.sh: 从 GitHub Release 下载桌面二进制
支持 linux/darwin/windows × amd64/arm64
从官方 checksums.txt 做 SHA256 校验;Windows 额外取 wintun.dll
幂等:产物+校验文件均存在则跳过
dist/ 和 .build/ 加入 gitignore;所有脚本通过 shellcheck -S warning
任务: tsk_d8KmVEzJTCjl
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 14:30:56 +08:00
wangjia
4df8dc6f87
feat(web): 官网 ui_kits/website → Astro 纯静态 SSG 迁移 (tsk_acMYQ-Z-EIF_)
...
新建 web/website/(Astro 零 SSR):
- 组件迁移:交互块保留 .jsx 经 @astrojs/react(Header/AnnouncementBar/SignupForm/PricingPlans),纯展示块转 .astro 去运行时 JS;像素以原型为基准。
- 令牌同源:build-tokens.mjs 从 design/colors_and_type.css 生成 tokens.gen.css,仅剔除第三方 Google Fonts @import,数值不改。
- 字体自托管:@fontsource(Sora/Manrope/Noto Sans SC/JetBrains Mono),无第三方 CDN。
- 图标:Lucide 构建期内联 SVG(替代 unpkg CDN),零运行时、零 CDN。
- i18n:/(zh)与 /en/(en)双路由单显,语言切换组件;文案沿用 ui_kits 脱敏文案。
- 安全:public/_headers 严格 CSP(全 self + 自托管资源 + 内联片段 sha256,无 unsafe-inline)+ HSTS;无支付表单。
- CI:.gitea/workflows/website.yml 构建(红线扫描+lint+CSP 哈希)→ 同时发布 Cloudflare Pages 主站与镜像。
- 灾备:README 写明干净环境 npm ci && npm run build 可直接部署到任意静态托管;dist 指纹确定性,主站镜像一致。
测试:npm run lint(0 error)/ npm test(build+CSP 哈希注入+红线扫描 0 命中)/ 两次干净构建 dist 指纹一致。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-13 14:26:40 +08:00
wangjia
3d5bac66b4
feat(provision): 弹性节点基建 Terraform + 一键更换 (tsk_6u0FxmbC7Yeq)
...
IaC 面 (infra/) 与控制面 (server/internal/provision) 双产出,落地 doc/04 §4
「节点是牲口」弹性拓扑与 make-before-break 一键更换。
server/internal/provision:
- CloudAdapter 适配层 + Registry(首发 vultr 消耗品池 / hetzner 精品池各一);
厂商凭证仅从 PROVISION_<VENDOR>_* env 注入,不入库不入 git。
- ProvisionService:CreateNode(幂等键重放不重复开机)、DestroyNode(幂等)、
RotateIP(换 IP 不换机 + version bump)、ListProviders。
- Replace 一键更换:先建后拆,新机 up 先于旧机 draining(容量不下降),
replacement_uuid 幂等键 + replacements 表分步记录,崩溃可续跑不重复。
- RotatePool:池内滚动轮换,并发度 1–2。
- cmd/nodectl CLI:create/destroy/rotate-ip/replace/rotate-pool/providers。
- 单测(mock 厂商 API + 内存 Store):幂等重放、make-before-break 时序断言、
开机失败/探活超时→destroyed+失败计数+告警钩子、崩溃续跑、RotatePool。
infra/:
- terraform/:探针机 + 控制面基线模块化(probe / control-plane)+ README,
低频基线进 state,节点不进 Terraform。
- cloud-init/node.yaml.tmpl:节点引导模板(注入一次性 bootstrap token,task #5)。
- identity-isolation.md:身份隔离登记表(doc/06 §2 红线,无任何凭证)。
migrations/000008:nodes 增 provider_instance_id/elastic_ip_id、node_events
增 ip_rotated、provision_idempotency / replacements 表(附加式,不动现网)。
红线:仅面向新厂商池,绝不纳管现网生产 EC2(deploy/ marzban)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-13 14:23:39 +08:00
wangjia
7d89ec9d91
feat(infra/domains): 域名池 + CDN 前置 + 签名端点分发 (tsk_NU9JuUweHWMt)
...
- domains.md: 四组域名隔离登记 + 冷备池 ≥5 + 启用流程(不含身份信息)
- cdn/terraform: Cloudflare 配置即代码(WAF/bot/速率限制/代理DNS/回源鉴权注入)+ 30min 重放 Runbook
- server/internal/originauth: 回源鉴权中间件,非 CDN 网段或鉴权头不符一律 403,支持双值轮换
- tools/endpoint-signer: 离线 Ed25519 签名 CLI(端点 + 公告文档,单调版本防回滚,key_id 双公钥轮换)
- tools/publish-mirrors: ≥3 镜像发布 + hash 一致性校验 + 故障转移取回
- CLIENT-CONTRACT.md: schema/验签/防回滚/合并/兜底链/channel 客户端契约
- 出站独立出口要求写入部署文档;私钥/token/身份信息一律不入库
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-13 14:21:55 +08:00
wangjia
bd8f974a25
feat(M6): 控制面联调 — mock connect server + 客户端 ConnectApi 透传
...
tsk_nuoKSM4Vt-zK
## 服务端(server/cmd/mockserver)
- `main.go`(79 行):独立 mock HTTP server,实现 POST /v1/nodes/:id/connect
- 路径/请求体/响应体字段名与 §3.1 契约逐字一致
- Bearer token 鉴权(-token 参数,空值跳过,不入库)
- 缺少 device_id → 400;未授权 → 401;非 POST → 405
- 返回完整 sing-box config JSON(tun + REALITY/Hy2 outbound + urltest + route/dns)
- 注:mock 联调;待 #5/#6 真实 connect 接口就绪后替换
- `main_test.go`:6 项单测,覆盖 §3.1 结构校验、auth、method、path
## 客户端(client/)
- `lib/services/connect_api.dart`:ConnectApi 类
- fetchConfig(nodeId, deviceId) → 原始响应体字符串(不做任何修改)
- 错误路径:HTTP 非 200 / 超时 / 非法 JSON → ConnectApiException(含双语 message)
- `lib/services/vpn_bridge.dart`:VpnBridge stub(M6 联调,libbox 绑定待 11C)
- start(configJson) 原样存储,不修改;stop() 清空
- `lib/widgets/home_shell.dart`:_toggle/_pick 替换为真实 API 流程
- ConnectApi.fetchConfig → VpnBridge.start(透传,无中间变换)
- 错误路径:ConnectApiException → SnackBar,status 回 off,无残留半开隧道
- API URL/token/deviceId 由 --dart-define 注入(不入库)
- `lib/widgets/server_tile.dart`:ServerInfo 新增 nodeId 字段
- `pubspec.yaml`:新增 http: ^1.2.1 依赖
- `test/connect_passthrough_test.dart`:4 项透传断言单测
- 核心:fetchConfig 返回字符串 === VpnBridge.start 接收字符串(逐字节相等)
- 空格/格式原样保留(不经 jsonEncode 重序列化)
- HTTP 错误、非法 JSON 错误路径覆盖
## 运行方式(M6 联调)
```
# 启动 mock server(无 auth)
go run ./server/cmd/mockserver -addr :8081
# 启动客户端(指向 mock server)
flutter run \
--dart-define=PANGOLIN_API_URL=http://localhost:8081 \
--dart-define=PANGOLIN_API_TOKEN=dev-mock-token \
--dart-define=PANGOLIN_DEVICE_ID=demo-device-001
```
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 14:16:29 +08:00
wangjia
f03d2dc8a6
feat(agent): node agent — enroll/mTLS, heartbeat, command stream, sing-box 用户表 (tsk__R8M4jEw43JR)
...
与控制面同仓同 go.mod,新增节点 agent 实现:
- proto/agent/v1/agent.proto + internal/pb/agentv1:冻结的控制面↔agent gRPC 契约
(Enroll/Register/Heartbeat/Subscribe/Ack/ReportUsage)。仓库尚无 protoc 流水线,
暂以手写 Go 类型 + JSON gRPC codec 实现,与 proto 1:1 对应,待 protoc 接入即可替换。
- internal/agentd:
- enroll.go:首启生成 EC 密钥+CSR,持 bootstrap token 调 Enroll 换 90d 节点证书
(CN=node_uuid),落 /etc/pangolin-agent/,此后 mTLS。
- conn.go(agent.go)+creds.go:mTLS 主动拨号 + 指数退避重连;重连携带 last_command_id;
Register 取 ConfigSnapshot 全量配置覆盖本地。
- heartbeat.go:30s 上报 peer/带宽/CPU + config_version;need_full_resync→全量同步。
- command.go:消费 Subscribe,Upsert/Revoke/Rotate/ApplyConfig/Lifecycle 幂等处理后
Ack(at-least-once,按 command_id 去重)。
- singbox.go+render.go:内存用户表 + 落盘 state.json(仅 dp_uuid+expires_at);任何变更
渲染完整 sing-box 配置(REALITY users[uuid,flow] + Hy2 users[派生口令])→ 500ms 去抖
合并 → systemd 重启。
- ttl.go:凭证 TTL 定时移除并上报。
- usage.go:按 dp_uuid 聚合上报,绝无 user_id/email/目的地址。
- derive.go:Hy2 口令 = HMAC-SHA256(key, dp_uuid),与控制面同源派生。
- cmd/agent:入口(flag/env 配置)。
- infra/cloud-init/{node.yaml.tmpl,install-node.sh,README.md}:一段式安装,下载锁定版本
二进制并校验 SHA-256,systemd 拉管,首启即 Enroll/Register。shellcheck -S warning 通过。
测试(bufconn mock 控制面,无需 docker):Enroll→Register→Heartbeat 全流转;Upsert/Revoke
渲染正确;Rotate 宽限期新旧并存到点移除;TTL 自动移除并上报;断流重连 last_command_id
续发不丢不重;need_full_resync 触发重注册;state.json 恢复;去抖合并;扫描确认无身份字段。
go test -race ./internal/agentd/... ./internal/pb/... 通过;go vet ./... 通过。
落实 doc/04 §2 节点无状态化与 doc/06 §3 数据面红线(节点仅见 dp_uuid)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-13 12:26:58 +08:00
wangjia
a5e25b444f
feat(auth): 验证码/注册/登录/JWT 鉴权模块 (tsk_2PFfyviECIXh)
...
实现 server/internal/auth 控制面鉴权底座,对应 doc/02 §4.1、doc/06 §3:
- POST /v1/auth/code:IP+邮箱双维度 Redis 滑窗限频(同邮箱 1/min、同 IP 10/h)
+ 一次性邮箱域黑名单(embed 词表)→ 6 位数字码写 auth:code:{email} TTL 10min
→ 异步发信(SMTP / 开发态 log mailer)。
- POST /v1/auth/register:验码(一次性,删 key;超次数烧码)→ 单事务建号
(uuid + dp_uuid 应用层生成、argon2id)+ 7 天 PRO 试用(source='trial')→ 签发 JWT。
- POST /v1/auth/login:argon2id 校验(失败恒定时、未知用户走 dummy hash);
失败计数限流 rl:login:{email} + 锁定;banned 拒绝。
- POST /v1/auth/refresh:RS256,access 15min + refresh 30d 落 Redis 白名单
jwt:refresh:{jti},旋转时删旧写新;JWT 头带 kid,验证端接受新旧公钥支持轮换。
- RequireAuth 中间件:解析 Bearer access,注入 user id(复用 codes.CtxKeyUserID
避免循环依赖)+ uuid + claims 到 context。
- 错误体统一走 internal/apierr 的 {code, message_zh, message_en},文案双语脱敏。
- 文件拆分:handler/service/password/token/ratelimit/emailcheck/store/mailer/
middleware/errors/keyloader;config 增加可选 JWT PEM 路径/kid 加载。
测试:password/token/ratelimit/emailcheck/service/handler/middleware 单测
(miniredis,含注册全流程、重复邮箱 409、验证码错误/过期/复用/烧码、限频 429
带 Retry-After、登录失败锁定、banned 拒绝、refresh 旋转后旧 token 失效),
go test -race 通过;集成测试 integration_test.go(testcontainers MySQL8+Redis,
register→login→refresh→受保护接口全链路,构建 tag integration)与 codes 模块同构。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-13 12:19:10 +08:00
wangjia
ece8ea3b57
feat(usage): usage_daily 聚合 + 广告解锁 + free 额度校验 (tsk_1taxhtV2k3RP)
...
server/internal/usage 模块实现(仅字节/分钟,无目的地,符合无日志口径):
- aggregator.go:实现 #5 的 ReportUsage 回调接口(UsageReporter),
dp_uuid→user_id 走内存+Redis 短缓存,按上报批次 id Redis 去重防重放,
按 At 的 UTC 日期 INSERT ... ON DUPLICATE KEY UPDATE 累加,并发安全。
- store.go:usage_daily 累加/区间查询/当日查询、MarkAdUnlocked(事务+FOR
UPDATE 幂等)、EffectivePlan(活跃订阅取最高 tier,无则回落 free)。
- ads.go:AdVerifier 接口 + AdMob SSV 实现(拉取并缓存 Google ECDSA 公钥,
ECDSA-SHA256 验签 + custom_data/ad_unit 匹配),Unity 留接口占位。
- service.go:UsageCurve(空日补零、仅当前用户)、TodaySummary(/v1/me)、
UnlockAd(验签→ad_token 一次性 nonce 去重→写 ad_unlocked_at,当日二次幂等)。
- quota.go:CheckFreeConnect 供 #5 connect 使用:ad_gate=true 校验当日
ad_unlocked_at + minutes_used<daily_minutes(free=10),返回剩余分钟;
pro/team 不受 ad_gate 限制(返回 Unlimited);带双语 code 错误。
- handler.go:GET /v1/usage?days=7、POST /v1/ads/unlock(204)。
- apierr:新增 AD_NOT_UNLOCKED/QUOTA_EXHAUSTED/AD_VERIFY_FAILED/AD_TOKEN_REPLAY。
- openapi:/ads/unlock 补 409 Conflict(重放)。
测试:ads_test.go 单测覆盖 AdMob 验签全链路(合法/伪造/custom_data 不符/
ad_unit 不允许/畸形 token)+ Unity 未实现;usage_integration_test.go
(testcontainers, build tag integration) 覆盖并发多节点累加、批次重放去重、
跨 UTC 日界分桶、未知 dp_uuid 丢弃、free 额度四态、ads 解锁伪造/重放/幂等、
曲线补零/隔离、/me 摘要。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-13 12:16:50 +08:00
wangjia
6e2657049e
feat(proto): add agent.proto contract + buf toolchain (tsk_V9IrIk2g5Q78)
...
Introduces the frozen control-plane ↔ agent gRPC contract and the full
buf v2 code-generation pipeline that task #6/#14/#15 depend on.
Changes:
- server/proto/pangolin/agent/v1/agent.proto: AgentService with 6 RPCs
(Enroll, Register, Heartbeat, Subscribe, Ack, ReportUsage) and all
supporting message types (Command + 5 payload variants, ConfigSnapshot,
CredentialEntry, NodeLoad, UsageItem). Red-line comments prohibit
user_id/email and destination-address fields per privacy policy.
- server/buf.yaml: v2 format, STANDARD lint + FILE breaking rules.
- server/buf.gen.yaml: remote protoc-gen-go v1.36.4 + protoc-gen-go-grpc
v1.5.1 plugins, out=internal/pb, paths=source_relative.
- server/internal/pb/pangolin/agent/v1/: generated agent.pb.go +
agent_grpc.pb.go committed to repo (CI diff-clean verified).
- server/Makefile: proto / proto-lint / proto-breaking targets added.
- server/tools.go: buf v1.70.0, protoc-gen-go, protoc-gen-go-grpc pinned.
- server/go.mod / go.sum: buf and grpc tool deps added via go mod tidy.
Acceptance verified:
buf lint → zero warnings
buf generate × 2 → git diff clean (idempotent)
go build ./... → passes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 12:13:43 +08:00
wangjia
87ccaecddf
feat(server/devices): 设备管理 + 订阅校验中间件 (tsk_x7wrlA87orsY)
...
实现 server/internal/devices 模块:
- handler.go: GET /v1/me/devices 列表、DELETE /v1/me/devices/{id} 移除
(chi 路由,挂在 /v1/me 下;JWT 中间件之后)。
- service.go: ListDevices / RegisterIfAbsent(隐式登记,按 plan.max_devices
校验,超限返回双语 DEVICE_LIMIT_EXCEEDED 含上限数字)/ DeleteDevice(事务硬删
+ audit_log → 调 CredentialRevoker.RevokeForUser 按用户回收凭证,dp_uuid 模型)
/ SubscriptionSummary / ResolvePlan / 纯函数 resolveEffectivePlan。
- middleware.go: 订阅校验中间件,解析最高档未过期订阅注入 context;
helpers PlanFromCtx / CheckDeviceQuota / RequirePaidTier;预留 60s Redis 缓存开关。
- store.go: devices/users/subscriptions/plans/audit_log 数据访问,按用户行锁串行化登记。
- context.go: user_id / plan 的 context key 与 helper。
- CredentialRevoker 接口(消费侧定义,避免与 nodes 循环依赖)+ NoopRevoker,
形状对齐 #5 的 Hub.Push(RevokeCredential),#5 落地前注入 no-op。
测试:service_test.go 15 个单测(trial→pro、过期回落 free、banned 拒绝、
最高档/同档最晚到期、UTC 严格边界、设备配额、双语限额、平台/名称归一化)全过;
devices_integration_test.go(testcontainers,build tag integration)覆盖
注册→connect 隐式登记→list→delete 全链路 + 回收断言 + 403/404。
apierr 增加 Unauthorized/Forbidden/NotFound/AccountBanned;
OpenAPI SubscriptionInfo.source 枚举补 free。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-13 12:13:09 +08:00
wangjia
4992d916d0
feat(bridge): 桥接 API 面 Channel 契约 + 三端原生骨架 [tsk_iP5-_Ztq5THx]
...
MethodChannel pangolin/vpn (start/stop/getStatus/selectOutbound/
getActiveOutbound/setKillSwitch) + 双 EventChannel (status/stats)
契约定义在 lib/bridge/vpn_bridge.dart 头部注释冻结。
- Dart: VpnBridge 抽象接口 + VpnNativeBridge 原生实现 + VpnBridgeMock
假内核(含 connectDelay/statsInterval 可配参数)
- Dart 桌面: kernel_process.dart KernelProcess/ClashApiClient 接口骨架
- Android: PangolinVpnService (VpnService 骨架 + 前台通知占位) +
VpnEventBus 解耦总线 + MainActivity MethodChannel/EventChannel 注册
- iOS: PacketTunnelProvider (NEPacketTunnelProvider 骨架) + VpnManager
(NETunnelProviderManager + NEVPNStatus 订阅) + AppDelegate 通道注册
+ Xcode project.pbxproj 添加 PacketTunnel extension target
- Widget: VpnStatus enum 迁移至 bridge/vpn_bridge.dart (+error 态),
connect_button/home_shell 更新穷举匹配
- Test: test/bridge/vpn_bridge_mock_test.dart 覆盖 start→connecting→on
→stop→off 序列、stats 周期推帧、selectOutbound 重连序列、schema 回测
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 12:06:03 +08:00
wangjia
b64c002a33
feat(tsk_GXDoc3Cs07Rn): apierr + idgen + CONVENTIONS.md
...
apierr:
- Add New() constructor, StatusFor() HTTP-status mapping
- Add ErrUnauthorized, ErrForbidden, ErrNotFound, ErrConflict predefined errors
- Add chi-compatible Middleware for panic(*Error) → JSON recovery
- Add apierr_test.go (8 tests; covers New, StatusFor, WriteJSON, Middleware)
idgen:
- Implement idgen.go: New()/NewString() (UUID v7 via google/uuid v1.6.0)
- Implement GenerateCode/CanonicalizeCode/HashCode (Crockford Base32 moved from codes)
- Add idgen_test.go (12 tests; UUID v7 ordering/uniqueness + Crockford format/normalization/check)
codes:
- Refactor generator.go to delegate GenerateCode/Canonicalize/Hash to idgen
- All existing codes generator tests continue to pass unchanged
server:
- Add CONVENTIONS.md covering package structure, error handling, ID generation,
database conventions, handler templates, auth context, testing, and logging rules
- Move google/uuid from indirect to direct dependency in go.mod
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 11:58:38 +08:00
wangjia
fb3389a94b
feat(15A): probe ingest endpoint + Redis store (tsk_rBPr0Xuy10bz)
...
Add `server/internal/scheduler/probe/` package:
* types.go – frozen ReportRequest/NodeReport/L1-L3 schema (15B/15C contract)
* store.go – Redis read/write: probe:{nodeId}:{vantage} TTL 30min,
probe:hb:{probeId} TTL 15min; missing key = no data, not failure;
SnapshotsByNode + AliveProbes read interfaces for 15D
* ingest.go – POST /probe/report handler: per-probe HMAC-SHA256 auth
(X-Probe-Id / X-Probe-Ts / X-Probe-Sign), ±300s time window,
constant-time comparison, idempotent replay via Redis SetNX
Wire route in cmd/server/main.go (opt-in via PROBE_SECRETS env var).
18/18 tests pass (go test ./internal/scheduler/probe/... -count=1).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 11:56:34 +08:00
wangjia
787151245e
merge: maestro/tsk_tFMU7-hKzfOf [tsk_tFMU7-hKzfOf] codes 激活码模块
...
解决与 1A 骨架的冲突:module 统一为 github.com/wangjia/pangolin/server
(codes 分支原用 pangolin/server,7 个源文件 import 已改写);
go.mod require 并集(redis 取 9.20.1);Makefile 以骨架为基底并入
build-codegen/test-unit/test-integration target。
go build/vet 通过,codes 单测通过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-13 03:03:11 +08:00
wangjia
a139ef937a
merge: maestro/tsk_iRa5iBC4v7Tu [tsk_iRa5iBC4v7Tu] Flutter 客户端工程搭建(演示态跑通)
2026-06-13 03:01:31 +08:00
wangjia
8c516d4f2a
merge: maestro/tsk_FUQws_DMIcXa [tsk_FUQws_DMIcXa] mTLS/CA + 引导 token 框架
...
解决与 Go 骨架(1A)的 add/add 冲突:go.mod 统一 module 路径为
github.com/wangjia/pangolin/server,require 取并集;setup.sh 合并两边意图。
go build ./... 与 go test ./internal/mtls/... 通过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-13 03:01:31 +08:00
wangjia
37b18d4293
merge: maestro/tsk_9uMrd9kUpmVA [tsk_9uMrd9kUpmVA] 数据面定稿 + 修订 ARCHITECTURE.md connect 契约
2026-06-13 02:57:25 +08:00
wangjia
2112230d57
merge: maestro/tsk_A1HkEdRbXLGY [tsk_A1HkEdRbXLGY] openapi.yaml 契约(13 操作 + 全量 schema)
2026-06-13 02:57:25 +08:00
wangjia
dc7a4f12d7
merge: maestro/tsk_P5b5nIrEsfrV [tsk_P5b5nIrEsfrV] CI 流水线
2026-06-13 02:57:25 +08:00
wangjia
3f0f82eca0
merge: maestro/tsk_L2k2VrEujof8 [tsk_L2k2VrEujof8] Go 模块骨架 + 工具链(go.mod/tools.go/Makefile)
2026-06-13 02:57:25 +08:00
wangjia
18869bc1fa
merge: maestro/tsk_paiqu21eZIuw [tsk_paiqu21eZIuw] MySQL migration 7 文件 + plans seed
2026-06-13 02:57:25 +08:00
wangjia
adfd8ce3f2
merge: maestro/tsk_bYv-CoKSwgAC [tsk_bYv-CoKSwgAC] 备份与灾备演练
2026-06-13 02:57:25 +08:00
wangjia
afcd7b325c
feat(codes): implement activation-code module (tsk_tFMU7-hKzfOf)
...
Implements server/internal/codes/ with all required functionality:
## Generator (generator.go)
- Crockford Base32 16-char codes (15 data + 1 Crockford mod-37 check char)
- crypto/rand for unbiased random generation with rejection sampling
- Canonicalize(): I/L→1, O→0 folding, hyphen/space stripping
- Hash(): SHA-256 of canonical plaintext (only value stored in DB)
- Algorithm hard-coded; check char detects all single-char substitution errors
## Store (store.go)
- MySQL-backed via database/sql
- CreateBatch / CreateCode with ErrDuplicate on UNIQUE conflict
- FindCodeByHashForUpdate: SELECT … FOR UPDATE for row-level concurrency control
- MarkRedeemed, ExtendSubscription, CreateSubscription, GetActiveSubscriptions
- WriteAuditLog, CodeExistsByHash
- BeginTx at READ COMMITTED (FOR UPDATE provides row exclusivity)
## Service (service.go)
- Redeem(): 9-step flow with full idempotency and concurrency safety
- isLocked / recordFail / clearFail via Redis key redeem:fail:{user_id}
- SELECT … FOR UPDATE → single winner under N-concurrent redemptions
- Same-plan: extends existing subscription (max(expires_at,now)+days)
- Cross-plan: creates new subscription (max(now,latest)+days)
- Idempotent: same user re-submits → 200 without re-applying
- 5 failures → ACCOUNT_LOCKED for 1 hour (sliding window via Redis)
- CreateBatch(): generates N codes, stores hashes, returns plaintext once
- Automatic retry on hash collision (birthday probability ≈10⁻⁸)
## Webhook (webhook.go)
- POST /webhook/store/codes — outside /v1, no JWT required
- HMAC-SHA256 with hmac.Equal constant-time comparison
- ±5 min timestamp window
- Redis SetNX nonce deduplication (15-min TTL)
- Idempotent: duplicate nonce → 200; duplicate code_hash → 200
## HTTP Handler (handler.go)
- POST /v1/redeem endpoint wired to Service.Redeem
- Reads userID from context key (set by JWT middleware from auth module)
- Bilingual error responses {code, message_zh, message_en}
## Export (export.go)
- ExportCSV(): streams plaintext codes to io.Writer as CSV
- Plaintext NEVER stored in DB; only SHA-256 hash persists
## CLI (cmd/codegen/main.go)
- codegen -plan -days -count -channel -note -dsn [-out]
- Transition tool until admin panel (#8 ) is ready
- Outputs CSV to stdout or file; warns operator about plaintext sensitivity
## Infrastructure (skeleton)
- internal/config/config.go: env-var configuration
- internal/db/db.go: MySQL connection pool helper
- internal/redisutil/redis.go: Redis client constructor
- internal/apierr/apierr.go: bilingual error types
- migrations/001_init.sql: DDL for codes module tables
- go.mod with all dependencies
## Tests
- generator_test.go (unit, no deps):
- Format, uniqueness (10k codes, zero collisions), normalization,
check-char detection of all single-char errors, hash consistency
- webhook_test.go (unit, no deps):
- Signature rejection, missing signature, stale/future timestamp,
missing nonce, constant-time HMAC comparison
- service_test.go (//go:build integration, testcontainers):
- N=20 concurrent redeemers → exactly 1 winner
- Idempotent redeem returns success for same user
- Other-user redemption → CODE_REDEEMED + failure counted
- 5 failures → ACCOUNT_LOCKED on 6th attempt
- Same-plan extension: expires_at precision assertion
- Cross-plan creation: new subscription row
- Audit log written on every successful redemption
- CSV export: no plaintext in DB, hash present
- Webhook nonce replay: idempotent 200
- Webhook same-hash: idempotent 200, single DB row
Run unit tests: make test
Run integration tests: make test-integration (requires Docker)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 02:16:16 +08:00
wangjia
526e7f2f33
feat(tsk_iRa5iBC4v7Tu): Flutter 客户端工程搭建(演示态)
...
基于 design/flutter/ 起步包在 client/ 下建立 Flutter 工程:
- pubspec.yaml:flutter_svg / lucide_icons / google_fonts 依赖;
SVG 资产注册;字体由 google_fonts 运行时加载,无需本地 ttf
- lib/pangolin_theme.dart:完整设计令牌(色阶 / 间距 / 圆角 /
动效 / 文字阶),PangolinText 改用 GoogleFonts getter,
PangolinTheme.light / .dark 开箱即用
- lib/main.dart:runApp + 明暗主题 + 登录 → 引导 → 主框架 RootFlow
- lib/widgets/:全套组件雏形
pangolin_icons Lucide 图标映射
pangolin_button 胶囊按钮四态
country_code 国家码块 + 信号条
status_pill 色点胶囊
connect_button 核心连接键三态(off/connecting/on + 动画)
server_tile 服务器列表行
plan_card 套餐卡(专业版渐变高亮)
auth_screen 登录 / 注册(邮箱验证码 + 设密码)
onboarding_screen 首次引导 PageView(3 屏可滑动 + 进度点)
home_shell 底部 4 Tab(连接/节点/统计/账户 + 状态机)
account_screens 套餐选择 / 设备管理 / 兑换 / 联系我们
pangolin_logo PangolinMark / BrandLockup / AppIcon(SVG)
- assets/:logo-mark / logo-mark-white / logo-wordmark / app-icon(SVG)
- android/:Kotlin 主 Activity + Manifest + Gradle 配置
- ios/:Runner.xcodeproj + xcscheme + Podfile + AppDelegate + storyboard
运行方式(cd client/):
flutter pub get && flutter run
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 02:15:42 +08:00
wangjia
73fd281bb5
feat(mtls): implement mTLS/CA + bootstrap token framework [tsk_FUQws_DMIcXa]
...
ECDSA P-256 self-signed CA with disk persistence (load-or-generate),
CSR signing (CN=nodeUUID, 90d validity, EKU=ClientAuth), one-time
bootstrap tokens via Redis GETDEL (15min TTL), CRL revocation with
Redis SET + DB interface, gRPC unary+stream interceptors that extract
CN from verified TLS chains (Enroll whitelisted, others require cert),
and NewServerTLSConfig (VerifyClientCertIfGiven + TLS 1.3 + CRL hook).
Frozen API: SignCSR / CAPEM / IssueToken / ConsumeToken / Revoke /
NewServerTLSConfig / UnaryServerInterceptor / NodeUUIDFromContext
Tests cover: CA sign+verify, token one-time guarantee, TTL expiry,
revocation rejection, interceptor whitelist (5 categories).
Redis layer backed by miniredis in tests.
Run setup.sh from server/ to fetch deps and verify tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 01:44:21 +08:00
wangjia
ab2bbaf683
tsk_9uMrd9kUpmVA: 数据面定稿 + 修订 ARCHITECTURE.md connect 契约
...
决策:libbox 统一承载 REALITY/Hy2 outbound(方案 C),弃用 WireGuard peer 注册。
现网 deploy/ 基础设施(singbox Hy2 + xray REALITY)已验证,零改动复用。
变更文件:
- design/server/ARCHITECTURE.md v0.2:
· §0 数据面描述改为 sing-box libbox (REALITY/Hy2)
· §1 拓扑图更新(wireguard+agent → singbox+agent,客户端标注 libbox 内嵌)
· §2 devices 表移除 pubkey;nodes 表改为 reality_public_key/short_id/uuid/hy2_password
· §3 connect 响应由 WireGuard 配置改为完整 sing-box config JSON
· §3.1(新增)connect 契约详细规范:四块 inbounds/outbounds/route/dns、
客户端透传原则、字段与 deploy/ 模板逐字段对照、占位符替换说明
· §4.3 连接流程更新(peer 注册 → config JSON 渲染下发)
· 附录 A(新增)v0.1→v0.2 变更汇总
- doc/plans/11-libbox-bridge.md(新建):
完整决策记录(背景/备选/结论/影响面),任务链 11A→11C→11D/E/F→11H
- doc/plans/client-connect-config.example.json(新建):
最小示例 config,字段与 deploy/ 模板对齐,可用 sing-box check -c 校验
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 01:43:17 +08:00
wangjia
58c6eb721e
feat: 新增 server/api/openapi.yaml — v1 API 完整契约(tsk_A1HkEdRbXLGY)
...
OpenAPI 3.0.3,覆盖 doc/02 §2 全部 15 个操作:
- 认证 4(sendVerificationCode/register/login/refreshToken,security:[])
- 账户 3(getMe/listDevices/deleteDevice)
- 商业 3(redeemCode/adsUnlock/listPlans)
- 节点 3(listNodes 含 304/connectNode/disconnectNode)
- 用量 1(getUsage)、公告 1(listNotices)
全量 schema:Error·TokenPair·Me·UserInfo·SubscriptionInfo·TodayUsageSummary·
Device·Plan·RedeemResult·NodeDirectory·Node·ConnectCredential·
RealityParams·Hysteria2Params·UsagePoint·Notice
安全约定:bearerAuth(JWT RS256) 全局;auth 四操作 security:[];
components/responses 统一 Error 引用;429 TooManyRequests 带 Retry-After;
节点目录不含密钥,密钥仅由 connect 端点下发;对外 ID 全部 uuid 格式。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 01:29:31 +08:00
wangjia
1629385c70
feat(server): Go 模块骨架 + 工具链 [tsk_L2k2VrEujof8]
...
新建 server/ Go 模块:
- go.mod: module github.com/wangjia/pangolin/server, Go 1.22
依赖: go-chi/chi/v5, google/uuid
- tools.go: //go:build tools 锁定 oapi-codegen / golang-migrate 版本
- cmd/server/main.go: chi router + GET /healthz → {"status":"ok"},监听 :8080
- cmd/migrate/main.go: 占位入口(实逻辑归 1e)
- internal/{config,store,apierr,idgen,auth,codes,devices,nodes,usage,admin}/doc.go
各包职责说明
- Makefile: build/test/vet/lint/generate/migrate-up/migrate-down
- .golangci.yml: govet/errcheck/staticcheck/revive/gofmt 基线
- README.md: 定位 + 目录树 + make 入口说明
- setup.sh: 首次 go mod tidy 引导脚本(go.sum 需运行后生成)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 01:28:37 +08:00
wangjia
b4114c93ee
feat(db): add MySQL migrations 000001-000007 covering all 12 tables + seed [tsk_paiqu21eZIuw]
...
- 000001: users + devices(账户 / 设备,含 argon2id pw_hash、dp_uuid)
- 000002: plans + subscriptions(套餐口径来自 design/CLAUDE.md §7)
- 000003: code_batches + codes(SHA-256 code_hash,明文不入库)
- 000004: usage_daily + audit_log(最小数据原则 + JSON meta)
- 000005: providers + nodes(providers 先建,nodes FK 引用)
- 000006: node_events(9 值 ENUM)+ directory_version(CHECK id=1,MySQL ≥ 8.0.16)
- 000007: plans 三行 seed + directory_version(1,1),ON DUPLICATE KEY UPDATE 保可重入
所有表 ENGINE=InnoDB DEFAULT CHARSET=utf8mb4,时间列 DATETIME(6),
主键 BIGINT UNSIGNED AUTO_INCREMENT + 对外 uuid CHAR(36) UNIQUE。
每对 down 文件逆序 DROP(子表先于父表)。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 01:28:01 +08:00
wangjia
c92cd11cc5
feat: CI 流水线 — lint + 单测 + OpenAPI 校验 + 脱敏扫描 + 镜像构建 [tsk_P5b5nIrEsfrV]
...
新增 .gitea/workflows/ci.yml 五个 Job:
1. lint — shellcheck -S warning 扫描全部 deploy/ shell 脚本
2. unit-test — docker-compose config 语法校验 + nginx -t(桩证书)
3. openapi-check — openapi-spec-validator 验证 design/server/openapi.yaml
4. redline-scan — ci/scan-redline.sh 扫描 UI 文案红线词(design/ jsx/dart/html)
5. image-build — docker build pangolin-edge:ci
附带:
- ci/scan-redline.sh:脱敏扫描脚本,过滤注释行与外部渠道 handle
- ci/nginx-test.sh:自签桩证书 + nginx -t,CI 免依赖真实 Let's Encrypt
- design/server/openapi.yaml:依据 ARCHITECTURE.md §3 展开的 OAS 3.0 完整契约
- dparts.jsx / parts.jsx:修复 killSwitchSub EN 文案「the VPN drops」红线词
→ 改为「connection drops」(行为描述,不提产品类别)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 01:19:28 +08:00
wangjia
20dbb5f363
feat(backup): 备份与灾备演练系统 [tsk_bYv-CoKSwgAC]
...
实现 xtrabackup + binlog 增量备份、age 加密异地存储与月度 DR 演练。
## 交付内容
### deploy/backup/ —— 备份容器
- Dockerfile:Ubuntu 22.04 + xtrabackup 8.0 + age v1.2.0 + AWS CLI v2 + pg_client
- entrypoint.sh:初始化 AWS 凭据 / age 公钥,启动 crond
- crontab.txt:全量 02:00 / 增量每 15min / DR 演练每月 1 日
- scripts/lib.sh:日志 / age 加密 / S3 上传 / 连接测试等共用函数
- scripts/backup-full.sh:MySQL xtrabackup 全量 + pg_dump 全量 + SQLite 备份
- scripts/backup-inc.sh:MySQL xtrabackup 增量 + binlog flush & 归档 + pg_dump 快照
- scripts/restore.sh:从 S3 下载、解密、prepare / pg_restore 完整恢复
- scripts/dr-drill.sh:月度演练 —— RPO 验证 / 备份完整性 / MySQL 临时容器恢复 / RTO 度量
- scripts/verify-rpo-rto.sh:实时 RPO/RTO 状态检查(S3 最新备份时间戳)
- backup.env.example:配置模板(MySQL / PG / S3 / age 公钥)
### 修改已有文件
- deploy/docker-compose.yml:新增 pangolin-backup 服务
- deploy/scripts/gen-secrets.sh:幂等生成 age 密钥对 + backup.env 模板
- .gitignore:排除 age 私钥 / backup.env
- deploy/README.md:容器一览 + 备份快速命令
### docs/备份与灾备.md
S3 初始化、IAM 权限设计、age 密钥管理、MySQL 权限、DR 恢复步骤、RTO 测量标准
## 设计要点
- RPO ≤ 15min:每 15min xtrabackup 增量 + binlog flush + pg_dump 快照
- RTO ≤ 4h:月度演练度量,估算恢复时长约 2h
- 独立身份账号:备份 IAM 仅有 s3:PutObject,与生产账号完全隔离
- age 加密:公钥存服务器,私钥离线保存,S3 中无明文
- 无 crontab:cron 在容器内运行,绕过 ec2-user crontab 限制
- 幂等部署:backup.env 存在则跳过,age 密钥对存在则跳过
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-06-13 01:09:18 +08:00
wangjia
a642bf16a2
feat: 同步 design/ 设计系统(含 iPad tablet kit) + 架构任务拆分(todo/)
...
design/ 同步自最新设计导出,新增 ui_kits/tablet/ 平板分栏布局;todo/ 录入 18 个并行实施任务。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-06-11 23:57:57 +08:00
wangjia
3ae96ef229
docs: 新增整体架构设计文档(doc/,HTML 七章)
...
覆盖前端五端/后端/MySQL/弹性节点拓扑/Web安全/安全总纲,遵循 design/ 设计系统视觉呈现。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-06-11 23:57:49 +08:00
wangjia
4bff8593e7
docs: 新增 CLAUDE.md(部署架构与主机约束指引)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-05-30 23:30:08 +08:00
wangjia
73e026a4df
edge 容器自带证书续期(certbot in nginx 镜像)
...
deploy-pangolin / deploy (push) Has been cancelled
- edge 改为自定义镜像:官方 nginx + certbot + dns-cloudflare 插件
- pangolin-entrypoint.sh:后台每12h certbot renew,续期后本容器内 nginx -s reload
- 不再需要宿主 cron / docker socket / 独立 certbot 服务做日常续期
- /etc/letsencrypt 改 rw 挂载,挂入 cloudflare.ini
- compose 去掉 build:(宿主 docker-compose 不支持 compose build),改脚本 docker build
- deploy.sh/cutover.sh: docker build -t pangolin-edge:local 后再 up
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-05-30 23:10:17 +08:00