wangjia
4fb3fe3fee
feat(ci): 服务端 server-v* 编译+release+部署(备份/迁移/回滚,复用 lib-ssh/lib-forgejo)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-06 00:24:18 +08:00
wangjia
91d1355721
fix(ci): 部署脚本 —— 私钥清理 trap 前移 + rsync 空目录守卫 + 主机密钥 accept-new(评审 Critical/Important)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-06 00:17:37 +08:00
wangjia
9bac5c8dcb
feat(ci): 官网 site-v* 构建+部署脚本(compile-site/deploy-site/lib-ssh/workflow)
...
- scripts/ci/compile-site.sh: node:20 容器内构建 web/website(Astro),
SITE_URL 注入 canonical 域名 https://pangolin.yanmeiai.com 。
- scripts/ci/lib-ssh.sh: 新增共享 setup_ssh/teardown_ssh(写临时私钥+
known_hosts),供 site 与后续 server 部署复用;目标写死 IP 103.119.13.48
(runner 无法解析用户本机 ~/.ssh/config 的 pangolin1 别名)。
- scripts/ci/deploy-site.sh: rsync dist/ 到 pangolin1
/var/www/pangolin-site/,root 部署。
- .gitea/workflows/deploy-site.yml: site-v* tag + workflow_dispatch 触发,
concurrency 组 deploy-site 防并发。
- .gitea/workflows/ci.yml: shellcheck 列表纳入三个新脚本。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
2026-07-06 00:10:53 +08:00
wangjia
f0a8c6dd98
ci(checks): 触发路径纳入 scripts/ci(否则 CI 脚本改动不触发 lint)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-06 00:05:26 +08:00
wangjia
0b9f291ff1
ci(checks): shellcheck 覆盖 scripts/ci
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-06 00:03:05 +08:00
wangjia
ff8fc36726
fix(ci): lib-forgejo TLS 校验默认开启,-k 改显式 opt-in(安全)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-05 23:55:56 +08:00
wangjia
b8343a8788
feat(ci): scripts/ci 基座(_env/lib-forgejo/notify)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-05 23:49:59 +08:00
wangjia
6be777dff5
docs(plan): CI/CD 实现计划( #30 ) —— .md 真相源 + HTML 阅读版 + 索引
...
三期 11 任务:Phase1 基座+官网+服务端(无签名) → Phase2 Android → Phase3 macOS/Windows。
服务端部署固化 F3/F4 备份/迁移/回滚;下载链接接 release 资产;密钥对齐 jiu。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-05 23:40:59 +08:00
wangjia
0ab31b4c58
docs(spec): CI/CD 密钥命名对齐 jiu(MACOS_*/APPSTORE_*/ANDROID_*)
...
Apple 那套账户级与 jiu 共用一份(同名);新增 FORGEJO_URL、macOS 两个描述文件
入表。Android keystore 同命名规范但各 app 独立不共享。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-05 23:19:29 +08:00
wangjia
fe9a3a6cdb
docs: CI/CD 方案 HTML 阅读版 + 登记 docs/index.html( #30 )
...
深色家族风 HTML(cicd-design.html),真相源仍是 superpowers/specs 的 .md。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-05 22:06:25 +08:00
wangjia
f30097172a
docs(spec): CI/CD 全流程设计方案( #30 )
...
范围 A~F(官网+服务端+Android/macOS/Windows,排除 iOS/#26/#25);runner 混合
(nas/mac/windows);tag 触发;镜像 jiu 的 scripts/ci+workflows 结构;密钥作用域
(Apple/token 账户级、部署key/keystore 仓库级);服务端部署固化 F3/F4 手动次序带回滚。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-05 22:02:40 +08:00
wangjia
70d3293251
refactor(website): 域名/邮箱/渠道收口到 src/config/site.ts 单一真相源
...
新增 SITE 配置(url/email/telegram/line/store),Footer、Pricing、astro.config
(canonical 域名)均改为引用它;改联系方式/域名只动一处。
astro.config.mjs 直接 import 该 TS(Astro 配置加载支持)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-05 19:23:19 +08:00
wangjia
a454c63c4e
fix(website): 落定真实域名/邮箱/Telegram
...
- canonical 域名 → https://pangolin.yanmeiai.com (astro.config SITE_URL 默认值)
- 联系邮箱 support@/buy@pangolin.vpn → pangolin@yanmeiai.com (可点 mailto)
- Telegram @PangolinVPN_bot → @pangolin_app(可点 https://t.me/pangolin_app )
邮件转发已在 CF 配好:pangolin@yanmeiai.com → chenxin880812@gmail.com (目标已验证)。
未动(待 #24 ):shop 自助商店 URL、LINE handle(TG 群为私有,不放公开页)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-05 19:19:58 +08:00
wangjia
c1c6b7ccf5
fix(website): Docs 卡片暂改不可点(去掉「阅读」死链)
...
4 张文档卡原为 <a class=doc> 无 href + 「阅读」死链;真实文档页未写。
改为信息卡(<div>),保留标题/描述与导航·页脚入口,不误导。
撰写真实文档(快速开始/FAQ/协议安全/隐私政策)并接回链接 → #31。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-05 18:09:07 +08:00
wangjia
75ed293151
fix(website): 移除无效的「登录」按钮(死 span,无链接)
...
Web 用户中心(web/usercenter)尚未部署、无 URL,登录入口暂无处可去;
产品主流程为 App 内登录。先删死按钮避免公开页点了没反应,待 #30 部署
usercenter 后接上登录地址恢复(已挂 #30 子任务)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-05 17:56:49 +08:00
wangjia
95abb74db2
fix(website): 价格按语言分币种 —— 英文版显示美元(P0)
...
PRICES 原为中英共用一份人民币值 → 英文页显示 ¥ 不专业。改为 Record<Lang,...>:
- zh: ¥0 / ¥25(年¥20) / ¥99(年¥79)
- en: $0 / $3.99(年$3.19) / $13.99(年$11.19) 年付沿用 8 折
Pricing.astro 接收 lang 按语言取价;Site.astro 传入 lang。
构建产物核实:EN 页 0 处 ¥、ZH 页 0 处 $;红线扫描 0 命中。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-05 17:43:44 +08:00
wangjia
1c8f6cb56b
fix(website): 去除夸大宣传 + 隐藏占位 Blog 区(官宣页上线前 P0)
...
- 「80+ 全球加速线路」→ 中性「全球加速线路 / Global routes」(hero 徽章/功能卡/Pro 套餐/对比表 4 处);
现实仅 1 个节点(pangolin1),80+ 属夸大宣传(国内 VPN 有法律风险)。
- 功能卡③描述去掉「港日新美欧韩」具体地区罗列 → 「覆盖多地优质线路」。
- 隐藏整个 Blog 区:3 篇均占位假帖,含虚构的「首尔/法兰克福节点」「v2.4 版本」。
移除 <Blog/> + 顶栏导航 Blog 项 + 页脚 Blog 链接;有真实文章后恢复。
未动(待用户输入):英文版价格货币(等美元定价);下载链接归入 #30 CI/CD。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-05 17:35:49 +08:00
wangjia
08fb915b5e
test(devices): #27 同机换账号登录注册 E2E(SQLite,服务层)
...
走 login 完全相同的路径 devices.Service.RegisterIfAbsent{MaxDevices:0}:
同一 device uuid 两账号各得一行、A 重登幂等刷新、共享 uuid 恰 2 行。
补 SQLite(生产同引擎)服务层覆盖,与既有 store 层 TestSQLite_DevicesUserScopedUUID
及 MySQL 集成 TestSameDeviceUUIDTwoAccounts 三层互证 F3 修复。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-05 13:11:25 +08:00
wangjia
eb7c3c1062
feat(agent): WARP 域名分流 —— 命中域名走 Cloudflare 干净出口( #29 )
...
节点 sing-box 渲染新增可选 WARP 分流:节点本地 warp.json(默认
<StateDir>/warp.json)配 WARP 凭证 + 域名清单 → 渲染时注入一个 userspace
WireGuard(WARP)endpoint + route(sniff 取 SNI/Host → domain_suffix 命中走
warp,其余 final=direct)。sing-box 1.11+ endpoints 语法,system=false 用户态
不依赖内核 wg 模块。
- 运营改域名只需编辑 warp.json + 重启 agent(sing-box 无热重载),即「配置的方式」。
- warp.json 不存在/enabled=false/域名空/凭证缺/坏 JSON → 一律按未启用,配置与旧
节点逐字节一致,坏配置绝不产出无法启动的 sing-box config(渲染读失败仅记日志)。
- WARP 凭证节点私有(wgcf 注册免费匿名账号),不入 git、不经控制面。
- 测试:注入 endpoint+route/无配置无 route/禁用或残缺不注入/坏 JSON 优雅退化。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 13:40:00 +08:00
wangjia
f035552ff5
fix: disconnect 吊销每设备凭证 + 客户端断开时真正调用(F4,#28)
...
审查 F4 两层问题:①DisconnectNode 只撤账户级 ent.DpUUID,而 connect 下发的是
每设备 devDp——吊销从未对准目标;②客户端从未调用过 disconnect 端点(只有
fetchConfig),端点是死代码,凭证一律活到 TTL(付费 24h)。
- server:disconnect 收 optional body {device_id},吊销该设备 dp_uuid(优先)+
账户级遗留兜底;旧客户端无 body 走兜底,行为不回归。nil hub 守卫(测试友好,
与 ListNodes 一致)。
- client:ConnectApi.disconnect(best-effort,5s 超时吞错);_disconnect 加
revokeCredential 参数,仅在「不会紧接重连同节点」的路径置 true(用户主动断开/
额度耗尽/登出)——看门狗断开→重连若也吊销,revoke 可能晚于新 connect 推送、
误杀新会话。
- test:httpapi disconnect 三态(带 device_id 双吊销/无 body 仅兜底/设备已移除
不炸);client 功能套件 182 过(golden 为已知 macOS 本地漂移,不相关)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 13:29:01 +08:00
wangjia
63d1baeb01
fix(server): devices 唯一键改 (user_id,uuid) —— 同机换账号不再 403 死结(F3,#27)
...
device_id 按安装持久、跨账号复用;旧全局 UNIQUE(uuid) 使同机第二账号注册永远
403 → ConnectNode 判 DEVICE_NOT_REGISTERED,提示的「重新登录」无法自救。
- migration 21(sqlite/mysql):UNIQUE(uuid)→UNIQUE(user_id,uuid);platform 放行
linux(normalizePlatform 早已接受,旧 CHECK/ENUM 会拒)。SQLite 表重建用
rename→重建→复制→drop 次序,单事务内不触发 sessions 的级联清空(FK ON)。
- 查找全部收口为按 (user,uuid) 作用域(重复 uuid 跨用户后全局查询歧义):
findDeviceByUserUUIDTx / FindByUserUUID;Register 删跨用户 Forbidden 分支;
Delete/ForceLogout/Rename 对他人设备返回 404(不可见);SessionActive 删
「非本人 fail-safe」分支,dev==nil→false 语义不变。
- 测试:SQLite 真迁移库 F3 回归(两账号同 uuid 各自成行/同用户重复拒/linux 入库/
sessions 重建后级联仍成立)+ MySQL 集成测试 schema 同步与双账号用例。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 13:16:40 +08:00
wangjia
bbe149fd41
docs: 全栈设计审查 2026-07(前端/后端/数据库,13 项发现分级)
...
P0:控制面明文 HTTP、SQLite 零备份。P1:同机换账号设备注册 403 死结(migration16
注释里推迟的 UNIQUE(user_id,uuid) 未落地)、disconnect 撤账户级凭证而 connect 发
每设备凭证、agent 用量 Reset 后上报失败即丢(at-most-once)、Redis 白名单单点全员
掉线、argon2id 64MiB 并发 OOM。P2:sessions/audit 无留存、免费额度 UTC 日界、
三时钟口径、hub 内存态、单实例端口无握手、ReportUsage 无事务。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 12:52:08 +08:00
wangjia
14bcdc2e79
docs(contact): 落定渠道二级页设计 —— 统一单表 + LINE 灰置
...
拍板:不展示成员数;群组=「加入」其余=「打开」;独立 GET /v1/contact;
全渠道(Telegram/LINE/邮箱/发卡)统一一张 contact_link 表,靠 url 协议区分
(mailto:/https:/tg://),邮箱=kind=link+mailto。L1 平台图标/名/强调色由前端
platform 注册表内置(唯一不入库的皮)。LINE 也做二级、同构,但先灰置「即将开放」
(registry comingSoon=[line],配好去灰即用)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 12:30:24 +08:00
wangjia
91a890428f
docs: 联系我们渠道二级页交互设计(Telegram/LINE 多频道群组,DB 配置)
...
点渠道进二级页,按 kind 分组(频道/群组/Bot)列多条链接,内容 DB 配置。
含可点原型(App 真实暗色 token)+ 单表 contact_link 数据模型 + GET /v1/contact
接口 + 交互规则(平台 >1 条链接才进二级,否则点击直达)。#24,待统一后开发。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 12:23:02 +08:00
wangjia
2c804d4ebf
fix(scripts): local_test.sh 对齐当前 macOS 架构(sysext 标识符名 + 静态 libbox)
...
发版脚本停留在旧「嵌入 Libbox.framework」架构,与现工程漂移两处、导致 sign 失败:
- sysext bundle 名 = 标识符 com.pangolin.pangolin.PacketTunnel.systemextension
(PRODUCT_NAME,见 CLAUDE.md),非短名 PacketTunnel.systemextension。
- libbox 现「只 Link 不 Embed」静态进 sysext 二进制,sysext 内已无独立
Libbox.framework;删掉对不存在 framework 的签名,签 sysext bundle 即覆盖。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 11:11:05 +08:00
wangjia
8937044f55
fix(client/macos): 最小化托盘后点 Dock 图标唤回窗口(1.0.48+49)
...
windowManager.hide() 走 orderOut: 后主窗口不可见,点 Dock 图标系统只发
applicationShouldHandleReopen(hasVisibleWindows=false),AppDelegate 未接管 →
点了没反应。AppDelegate 实现该回调,无可见窗口时 makeKeyAndOrderFront +
activate 唤回 mainFlutterWindow。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 10:50:40 +08:00
wangjia
76126f7a0c
chore: bump 1.0.47+48 (弱网自动重连#18 + 用量去计)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 09:58:27 +08:00
wangjia
be27529054
feat(client): 弱网抖动先自动重连当前节点,不再动不动报「节点异常」( #18 )
...
弱网下(实测 RTT 2.2s/丢包)urltest 瞬断或内核 off,看门狗此前一判死就断开+要用户手动
重连(nodeUnhealthyError),体验差。改为:无备用节点时先自动重连**当前节点**(不换节点——
弱网是本地网络问题,换节点无益还会来回横跳),连续失败超过 _kMaxAutoReconnect(3) 次才真报
「节点异常」。urltest 成功或用户主动连接即清零计数,只有持续失败才耗尽。
- _tryAutoReconnectCurrent(bounded 重连当前节点)+ _handleUnexpectedOff(kernel off 走同一逻辑)。
- _onNodeUnhealthy 无备用分支改调 _tryAutoReconnectCurrent;智能有备用仍优先切备用(不变)。
- 重连期给「网络波动,正在重连…」瞬态提示(nodeReconnecting,zh/en)。
- 测试:注入时钟 + 内存 SecureKV(避免 deviceId 挂起);kernel-off 连续判死→3 次自动重连后报
节点异常;路径B 无备用→自动重连当前节点不换节点。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 09:57:19 +08:00
wangjia
daaed39bac
fix(server): 用量分钟数按墙上时钟去重 —— 多设备并发不再 N× 超计(bug)
...
agent 按每 dp_uuid(每设备)每窗口计 SessionMinutes=1,控制面 ReportUsage 逐条累加进
usage_daily.minutes_used → N 台并发 = N×墙上时钟(线上 chenxin 2903 分钟/天,不可能)。
免费额度是「所有设备共同的时间」,超计会把免费账号错误判「时长耗尽」而断连。
改 ReportUsage:账户维度按 user 分组去重 —— 字节仍求和(可加),分钟按窗口取 max
(=窗口墙上分钟,恒为 1)去重,循环后每 user 各 flush 一次 AccumulateUsage/Hourly。
每设备维度(usage_device_*)保持逐台累加不变。usage_daily.minutes_used ≤ Σ 每设备分钟,
/me 的 quota_today_min 与 ConnectNode 免费门随之正确。历史数据次日归零、不回填。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 09:57:19 +08:00
wangjia
837bf55f6a
fix(client): 「今日剩余」不再冻结 —— 倒计时兜底启动 + 会话后静默刷新 /me(1.0.46+47)
...
免费版「今日剩余 xx 分钟」一直不变的两处原因:
1. 倒计时仅在经 _connect 连接时锁定 _freeRemainingSec;Android 常驻隧道/重启后自动
同步到已在跑的隧道时不走 _connect → 倒计时不启,连接页只显示静态「今日剩余」。
现于 _onKernelStatus.on 兜底:免费且未锁定则按当前额度补锁,保证显示会动的倒计时。
2. /me 仅登录时拉一次,会话消耗后不刷新 → 「今日剩余」停在登录值。现于断开/内核掉线
后静默刷新 /me(新增 MeNotifier.silentRefresh,不进 AsyncLoading → 不闪整页 spinner)。
markExhausted / watchAd 也改用 silentRefresh,避免额度变动时连接页闪 spinner。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 01:54:53 +08:00
wangjia
038e5b6ab5
fix(server): 被移除的设备会话轮询判活 —— dev 行不存在应判「失效」而非「有效」(bug)
...
SessionActive 在设备被 DeleteDevice 删行后 FindByUUID 返回 nil,此前一律 return true
(「未知/非本人设备不据此登出」)。结果:被移除的设备轮询 /me/session 永远拿到
active=true → 不登出、不弹框,只表现为数据面凭证被召回→隧道断→客户端误报「节点异常」。
改为:dev==nil(自身 device_id 查无此行,已登录客户端必然注册过 → 只能是被移除)→
return false,让其登出;dev.UserID!=userID(他人 uuid)仍 return true 兜底。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 01:54:53 +08:00
wangjia
a274cc40c8
fix(client): 额度卡「今日剩余」标签不再被省略 + 短「免费版」标签(1.0.45+46)
...
连接页额度卡头部一行放不下「今日剩余 X 分钟 + 免费版·每日10分钟」时,此前 Flexible
落在标签上,导致「今日剩余」被挤成「今日…」。改为:标签固定绝不省略,数值作最后收缩项;
右上角标签缩短为「免费版」(每日额度由卡片本身「今日剩余 X 分钟」表达,不再挤进胶囊)。
新增 planFreeTag l10n(zh 免费版 / en Free),quotaFree 仍保留供账户页 plan_badge_card。
golden 基线(quota_low/exhausted)在 Linux 权威环境重生成。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 01:40:50 +08:00
wangjia
fc8c03f2c3
chore: bump 1.0.44+45 (免费版倒计时+看广告加时 #21 )
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 01:01:42 +08:00
wangjia
b461a476a2
feat(client): 免费版连接倒计时 + 到点切断 + 灰按钮 + 看广告加时( #21 前端)
...
四端共享 Dart 实现,配合 #21 后端账户级卡控:
- me.dart: 加 quota_cap_min(当日总额度 = daily + 看广告 bonus,进度条分母)。
- quota_provider: total 取 quotaCapMin;isExhausted;markExhausted(倒计时归零本地置耗尽
+ 登录态拉 me 校准);watchAd() async 调 /ads/unlock(占位 ad_token=uuid)→ 刷新 me,返回 granted。
- connection_provider: 连接时锁定 _freeRemainingSec(会员 null 不倒计时);复用 elapsed 计时器
每 tick 算 freeCountdown,归零 → _onFreeQuotaExhausted(主动切断不报节点异常 + markExhausted);
倒计时用墙上时钟(后台漏跳回前台补上、准时切);连接遇后端 QUOTA_EXHAUSTED 兜底置耗尽。
- connect_button: enabled/onDisabledTap —— 额度耗尽 off 态灰化(锁图标),点击弹加时。
- quota_card: 三态(连接倒计时 mm:ss / 未连接剩余分钟 / 耗尽今日已用完);移动「看广告加时」、桌面「升级会员」。
- ad_reward_dialog(新): 移动端占位广告(播放中→3s→加时→奖励);桌面版硬 10 分钟提示去移动端/升级。
- l10n(zh/en): 倒计时/已用完/看广告加时/占位广告/桌面升级 双语。
- 测试: quota isExhausted/markExhausted;连接倒计时归零自动切断+置耗尽(注入时钟);
额度卡三态 widget;/me 契约含 quota_cap_min;golden 更新(Linux 权威基线 quota_low/exhausted)。
- 文档: docs/free-quota-ad.html + 登记 docs/index.html。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 00:33:21 +08:00
wangjia
e023fb6579
feat(server): 免费版账户级分钟卡控 + 累加式看广告加时( #21 后端)
...
免费版此前形同虚设:ConnectNode 每次连接发固定 daily_minutes×1min TTL、
从不扣减已用,重连即崭新 10 分钟,日额度从未强制。改为账户级(全设备共享)
真卡控 + 累加式看广告加时:
- migration 000020: usage_daily 加 ad_bonus_minutes(sqlite+mysql)。
当日额度 = plans.daily_minutes + ad_bonus_minutes;剩余 = 额度 − minutes_used。
- usage.store.AddAdBonusMinutes: 事务内累加封顶(替代布尔 MarkAdUnlocked),
返回新总额+本次实际加时;GetDay/GetUsageRange 补读 ad_bonus_minutes。
- usage.service.UnlockAd: verify+nonce 后 +adBonusPerAd(10) 封顶 adDailyBonusCeiling(120),
返回 granted+remaining;TodaySummary 加 MinutesCap/AdBonusMinutes。
- usage.ads DevVerifier: 放行式占位校验(nonce 防重放仍生效),接真 AdMob 前
让看广告加时流程可端到端跑通;main.go 按 ADS_DEV_MODE/默认装配。
- ads/unlock: 204 → 200 返回 {granted_minutes, minutes_remaining}。
- ConnectNode 免费门: 读 AccountDayMinutes(used,bonus) → remaining≤0 拒 QUOTA_EXHAUSTED,
否则 TTL=remaining(凭证到点硬切断兜底)。nodes.store 加 AccountDayMinutes。
- /me: 补 ad_bonus_minutes,quota_today_min 分母改 daily+bonus,加 quota_cap_min。
- quota.CheckFreeConnect 同步累加语义(免费基础 10 分钟不再需先看广告)。
- openapi + 契约/迁移版本/集成测试同步;新增 SQLite AddAdBonusMinutes 单测。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 23:44:10 +08:00
wangjia
0bedd1c4d1
fix(server): 连接删掉账户级 dp_uuid 回退,未注册设备直接拒( #16 )
...
ci-pangolin / Lint — shellcheck (push) Has been cancelled
ci-pangolin / OpenAPI Sync Check (push) Has been cancelled
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Has been cancelled
ci-pangolin / Flutter — analyze + test (push) Has been cancelled
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Has been cancelled
ci-pangolin / Codegen Drift — token 生成物未漂移 (push) Has been cancelled
ci-pangolin / Go — build + test (push) Has been cancelled
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Has been cancelled
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Has been cancelled
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Has been cancelled
漏洞:设备被移除后行已删,但靠残留 access token + 连接时回退「账户级 dp_uuid」
还能连上(EnsureDeviceDpUUID 找不到设备→回退),且不计入活跃设备数,绕过设备上限。
修:ConnectNode 去掉账户级回退。设备未注册/已被移除(EnsureDeviceDpUUID 返回
ErrDeviceNotFound)→ 403 {code:DEVICE_NOT_REGISTERED} + 提示「请重新登录以重新
注册设备」。客户端现有 ConnectApiException 处理会把该 message_zh 显示在连接页,
用户重新登录即重新注册(并命中登录挡板/超限流程)。
- nodes: 新增哨兵 ErrDeviceNotFound;EnsureDeviceDpUUID 包装它
- httpapi ConnectNode: errors.Is(ErrDeviceNotFound) → 拒连;其余错误 500
go build/vet 干净;nodes/httpapi 测试全过。客户端不用改。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 22:51:12 +08:00
wangjia
6ea2ec4a32
chore: bump 1.0.43+44 (含连接设备上限 backstop 客户端 UX)
...
ci-pangolin / Lint — shellcheck (push) Has been cancelled
ci-pangolin / OpenAPI Sync Check (push) Has been cancelled
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Has been cancelled
ci-pangolin / Flutter — analyze + test (push) Has been cancelled
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Has been cancelled
ci-pangolin / Codegen Drift — token 生成物未漂移 (push) Has been cancelled
ci-pangolin / Go — build + test (push) Has been cancelled
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Has been cancelled
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Has been cancelled
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Has been cancelled
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 22:25:38 +08:00
wangjia
0a70a24cdb
feat: 连接设备上限 backstop — 兜住"已登录的超限设备"( #16 )
...
ci-pangolin / Lint — shellcheck (push) Has been cancelled
ci-pangolin / OpenAPI Sync Check (push) Has been cancelled
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Has been cancelled
ci-pangolin / Flutter — analyze + test (push) Has been cancelled
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Has been cancelled
ci-pangolin / Codegen Drift — token 生成物未漂移 (push) Has been cancelled
ci-pangolin / Go — build + test (push) Has been cancelled
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Has been cancelled
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Has been cancelled
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Has been cancelled
登录挡板只拦新登录,已登录的超限会话(如 pro 已连 5 台)永远不被提示 → 限制形同虚设。
补服务端连接卡点:超限账户连接直接拒,兜住已登录设备(下次一连即被拦,无需重登)。
服务端:
- Entitlement 加 MaxDevices(EntitlementForUser 查 p.max_devices;free 默认 1)
- NodeStore.CountActiveDevices(近 30d 活跃)+ SQLNodeStore/mock 实现
- ConnectNode:activeCount > MaxDevices → 403 {code:DEVICE_LIMIT_EXCEEDED, max_devices}
客户端:
- ConnectApiException 解析 code/max_devices(结构化错误体)
- _connect 遇 DEVICE_LIMIT_EXCEEDED → 置 deviceLimitProvider → 顶层路由弹「移除设备」页
(设备列表走 devicesProvider),移除到上限内自动放行
后端 go test(store/nodes/httpapi/devices)全过;前端 analyze 干净 + 66 测试过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 22:11:13 +08:00
wangjia
8afe1f050c
chore(windows): installer 版本 1.0.41→1.0.42 对齐 pubspec
...
ci-pangolin / Lint — shellcheck (push) Has been cancelled
ci-pangolin / OpenAPI Sync Check (push) Has been cancelled
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Has been cancelled
ci-pangolin / Flutter — analyze + test (push) Has been cancelled
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Has been cancelled
ci-pangolin / Codegen Drift — token 生成物未漂移 (push) Has been cancelled
ci-pangolin / Go — build + test (push) Has been cancelled
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Has been cancelled
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Has been cancelled
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Has been cancelled
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 21:30:46 +08:00
wangjia
fdca3d84a7
chore: bump 1.0.42+43
...
ci-pangolin / Lint — shellcheck (push) Has been cancelled
ci-pangolin / OpenAPI Sync Check (push) Has been cancelled
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Has been cancelled
ci-pangolin / Flutter — analyze + test (push) Has been cancelled
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Has been cancelled
ci-pangolin / Codegen Drift — token 生成物未漂移 (push) Has been cancelled
ci-pangolin / Go — build + test (push) Has been cancelled
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Has been cancelled
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Has been cancelled
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Has been cancelled
含设备数量限制超限挡板(#16 )+ 看门狗后台修复 + 连接时长墙上时钟等本轮客户端改动。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 21:24:39 +08:00
wangjia
e6f622d605
docs: 设备数量限制 + 超限 UX 设计文档( #16 ) + 登记 index
...
ci-pangolin / Lint — shellcheck (push) Has been cancelled
ci-pangolin / OpenAPI Sync Check (push) Has been cancelled
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Has been cancelled
ci-pangolin / Flutter — analyze + test (push) Has been cancelled
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Has been cancelled
ci-pangolin / Codegen Drift — token 生成物未漂移 (push) Has been cancelled
ci-pangolin / Go — build + test (push) Has been cancelled
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Has been cancelled
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Has been cancelled
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Has been cancelled
沿用家族样式;涵盖决策(登录闸/踢旧/自动清理)、DB(无 schema 变更)、后端
(devices CheckDeviceLimit + auth 登录闸)、前端(DeviceLimitScreen)、验证与不在本轮。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 19:26:51 +08:00
wangjia
34d980d875
feat(client): 设备数超限挡板 DeviceLimitScreen( #16 前端)
...
登录响应带 device_limit 时,顶层路由把用户挡在「移除设备」页,降到上限内才进主界面。
- auth_api.dart:AuthTokens 解析 device_limit;新增 DeviceLimit/DeviceBrief 模型
- auth_provider.dart:deviceLimitProvider(挡板状态);auth_screen 登录后置入
- main.dart:isLoggedIn 后若 deviceLimit!=null → DeviceLimitScreen(挡 HomeShell)
- screens/device_limit_screen.dart(新):设备列表(本机标注不可删)+ 每台移除
+ 一键「移除最久未用」+ 退出登录;降到上限内自动清挡板放行(复用 devicesProvider.remove)
- l10n:deviceLimitTitle/Desc/RemoveOldest(zh/en);顺带修 devicesSub 文案 5→3 台
analyze 干净;client 全量测试 161 通过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 19:25:14 +08:00
wangjia
6bac7fd2f0
feat(server): 设备上限登录闸(超限非硬拒登,返回 device_limit 信号)#16
...
启用设备数量限制的服务端部分。登录照常成功签发 token(非硬拒登),但若账户活跃
设备数超套餐上限,登录响应带 device_limit 信号,客户端据此弹「移除设备」页。
- devices/store.go:CountActiveDevices(last_seen 近 staleWindow)+ PruneStaleDevices
(删超期僵尸行,免费版重装 churn 自愈)
- devices/service.go:staleWindow=30d;DeviceLimitStatus + CheckDeviceLimit
(best-effort prune → ResolvePlan → 活跃 count > cap 即 Over,附活跃设备列表)
- auth:DeviceRegistrar 加 CheckDeviceLimit;recordLogin 回传 *DeviceLimit;
LoginOutcome.DeviceLimit;Login 透传;handler tokenPairResponse.device_limit(omitempty)
- main.go:authDeviceRegistrar 适配 devices.CheckDeviceLimit → auth.DeviceLimit
- 测试:auth 登录透传超限信号(仍签发 token);devices within/over/prune-stale
连接侧 backstop(服务端硬拦)本轮从简未做,作为后续硬化(登录闸为客户端可信信号)。
DB 无 schema 变更。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 19:16:10 +08:00
wangjia
27dc59ed63
feat(server): pro 套餐设备上限 5 → 3
...
- seed(sqlite/mysql 000007)+ 001_init:pro max_devices 5→3(全新库直接对)
- 新增迁移 000019(sqlite/mysql,up/down):UPDATE plans SET max_devices=3
WHERE code='pro' —— 已迁移的线上库靠它更新;down 回退 5
- 000002/001 注释同步(free 1 / pro 3 / team 10)
- 测试:sqlite_migrate 期望版本 18→19;devices 集成测试 pro 断言 5→3
- free(1)/team(10)不变
注:设备上限当前仍未强制执行(登录注册 MaxDevices=0),仅数据/展示口径;
真正启用+超限 UX 见 todo #16。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 18:07:03 +08:00
wangjia
58211d2fb8
fix(client): 连接时长按墙上时钟算,切后台/锁屏不漏计
...
iOS 锁屏/切后台后连接页时长少算:elapsed 原来靠每秒 +1 的 Timer 累加,后台
isolate 被挂起、Timer 停跳 → 后台那段时长漏掉。改为记 _connectedAt 起点,
elapsed = now - _connectedAt(墙上时钟),与 timer 是否跳无关;回前台(resumed)
立即 _refreshElapsed 补上。_connectedAt 用 ??= 保留同一会话起点,断开才清。
测试:新增「切后台 300s 时长不漏计」回归(前台 10s + 后台 300s = 310s);全 8 例过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 09:53:49 +08:00
wangjia
002eea7562
build(scripts): build-libbox apple 按平台分发产物,避免覆盖另一端 Frameworks
...
只编某一平台时(apple macos / apple ios),产出的 xcframework 只含该平台 slice,
不能覆盖到另一平台的 client/*/Frameworks(否则那端 build 缺 slice)。改为:
空(全 Apple)→ macos+ios 都放;macos→只 macos;ios→只 ios。
本次 iOS 打包已验证只部署到 client/ios/Frameworks。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 09:37:09 +08:00
wangjia
37c1724b67
fix(client): 切后台停看门狗 — 后台 urltest 被限流不再误判节点死断连
...
真机(Honor/MagicOS)复现:连上后切后台 ~1min 必断,报「当前节点异常」。
logcat 铁证:进程存活、Dart 看门狗后台照跑,
[Watchdog] urltest stale 59s → upstream dead → unhealthy → doStop
根因:后台(Doze)把 urltest 主动探测限流 → urltest 停更,但 speed 帧仍在流
→ statsLive=true + urltest 陈旧 → 路径A 误判上游死,把正常隧道断掉。上次的
statsLive 闸只挡「stats 整体停」(macOS 睡眠),挡不住「speed 在流、只 urltest 停」。
修:看门狗是前台 UX 功能(发现节点死好让用户换),后台无 UI 可响应、urltest 必被限流
→ AppLifecycleListener 在 paused/hidden 停看门狗,resumed 且仍连接则重启(基准由
_onStats 首帧/gap 重置)。真·内核掉线(路径A',事件驱动)不受影响,仍上报。
测试:connection_watchdog 新增「切后台停看门狗」回归(后台 speed 在流+urltest 停 >45s
不切节点);全 7 例过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 08:54:16 +08:00
wangjia
b23377acde
build(scripts): 各端 libbox 构建脚本修正 + 自动部署产物
...
排查 Android 打包"libbox.aar not found / Unresolved reference nekohasekai"踩坑:
- scripts/build-libbox.sh:
· 构建后自动部署产物到工程引用位置(android→app/kernel/dist/android/libbox.aar;
apple→client/{macos,ios}/Frameworks/Libbox.xcframework),不再留 /tmp 让人手动拷
· android 自动挑 NDK ≥28(未设 ANDROID_NDK_HOME 时):官方 build_libbox 拉 cronet-go,
其 libcronet.a 在 NDK 27 lld 链接报 unknown relocation(315)失败
· 头注释补全前置(NDK≥28 / JDK17 / gomobile 入 PATH)与产物路径
- app/kernel/build-android.sh:补 -javapkg io.nekohasekai —— 缺它产出默认包名 `libbox`,
与 Kotlin 的 io.nekohasekai.libbox 对不上、APK Kotlin 编译失败
- build.gradle:注释指明两条构建途径 + 包名必须 io.nekohasekai.libbox
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 07:53:35 +08:00
wangjia
07861ea04b
fix(client): 看门狗后台唤醒不再误判节点死→假重连 ( #18 )
...
现象:app/Mac 后台或睡眠一段时间再打开,出现"重连过程"但 VPN 没掉。
根因:连通看门狗路径A(urltest stale)用墙上时钟算陈旧度,挂起期 stats 停更、
_lastUrltestOk 冻住;唤醒时一算 >45s 即判上游死 → _onNodeUnhealthy 触发
disconnect+connect(智能模式还自动切节点),但隧道一直好好的。
修(connection_provider.dart 一处):
- _onStats 记 _lastStatsAt(任意 stats 帧时刻);stats 出现 >10s 长间断(挂起/唤醒)
时把 _lastUrltestOk 前移到现在,给节点新的 45s 窗口自证
- 看门狗路径A 增加 statsLive 闸:仅在 stats 确实在流(距今 <10s)且 urltest 停 ≥45s
时才判节点死;stats 整体停(挂起未恢复)则跳过本轮
- 加可注入时钟 _now(默认 DateTime.now),使时间驱动的 path A 可单测
测试:connection_watchdog_test 新增两例 —— ① stats 在流但 urltest 停≥45s 仍正常判死
切节点(path A 未被削弱);② 挂起 60s 唤醒不假重连(无 statsLive 闸则会误切,真回归)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 07:10:34 +08:00
wangjia
6bcdbbe752
revert(macos): killswitch 回退 L0(低优先级暂缓)
...
用户判定 killswitch 优先级低、暂不做:
- includeAllNetworks 实测堵死整机网络;退化方案 enforceRoutes+on-demand
几乎等于没做(核心 fail-closed 没拿到),on-demand 还对免费版配额造成
重连 churn —— 性价比低
- VpnChannel.swift 恢复到 L0 stub(setKillSwitch→result(nil));
PacketTunnelProvider.swift 已是原样
- docs §6.5 改为「尝试与暂缓」记录,状态表 macOS 回 L0
- local_test.sh 的 ks-* 漏测子命令保留(将来重拾 killswitch 时可用)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 06:38:47 +08:00