diff --git a/NEXT-ROUND-NOTES.md b/NEXT-ROUND-NOTES.md new file mode 100644 index 0000000..5c7c1b8 --- /dev/null +++ b/NEXT-ROUND-NOTES.md @@ -0,0 +1,74 @@ +# amo 下一版修改清单 + +用户 2026-10-21 上线当晚起陆续口述,攒齐后统一施工。 + +## 0. 施工口径变更(2026-10-21 04:36 用户拍板) + +Node 实现(server.js / lib / agent.js)冻结退役:本版起 **Go 单一真源**,只改 panel-go + agent-go + 共用前端,不再双写、不再对 Node 做 difftest;验收金标准改为冻结 Go 现状行为作回归基线。Node 代码留仓作历史与回滚参考,不删。 + +## 1. 客户页顶部加统计条(2026-10-21 03:53,用户附 3x-ui 截图) + +照 3x-ui 客户端页顶部那排,在 amo「客户与订阅」页上部加统计条,六个数: +- 客户端(总数) +- 在线(当前在线数,沿用现有 3 分钟 presence 窗口口径) +- 耗尽(流量已用满的客户数) +- 即将耗尽(接近用满的客户数,阈值口径施工时对齐 3x-ui) +- 关闭(已停用客户数) +- 启用(已启用客户数) + +出处:用户在路由器 3x-ui(:55889/rogue)客户端页截图,顶栏现网数字为 26 / 6 / 0 / 0 / 0 / 26。 + +## 2. 客户凭据改为全节点共用一套(2026-10-21 03:56,用户附 amo 凭据页截图) + +现状(amo):凭据长在每个节点绑定上——同一客户在 HWSG / ZouSG-V6 / ZouSG-V4 / LEGJP-V4 / LEGJP-V6 各有一套不同的 UUID、密码、Reality Short ID。 + +目标(照 3x-ui):凭据收归客户级——一个客户一套 UUID + 密码 + Short ID,名下所有节点共用;节点绑定不再各存一套。 + +实证(2026-10-21 查用户 2026-10-20 发的路由器 3x-ui 备份库,只读):clients 表 uuid/password/sub_id 只存一份(email 唯一);client_inbounds 是纯关联表,无任何凭据列。26 个客户中 19 个挂了多个入站,最多的 rogue 挂 20 个入站、共用同一套凭据。此模型与日后 3x-ui→amo 正式迁移正好 1:1 对齐。 + +施工前要定的点: +- 节点侧 Reality 密钥对是服务器身份,不动,仍按节点托管(keyStore)。 +- 存量不是问题:用户 2026-10-21 确认现网 amo 里服务器/客户/节点全是测试数据(随便加的),切换凭据模型时测试数据直接按新模型重建,无需收敛迁移;真实用户仍在 3x-ui 侧(待正式迁移)。 + +## 3. 内容区左右间距调小到与 3x-ui 一致(2026-10-21 04:02,用户附 amo 节点页 + 3x-ui 主机页对照图) + +amo 各页主内容区两侧留白偏大;把左右间距(padding/最大宽度约束)调小,调到与 3x-ui 一样的贴边宽度。所有页面统一,不是一页一调。 + +## 4. 服务器页「核心版本」加真实探测(2026-10-21 04:11 用户) + +现状:服务器页核心版本恒显"—"(agent 心跳没报这个字段)。 +改法:agent 探测本机 sing-box 版本随心跳上报,面板落到核心版本列显示。 +实现口径(已与用户对过内存账):版本探测只在 agent 启动 / 引擎安装更新 / 二进制文件变化时跑一次 `sing-box version` 并缓存,心跳只多带一个短字符串字段;不许每轮心跳起子进程。老 agent 无此字段时继续显"—",向后兼容。 + +## 5. 删除「延迟」(2026-10-21 04:12 用户) + +服务器页的延迟列删掉,统计条里的"平均延迟"一并去掉;系统状态页如有同名占位同删。 +理由(用户原话口径):延迟要不断探测上报、没什么用、还费内存——明确不做延迟探测功能,占位列也不要留。 + +## 6. 行操作小图标加悬停名称提示(2026-10-21 04:15,用户附客户行图标特写) + +列表行内的功能小图标(二维码、详情、重置、编辑、删除等)鼠标悬停时显示对应功能名称(tooltip/title)。客户页先行,全站同类图标统一补齐。 +样式照 3x-ui(用户 2026-10-21 附特写):黑底白字圆角气泡带小尖角、出现在图标上方;被悬停的图标本身带浅灰背景高亮。名称与 3x-ui 对齐:二维码 / 详情(i 图标)/ 重置流量(⇄ 图标)/ 编辑 / 删除。 + +## 7. 节点端口「范围内随机」保存后反复重掷(2026-10-21 04:19,用户附编辑节点截图) + +现状:新建节点时入口端口为"范围内随机",分配到随机端口;若不手工改成固定,之后每次编辑该节点(哪怕只改其他参数)点保存,端口都会被重新随机分配成另一个——端口静默漂移,客户端随之失效。 + +要求:随机只在新建时掷一次;已分配的端口在后续编辑中必须保持不变,除非用户显式改端口模式/数值。实现口径施工时定(如:编辑既有节点时端口模式默认按已分配端口固定呈现),验收要点:编辑既有节点任意字段保存后端口逐字不变。 + +## 8. 中转模型与 3x-ui 不对齐:缺"A 入站经内网 IP 中转到 B 节点"(2026-10-21 04:26,用户附中转节点编辑截图) + +现状:amo 的中转是"入口 → 出口设备"模型(转发拓扑 + 出口 SOCKS5),与 3x-ui 的"出站 + 路由规则"完全不同。 +缺口:无法实现用户在 3x-ui 上的既有用法——A 服务器的入站节点通过内网 IP 直接中转到 B 服务器的节点(线路鸡 → 落地鸡内网中转)。 +要求:下一版补齐这种中转形态(这是用户现网在用的模式,迁移前置之一)。 + +设计方向(2026-10-21 04:28 已与用户对过):出口目标二选一——① 受管设备(现状,面板管两边);② 新增"手动落地端点":填地址(内网 IP/域名)+ 端口 + 协议 + 凭据(VLESS=UUID、SS2022=密码、SOCKS5=账密),面板只在 A 侧生成出站+路由(sing-box outbound + route),B 侧不要求装 agent、可以是 3x-ui 的现成节点。凭据只存主控、随部署下发。 +补充(用户 2026-10-21 确认的理解):第三方/别人提供的节点同样可作落地——只要有 IP+协议+ID+密码即可,来源不限(自家内网鸡、3x-ui 节点、别人给的节点都行);前提是 A 能直达该地址端口。一个 vless:// / ss:// 分享链接本身就含全这四样,施工时可顺带做"粘贴链接自动填表"。 + +## 9. 删除右上角头像圆图标(2026-10-21 04:34,用户附顶栏特写) + +顶栏右侧的圆形头像(显用户名首字母"G")是纯装饰 span,无任何点击事件(退出登录在侧栏底部另有按钮)——删掉。已查证同排的「刷新」按钮有效(绑定 load() 重新拉数重渲染,与 45 秒自动刷新同一套),保留不动。 + +## 10. 删除顶栏全局搜索框(2026-10-21 04:39,用户附顶栏截图) + +顶栏"搜索服务器、客户、节点…"输入框是写死 disabled 的摆设、无任何功能;且每页都有自己的可用搜索框,此框多余——删除(顶栏共用一处,删一处全站生效)。 diff --git a/panel-go/golden/README.md b/panel-go/golden/README.md new file mode 100644 index 0000000..5583545 --- /dev/null +++ b/panel-go/golden/README.md @@ -0,0 +1,33 @@ +# Go 面板金标准回归基线(golden) + +2026-10-21 起 Node 实现冻结退役、Go 单一真源,difftest(Node 差分)随之退役。 +本目录把 **Go 面板在种子库上的当前行为**冻结为回归基线,替代 difftest 的 +守门角色: + +- `golden.mjs` — 抓取/校验脚本。复用 `../difftest/` 的种子库(`seed.json`) + 与学习式 Normalizer(`../difftest/lib.mjs`):以种子副本启动一个 Go 面板, + 按固定剧本驱动(登录 → 种子 agent 心跳 → 列表 → 凭据 → 订阅全格式 → + 建服务器/注册 agent → 建客户/节点/分配/部署并 drain 部署任务 → 用量上报 + 两轮 → 呈现投影 → UI 式全量 PATCH 节点 → `/api/system` 结构 → 备份导出 → + 错误用例),全部响应归一化后存入 `golden.json`(25 个 section)。 +- `golden.json` — 冻结的行为快照(2026-10-21,基线提交时的 Go 现状, + 二进制由 HEAD `156a31c` 构建)。 + +用法: + +```bash +node golden.mjs capture [--bin /path/to/nexusgate-panel] # 重新冻结(仅在确认行为变更为预期时) +node golden.mjs verify [--bin /path/to/nexusgate-panel] # 与基线比对,有差异退出码 1 并打印首个 diff 路径 +``` + +约定: + +- verify 出现 diff 时,先判定是**预期变更**(本版 9 项修改各自的预期 diff + 在 NEXT-ROUND-REPORT.md 中逐项登记)还是回归;预期变更在对应项提交里 + 一并重新 capture 并在提交说明中注明 section 名。 +- `/api/system` 的数值多为实时采样,脚本只保留结构与确定性计数 + (nodesTotal/customersTotal/online* /connections),其余数值与 IP 压成 + 占位;uptime 类键同样压平。其余 section 的数值都是剧本确定性结果, + 逐字比对。 +- 剧本对时间的依赖已被窗口设计吸收(presence 180s 窗口、心跳在线判定), + 正常复跑应恒 PASS;偶发 FAIL 先复跑一次区分抖动与真回归。 diff --git a/panel-go/golden/golden.json b/panel-go/golden/golden.json new file mode 100644 index 0000000..ab39c4d --- /dev/null +++ b/panel-go/golden/golden.json @@ -0,0 +1,3491 @@ +{ + "meta": { + "capturedAt": "2026-10-20T20:44:51.343Z", + "note": "Go panel golden baseline; see README.md in this directory." + }, + "sections": { + "login": { + "body": { + "csrf": "#1", + "user": { + "id": "#1", + "role": "owner", + "username": "admin" + } + }, + "status": 200 + }, + "session": { + "body": { + "authenticated": true, + "csrf": "#1", + "user": { + "id": "#1", + "role": "owner", + "username": "admin" + } + }, + "status": 200 + }, + "heartbeatSeedAgents": { + "entry": { + "body": { + "ok": true, + "serverTime": "" + }, + "status": 200 + }, + "exit": { + "body": { + "ok": true, + "serverTime": "" + }, + "status": 200 + } + }, + "lists": { + "customers": { + "body": { + "customers": [ + { + "createdAt": "", + "deviceLimit": 0, + "expiresAt": null, + "group": "", + "id": "#1", + "ipLimit": 0, + "lastActiveAt": null, + "lastUsageAt": "", + "name": "Alice", + "notes": "", + "observedIpCount": 0, + "online": false, + "serverUsage": { + "srv:#1": { + "usageCounters": { + "downlink": 2000, + "epoch": "diff-epoch-1", + "uplink": 1000 + } + } + }, + "speedDownBps": 0, + "speedUpBps": 0, + "status": "active", + "subscriptionClientCount": 3, + "subscriptionToken": "#1", + "tags": [], + "trafficLimitBytes": 0, + "updatedAt": "", + "usedBytes": 3000, + "usedDownlinkBytes": 2000, + "usedUplinkBytes": 1000 + }, + { + "createdAt": "", + "deviceLimit": 2, + "expiresAt": null, + "group": "", + "id": "#2", + "ipLimit": 5, + "lastActiveAt": null, + "name": "Bob", + "notes": "", + "observedIpCount": 0, + "online": false, + "speedDownBps": 0, + "speedUpBps": 0, + "status": "active", + "subscriptionClientCount": 0, + "subscriptionToken": "#2", + "tags": [], + "trafficLimitBytes": 1073741824, + "updatedAt": "", + "usedBytes": 0, + "usedDownlinkBytes": 0, + "usedUplinkBytes": 0 + } + ] + }, + "status": 200 + }, + "jobs": { + "body": { + "jobs": [ + { + "action": "apply_resource", + "attempts": 1, + "completedAt": "", + "createdAt": "", + "error": null, + "id": "#1", + "leaseUntil": null, + "nodeId": "#1", + "serverId": "#1", + "startedAt": "", + "status": "completed", + "updatedAt": "" + }, + { + "action": "apply_resource", + "attempts": 1, + "completedAt": "", + "createdAt": "", + "error": null, + "id": "#2", + "leaseUntil": null, + "nodeId": "#2", + "serverId": "#2", + "startedAt": "", + "status": "completed", + "updatedAt": "" + }, + { + "action": "apply_resource", + "attempts": 1, + "completedAt": "", + "createdAt": "", + "error": null, + "id": "#3", + "leaseUntil": null, + "nodeId": "#2", + "serverId": "#1", + "startedAt": "", + "status": "completed", + "updatedAt": "" + } + ] + }, + "status": 200 + }, + "nodes": { + "body": { + "nodeClients": [ + { + "clientUri": "vless://#1@entry.example.com:#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=#1&sid=45584502b5567121&type=tcp&headerType=none&flow=xtls-rprx-vision#N1", + "createdAt": "", + "customerId": "#1", + "id": "#1", + "nodeId": "#2", + "status": "active", + "updatedAt": "" + }, + { + "clientUri": "vless://#2@entry.example.com:#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=#1&sid=0adf5e3d9a002211&type=tcp&headerType=none&flow=xtls-rprx-vision#N1", + "createdAt": "", + "customerId": "#2", + "id": "#2", + "nodeId": "#2", + "status": "active", + "updatedAt": "" + }, + { + "clientUri": "ss://#1@entry.example.com:#3#N2", + "createdAt": "", + "customerId": "#1", + "id": "#3", + "nodeId": "#1", + "status": "active", + "updatedAt": "" + } + ], + "nodes": [ + { + "artifacts": { + "realityPublicKey": "#1" + }, + "autoRemoving": false, + "clientCount": 2, + "createdAt": "", + "entryReady": true, + "error": null, + "exitPort": "#1", + "exitPortMode": "random", + "exitProtocol": "shadowsocks-2022-aes256", + "exitReady": true, + "exitResourceId": "#1", + "exitServerId": "#2", + "id": "#2", + "lastUsageAt": "", + "meteredTraffic": { + "downlink": 5000, + "uplink": 4000 + }, + "name": "N1", + "networkMode": "ipv4", + "onlineCount": 0, + "onlineCustomerIds": [], + "pendingDelete": false, + "port": "#2", + "portMode": "random", + "protocol": "vless-reality-vision", + "reality": { + "destPort": 443, + "keyId": "reality-#2", + "serverName": "www.amd.com" + }, + "realityDestPort": 443, + "realityServerName": "www.amd.com", + "resourceId": "#2", + "retiredUsageEpochs": [], + "serverId": "#1", + "sortIndex": 0, + "speedDownBps": 0, + "speedUpBps": 0, + "status": "active", + "topology": "forward", + "updatedAt": "", + "usageCounters": { + "downlink": 5000, + "epoch": "diff-epoch-1", + "uplink": 4000 + }, + "wsPath": "#1" + }, + { + "artifacts": {}, + "autoRemoving": false, + "clientCount": 1, + "createdAt": "", + "entryReady": true, + "error": null, + "exitPort": null, + "exitPortMode": null, + "exitProtocol": null, + "exitReady": false, + "exitResourceId": "#3", + "exitServerId": null, + "id": "#1", + "lastUsageAt": null, + "meteredTraffic": null, + "name": "N2", + "networkMode": "ipv4", + "onlineCount": 0, + "onlineCustomerIds": [], + "pendingDelete": false, + "port": "#3", + "portMode": "random", + "protocol": "shadowsocks-2022-aes128", + "realityDestPort": 443, + "realityServerName": "www.tesla.com", + "resourceId": "#4", + "retiredUsageEpochs": [], + "serverId": "#1", + "sortIndex": 1, + "speedDownBps": 0, + "speedUpBps": 0, + "status": "active", + "topology": "direct", + "updatedAt": "", + "usageCounters": null, + "wsPath": "#2" + } + ] + }, + "status": 200 + }, + "overview": { + "body": { + "activity": [ + { + "action": "deploy_node", + "actor": "admin", + "at": "", + "detail": { + "name": "N2" + }, + "id": "#1", + "target": "#1" + }, + { + "action": "deploy_node", + "actor": "admin", + "at": "", + "detail": { + "name": "N1" + }, + "id": "#2", + "target": "#2" + }, + { + "action": "assign_customer", + "actor": "admin", + "at": "", + "detail": { + "customer": "Alice", + "customerId": "#1" + }, + "id": "#3", + "target": "#1" + }, + { + "action": "assign_customer", + "actor": "admin", + "at": "", + "detail": { + "customer": "Bob", + "customerId": "#2" + }, + "id": "#4", + "target": "#2" + }, + { + "action": "assign_customer", + "actor": "admin", + "at": "", + "detail": { + "customer": "Alice", + "customerId": "#1" + }, + "id": "#5", + "target": "#2" + }, + { + "action": "create_node", + "actor": "admin", + "at": "", + "detail": { + "name": "N2" + }, + "id": "#6", + "target": "#1" + }, + { + "action": "create_node", + "actor": "admin", + "at": "", + "detail": { + "name": "N1" + }, + "id": "#7", + "target": "#2" + }, + { + "action": "create_customer", + "actor": "admin", + "at": "", + "detail": { + "name": "Bob" + }, + "id": "#8", + "target": "#2" + }, + { + "action": "create_customer", + "actor": "admin", + "at": "", + "detail": { + "name": "Alice" + }, + "id": "#9", + "target": "#1" + }, + { + "action": "reconcile_server", + "actor": "agent:#1", + "at": "", + "detail": { + "nodes": 0 + }, + "id": "#10", + "target": "#2" + }, + { + "action": "enroll_agent", + "actor": "agent:#1", + "at": "", + "detail": {}, + "id": "#11", + "target": "#2" + }, + { + "action": "create_enrollment", + "actor": "admin", + "at": "", + "detail": {}, + "id": "#12", + "target": "#2" + } + ], + "counts": { + "activeNodes": 2, + "customers": 2, + "nodes": 2, + "online": 2, + "queuedJobs": 0, + "servers": 2 + }, + "degraded": [], + "version": "0.6.8" + }, + "status": 200 + }, + "servers": { + "body": { + "servers": [ + { + "agentVersion": "0.6.8", + "createdAt": "", + "enabled": true, + "engine": null, + "id": "#1", + "labels": [], + "lastSeenAt": "", + "name": "Entry", + "pendingCleanup": 0, + "portRangeEnd": 50000, + "portRangeStart": 20000, + "publicAddress": "entry.example.com", + "publicAddressV6": "v6.example.com", + "region": "", + "role": "relay", + "status": "online", + "system": {}, + "tlsDomain": "tls.example.com", + "updatedAt": "", + "usage": { + "error": null, + "lastReportAt": "", + "sampleCount": 2 + } + }, + { + "agentVersion": "0.6.8", + "createdAt": "", + "enabled": true, + "engine": null, + "id": "#2", + "labels": [], + "lastSeenAt": "", + "name": "Exit", + "pendingCleanup": 0, + "portRangeEnd": 50000, + "portRangeStart": 20000, + "publicAddress": "exit.example.com", + "publicAddressV6": "", + "region": "", + "role": "exit", + "status": "online", + "system": {}, + "tlsDomain": "", + "updatedAt": "" + } + ] + }, + "status": 200 + } + }, + "credentials": { + "alice": { + "body": { + "customerId": "#1", + "items": [ + { + "clientId": "#1", + "nodeClientId": "#1", + "nodeId": "#2", + "nodeName": "N1", + "password": "#3", + "protocol": "vless-reality-vision", + "shortId": "#4", + "status": "active", + "username": "" + }, + { + "clientId": "#3", + "nodeClientId": "#3", + "nodeId": "#1", + "nodeName": "N2", + "password": "#5", + "protocol": "shadowsocks-2022-aes128", + "shortId": "#6", + "status": "active", + "username": "" + } + ], + "subscriptionToken": "#1" + }, + "status": 200 + }, + "bob": { + "body": { + "customerId": "#2", + "items": [ + { + "clientId": "#2", + "nodeClientId": "#2", + "nodeId": "#2", + "nodeName": "N1", + "password": "#7", + "protocol": "vless-reality-vision", + "shortId": "#8", + "status": "active", + "username": "" + } + ], + "subscriptionToken": "#2" + }, + "status": 200 + } + }, + "subscriptionsSeed": { + "alice": { + "auto-clash": { + "body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N1\"\n server: \"entry.example.com\"\n port: #2\n type: \"vless\"\n uuid: \"#1\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"#1\"\n short-id: \"#4\"\n flow: \"xtls-rprx-vision\"\n - name: \"N2\"\n server: \"entry.example.com\"\n port: #3\n type: \"ss\"\n cipher: \"2022-blake3-aes-128-gcm\"\n password: \"xOcB7XGgo6VpaEHZfNXrlQ==:#5\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"N1\"\n - \"N2\"\nrules:\n - \"MATCH,节点选择\"\n", + "contentType": "text/yaml; charset=utf-8", + "status": 200, + "userinfo": "upload=1000; download=2000; total=0; expire=0" + }, + "auto-generic": { + "body": "vless://#1@entry.example.com:#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=#1&sid=#4&type=tcp&headerType=none&flow=xtls-rprx-vision#N1\nss://#1@entry.example.com:#3#N2\n", + "contentType": "text/plain; charset=utf-8", + "status": 200, + "userinfo": "upload=1000; download=2000; total=0; expire=0" + }, + "base64": { + "body": "vless://#1@entry.example.com:#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=#1&sid=#4&type=tcp&headerType=none&flow=xtls-rprx-vision#N1\nss://#1@entry.example.com:#3#N2\n", + "contentType": "text/plain; charset=utf-8", + "status": 200, + "userinfo": "upload=1000; download=2000; total=0; expire=0" + }, + "clash": { + "body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N1\"\n server: \"entry.example.com\"\n port: #2\n type: \"vless\"\n uuid: \"#1\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"#1\"\n short-id: \"#4\"\n flow: \"xtls-rprx-vision\"\n - name: \"N2\"\n server: \"entry.example.com\"\n port: #3\n type: \"ss\"\n cipher: \"2022-blake3-aes-128-gcm\"\n password: \"xOcB7XGgo6VpaEHZfNXrlQ==:#5\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"N1\"\n - \"N2\"\nrules:\n - \"MATCH,节点选择\"\n", + "contentType": "text/yaml; charset=utf-8", + "status": 200, + "userinfo": "upload=1000; download=2000; total=0; expire=0" + }, + "clash-smart": { + "body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N1\"\n server: \"entry.example.com\"\n port: #2\n type: \"vless\"\n uuid: \"#1\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"#1\"\n short-id: \"#4\"\n flow: \"xtls-rprx-vision\"\n - name: \"N2\"\n server: \"entry.example.com\"\n port: #3\n type: \"ss\"\n cipher: \"2022-blake3-aes-128-gcm\"\n password: \"xOcB7XGgo6VpaEHZfNXrlQ==:#5\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"自动选择\"\n - \"N1\"\n - \"N2\"\n - \"DIRECT\"\n - name: \"自动选择\"\n type: url-test\n url: \"https://www.gstatic.com/generate_204\"\n interval: 300\n tolerance: 80\n proxies:\n - \"N1\"\n - \"N2\"\n - name: \"流媒体\"\n type: select\n proxies:\n - \"节点选择\"\n - \"自动选择\"\n - \"N1\"\n - \"N2\"\n - name: \"AI 服务\"\n type: select\n proxies:\n - \"节点选择\"\n - \"自动选择\"\n - \"N1\"\n - \"N2\"\n - name: \"Meta 服务\"\n type: select\n proxies:\n - \"节点选择\"\n - \"自动选择\"\n - \"N1\"\n - \"N2\"\nrules:\n - \"GEOSITE,category-ads-all,REJECT\"\n - \"GEOSITE,private,DIRECT\"\n - \"GEOSITE,openai,AI 服务\"\n - \"GEOSITE,netflix,流媒体\"\n - \"GEOSITE,facebook,Meta 服务\"\n - \"DOMAIN-SUFFIX,fbcdn.net,Meta 服务\"\n - \"DOMAIN-SUFFIX,instagram.com,Meta 服务\"\n - \"DOMAIN-SUFFIX,whatsapp.net,Meta 服务\"\n - \"GEOSITE,geolocation-!cn,节点选择\"\n - \"GEOSITE,cn,DIRECT\"\n - \"GEOIP,CN,DIRECT\"\n - \"MATCH,节点选择\"\n", + "contentType": "text/yaml; charset=utf-8", + "status": 200, + "userinfo": "upload=1000; download=2000; total=0; expire=0" + }, + "raw": { + "body": "vless://#1@entry.example.com:#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=#1&sid=#4&type=tcp&headerType=none&flow=xtls-rprx-vision#N1\nss://#1@entry.example.com:#3#N2\n", + "contentType": "text/plain; charset=utf-8", + "status": 200, + "userinfo": "upload=1000; download=2000; total=0; expire=0" + }, + "singbox": { + "body": "{\n \"log\": {\n \"level\": \"warn\"\n },\n \"inbounds\": [\n {\n \"type\": \"mixed\",\n \"tag\": \"local\",\n \"listen\": \"127.0.0.1\",\n \"listen_port\": 2080\n }\n ],\n \"outbounds\": [\n {\n \"type\": \"selector\",\n \"tag\": \"proxy\",\n \"outbounds\": [\n \"node-1\",\n \"node-2\"\n ]\n },\n {\n \"type\": \"vless\",\n \"tag\": \"node-1\",\n \"server\": \"entry.example.com\",\n \"server_port\": #2,\n \"uuid\": \"#1\",\n \"flow\": \"xtls-rprx-vision\",\n \"tls\": {\n \"enabled\": true,\n \"server_name\": \"www.amd.com\",\n \"reality\": {\n \"enabled\": true,\n \"public_key\": \"#1\",\n \"short_id\": \"#4\"\n }\n }\n },\n {\n \"type\": \"shadowsocks\",\n \"tag\": \"node-2\",\n \"server\": \"entry.example.com\",\n \"server_port\": #3,\n \"method\": \"2022-blake3-aes-128-gcm\",\n \"password\": \"xOcB7XGgo6VpaEHZfNXrlQ==:#5\"\n },\n {\n \"type\": \"direct\",\n \"tag\": \"direct\"\n }\n ],\n \"route\": {\n \"final\": \"proxy\"\n }\n}\n", + "contentType": "application/json; charset=utf-8", + "status": 200, + "userinfo": "upload=1000; download=2000; total=0; expire=0" + }, + "surge": { + "body": "[General]\nloglevel = notify\n[Proxy]\nN2 = ss, entry.example.com, #3, encrypt-method=2022-blake3-aes-128-gcm, password=xOcB7XGgo6VpaEHZfNXrlQ__:#5VgHuXw__\n[Proxy Group]\n节点选择 = select, N2, DIRECT\n自动选择 = url-test, N2, url=http://www.gstatic.com/generate_204, interval=600\n[Rule]\nGEOIP,CN,DIRECT\nFINAL,节点选择\n", + "contentType": "text/plain; charset=utf-8", + "status": 200, + "userinfo": "upload=1000; download=2000; total=0; expire=0" + } + }, + "bob": { + "auto-clash": { + "body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N1\"\n server: \"entry.example.com\"\n port: #2\n type: \"vless\"\n uuid: \"#2\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"#1\"\n short-id: \"#8\"\n flow: \"xtls-rprx-vision\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"N1\"\nrules:\n - \"MATCH,节点选择\"\n", + "contentType": "text/yaml; charset=utf-8", + "status": 200, + "userinfo": "upload=0; download=0; total=1073741824; expire=0" + }, + "auto-generic": { + "body": "{\"message\":\"订阅客户端数达到上限。可在客户访问记录中检查并重置观察窗口。\"}", + "contentType": "application/json; charset=utf-8", + "status": 429, + "userinfo": "" + }, + "base64": { + "body": "vless://#2@entry.example.com:#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=#1&sid=#8&type=tcp&headerType=none&flow=xtls-rprx-vision#N1\n", + "contentType": "text/plain; charset=utf-8", + "status": 200, + "userinfo": "upload=0; download=0; total=1073741824; expire=0" + }, + "clash": { + "body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N1\"\n server: \"entry.example.com\"\n port: #2\n type: \"vless\"\n uuid: \"#2\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"#1\"\n short-id: \"#8\"\n flow: \"xtls-rprx-vision\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"N1\"\nrules:\n - \"MATCH,节点选择\"\n", + "contentType": "text/yaml; charset=utf-8", + "status": 200, + "userinfo": "upload=0; download=0; total=1073741824; expire=0" + }, + "clash-smart": { + "body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N1\"\n server: \"entry.example.com\"\n port: #2\n type: \"vless\"\n uuid: \"#2\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"#1\"\n short-id: \"#8\"\n flow: \"xtls-rprx-vision\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"自动选择\"\n - \"N1\"\n - \"DIRECT\"\n - name: \"自动选择\"\n type: url-test\n url: \"https://www.gstatic.com/generate_204\"\n interval: 300\n tolerance: 80\n proxies:\n - \"N1\"\n - name: \"流媒体\"\n type: select\n proxies:\n - \"节点选择\"\n - \"自动选择\"\n - \"N1\"\n - name: \"AI 服务\"\n type: select\n proxies:\n - \"节点选择\"\n - \"自动选择\"\n - \"N1\"\n - name: \"Meta 服务\"\n type: select\n proxies:\n - \"节点选择\"\n - \"自动选择\"\n - \"N1\"\nrules:\n - \"GEOSITE,category-ads-all,REJECT\"\n - \"GEOSITE,private,DIRECT\"\n - \"GEOSITE,openai,AI 服务\"\n - \"GEOSITE,netflix,流媒体\"\n - \"GEOSITE,facebook,Meta 服务\"\n - \"DOMAIN-SUFFIX,fbcdn.net,Meta 服务\"\n - \"DOMAIN-SUFFIX,instagram.com,Meta 服务\"\n - \"DOMAIN-SUFFIX,whatsapp.net,Meta 服务\"\n - \"GEOSITE,geolocation-!cn,节点选择\"\n - \"GEOSITE,cn,DIRECT\"\n - \"GEOIP,CN,DIRECT\"\n - \"MATCH,节点选择\"\n", + "contentType": "text/yaml; charset=utf-8", + "status": 200, + "userinfo": "upload=0; download=0; total=1073741824; expire=0" + }, + "raw": { + "body": "vless://#2@entry.example.com:#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=#1&sid=#8&type=tcp&headerType=none&flow=xtls-rprx-vision#N1\n", + "contentType": "text/plain; charset=utf-8", + "status": 200, + "userinfo": "upload=0; download=0; total=1073741824; expire=0" + }, + "singbox": { + "body": "{\n \"log\": {\n \"level\": \"warn\"\n },\n \"inbounds\": [\n {\n \"type\": \"mixed\",\n \"tag\": \"local\",\n \"listen\": \"127.0.0.1\",\n \"listen_port\": 2080\n }\n ],\n \"outbounds\": [\n {\n \"type\": \"selector\",\n \"tag\": \"proxy\",\n \"outbounds\": [\n \"node-1\"\n ]\n },\n {\n \"type\": \"vless\",\n \"tag\": \"node-1\",\n \"server\": \"entry.example.com\",\n \"server_port\": #2,\n \"uuid\": \"#2\",\n \"flow\": \"xtls-rprx-vision\",\n \"tls\": {\n \"enabled\": true,\n \"server_name\": \"www.amd.com\",\n \"reality\": {\n \"enabled\": true,\n \"public_key\": \"#1\",\n \"short_id\": \"#8\"\n }\n }\n },\n {\n \"type\": \"direct\",\n \"tag\": \"direct\"\n }\n ],\n \"route\": {\n \"final\": \"proxy\"\n }\n}\n", + "contentType": "application/json; charset=utf-8", + "status": 200, + "userinfo": "upload=0; download=0; total=1073741824; expire=0" + }, + "surge": { + "body": "{\"message\":\"当前没有部署成功的入口节点,请在“转发与节点”部署节点\"}", + "contentType": "application/json; charset=utf-8", + "status": 503, + "userinfo": "" + } + } + }, + "createServerS3": { + "body": { + "server": { + "createdAt": "", + "enabled": true, + "id": "#3", + "labels": [], + "name": "S3", + "portRangeEnd": 50000, + "portRangeStart": 20000, + "publicAddress": "s3.example.com", + "publicAddressV6": "", + "region": "", + "role": "relay", + "status": "pending", + "tlsDomain": "", + "updatedAt": "" + } + }, + "status": 201 + }, + "enrollS3": { + "enroll": { + "body": { + "agentId": "#2", + "agentKey": "#1", + "reconciled": 0, + "serverId": "#3", + "serverName": "S3" + }, + "status": 201 + }, + "ticket": { + "body": { + "expiresAt": "", + "token": "#1" + }, + "status": 201 + } + }, + "createCarol": { + "body": { + "customer": { + "createdAt": "", + "deviceLimit": 0, + "expiresAt": null, + "group": "", + "id": "#3", + "ipLimit": 0, + "name": "Carol", + "notes": "", + "status": "active", + "subscriptionToken": "#3", + "tags": [], + "trafficLimitBytes": 0, + "updatedAt": "", + "usedBytes": 0, + "usedDownlinkBytes": 0, + "usedUplinkBytes": 0 + } + }, + "status": 201 + }, + "createNodeN3": { + "body": { + "node": { + "artifacts": {}, + "createdAt": "", + "entryReady": false, + "error": null, + "exitPort": null, + "exitPortMode": null, + "exitProtocol": null, + "exitReady": false, + "exitResourceId": "#5", + "exitServerId": null, + "id": "#3", + "lastUsageAt": null, + "meteredTraffic": null, + "name": "N3", + "networkMode": "ipv4", + "pendingDelete": false, + "port": "#4", + "portMode": "random", + "protocol": "vless-reality", + "reality": { + "destPort": 443, + "keyId": "reality-#3", + "serverName": "www.amd.com" + }, + "realityDestPort": 443, + "realityServerName": "www.amd.com", + "resourceId": "#6", + "retiredUsageEpochs": [], + "serverId": "#3", + "sortIndex": 2, + "status": "draft", + "topology": "direct", + "updatedAt": "", + "usageCounters": null, + "wsPath": "#9" + } + }, + "status": 201 + }, + "assignN3": { + "body": { + "nodeClient": { + "createdAt": "", + "customerId": "#3", + "id": "#4", + "nodeId": "#3", + "status": "active", + "updatedAt": "" + } + }, + "status": 200 + }, + "deployN3": { + "body": { + "ok": true + }, + "status": 202 + }, + "deployN3Jobs": [ + { + "complete": { + "job": { + "action": "apply_resource", + "attempts": 1, + "completedAt": "", + "createdAt": "", + "error": null, + "id": "#4", + "leaseUntil": null, + "nodeId": "#3", + "payload": { + "keyMaterial": { + "reality-#3": { + "privateKey": "#1", + "publicKey": "#2" + } + }, + "kind": "node", + "resource": { + "engine": "sing-box", + "id": "#6", + "inbounds": [ + { + "listen": "198.18.187.63", + "listen_port": "#4", + "tag": "ng-#3-in", + "tls": { + "enabled": true, + "reality": { + "enabled": true, + "handshake": { + "server": "www.amd.com", + "server_port": "#5" + }, + "private_key": "${REALITY_PRIVATE:reality-#3}", + "short_id": [ + "#10" + ] + }, + "server_name": "www.amd.com" + }, + "type": "vless", + "users": [ + { + "name": "ng:#3", + "uuid": "#4" + } + ] + } + ], + "meta": { + "kind": "node", + "metricsTag": "ng-#3-in", + "nodeId": "#3", + "protocol": "vless-reality" + }, + "outbounds": [ + { + "tag": "ng-#3-direct", + "type": "direct" + } + ], + "routingRules": [ + { + "action": "route", + "inbound": [ + "ng-#3-in" + ], + "outbound": "ng-#3-direct" + } + ] + } + }, + "serverId": "#3", + "startedAt": "", + "status": "completed", + "updatedAt": "" + } + }, + "job": { + "action": "apply_resource", + "attempts": 1, + "createdAt": "", + "error": null, + "id": "#4", + "leaseUntil": "", + "nodeId": "#3", + "payload": { + "keyMaterial": { + "reality-#3": { + "privateKey": "#1", + "publicKey": "#2" + } + }, + "kind": "node", + "resource": { + "engine": "sing-box", + "id": "#6", + "inbounds": [ + { + "listen": "198.18.187.63", + "listen_port": "#4", + "tag": "ng-#3-in", + "tls": { + "enabled": true, + "reality": { + "enabled": true, + "handshake": { + "server": "www.amd.com", + "server_port": "#5" + }, + "private_key": "${REALITY_PRIVATE:reality-#3}", + "short_id": [ + "#10" + ] + }, + "server_name": "www.amd.com" + }, + "type": "vless", + "users": [ + { + "name": "ng:#3", + "uuid": "#4" + } + ] + } + ], + "meta": { + "kind": "node", + "metricsTag": "ng-#3-in", + "nodeId": "#3", + "protocol": "vless-reality" + }, + "outbounds": [ + { + "tag": "ng-#3-direct", + "type": "direct" + } + ], + "routingRules": [ + { + "action": "route", + "inbound": [ + "ng-#3-in" + ], + "outbound": "ng-#3-direct" + } + ] + } + }, + "serverId": "#3", + "startedAt": "", + "status": "running", + "updatedAt": "" + } + } + ], + "nodesAfterDeploy": { + "body": { + "nodeClients": [ + { + "clientUri": "vless://#1@entry.example.com:#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=#1&sid=#4&type=tcp&headerType=none&flow=xtls-rprx-vision#N1", + "createdAt": "", + "customerId": "#1", + "id": "#1", + "nodeId": "#2", + "status": "active", + "updatedAt": "" + }, + { + "clientUri": "vless://#2@entry.example.com:#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=#1&sid=#8&type=tcp&headerType=none&flow=xtls-rprx-vision#N1", + "createdAt": "", + "customerId": "#2", + "id": "#2", + "nodeId": "#2", + "status": "active", + "updatedAt": "" + }, + { + "clientUri": "ss://#1@entry.example.com:#3#N2", + "createdAt": "", + "customerId": "#1", + "id": "#3", + "nodeId": "#1", + "status": "active", + "updatedAt": "" + }, + { + "clientUri": "vless://#4@s3.example.com:#4?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=#2&sid=#10&type=tcp&headerType=none#N3", + "createdAt": "", + "customerId": "#3", + "id": "#4", + "nodeId": "#3", + "status": "active", + "updatedAt": "" + } + ], + "nodes": [ + { + "artifacts": { + "realityPublicKey": "#1" + }, + "autoRemoving": false, + "clientCount": 2, + "createdAt": "", + "entryReady": true, + "error": null, + "exitPort": "#1", + "exitPortMode": "random", + "exitProtocol": "shadowsocks-2022-aes256", + "exitReady": true, + "exitResourceId": "#1", + "exitServerId": "#2", + "id": "#2", + "lastUsageAt": "", + "meteredTraffic": { + "downlink": 5000, + "uplink": 4000 + }, + "name": "N1", + "networkMode": "ipv4", + "onlineCount": 0, + "onlineCustomerIds": [], + "pendingDelete": false, + "port": "#2", + "portMode": "random", + "protocol": "vless-reality-vision", + "reality": { + "destPort": 443, + "keyId": "reality-#2", + "serverName": "www.amd.com" + }, + "realityDestPort": 443, + "realityServerName": "www.amd.com", + "resourceId": "#2", + "retiredUsageEpochs": [], + "serverId": "#1", + "sortIndex": 0, + "speedDownBps": 0, + "speedUpBps": 0, + "status": "active", + "topology": "forward", + "updatedAt": "", + "usageCounters": { + "downlink": 5000, + "epoch": "diff-epoch-1", + "uplink": 4000 + }, + "wsPath": "#1" + }, + { + "artifacts": {}, + "autoRemoving": false, + "clientCount": 1, + "createdAt": "", + "entryReady": true, + "error": null, + "exitPort": null, + "exitPortMode": null, + "exitProtocol": null, + "exitReady": false, + "exitResourceId": "#3", + "exitServerId": null, + "id": "#1", + "lastUsageAt": null, + "meteredTraffic": null, + "name": "N2", + "networkMode": "ipv4", + "onlineCount": 0, + "onlineCustomerIds": [], + "pendingDelete": false, + "port": "#3", + "portMode": "random", + "protocol": "shadowsocks-2022-aes128", + "realityDestPort": 443, + "realityServerName": "www.tesla.com", + "resourceId": "#4", + "retiredUsageEpochs": [], + "serverId": "#1", + "sortIndex": 1, + "speedDownBps": 0, + "speedUpBps": 0, + "status": "active", + "topology": "direct", + "updatedAt": "", + "usageCounters": null, + "wsPath": "#2" + }, + { + "artifacts": { + "realityPublicKey": "#2" + }, + "autoRemoving": false, + "clientCount": 1, + "createdAt": "", + "entryReady": true, + "error": null, + "exitPort": null, + "exitPortMode": null, + "exitProtocol": null, + "exitReady": false, + "exitResourceId": "#5", + "exitServerId": null, + "id": "#3", + "lastUsageAt": null, + "meteredTraffic": null, + "name": "N3", + "networkMode": "ipv4", + "onlineCount": 0, + "onlineCustomerIds": [], + "pendingDelete": false, + "port": "#4", + "portMode": "random", + "protocol": "vless-reality", + "reality": { + "destPort": 443, + "keyId": "reality-#3", + "serverName": "www.amd.com" + }, + "realityDestPort": 443, + "realityServerName": "www.amd.com", + "resourceId": "#6", + "retiredUsageEpochs": [], + "serverId": "#3", + "sortIndex": 2, + "speedDownBps": 0, + "speedUpBps": 0, + "status": "active", + "topology": "direct", + "updatedAt": "", + "usageCounters": null, + "wsPath": "#9" + } + ] + }, + "status": 200 + }, + "carolSubscriptions": { + "auto-clash": { + "body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N3\"\n server: \"s3.example.com\"\n port: #4\n type: \"vless\"\n uuid: \"#4\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"#2\"\n short-id: \"#10\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"N3\"\nrules:\n - \"MATCH,节点选择\"\n", + "contentType": "text/yaml; charset=utf-8", + "status": 200, + "userinfo": "upload=0; download=0; total=0; expire=0" + }, + "auto-generic": { + "body": "vless://#4@s3.example.com:#4?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=#2&sid=#10&type=tcp&headerType=none#N3\n", + "contentType": "text/plain; charset=utf-8", + "status": 200, + "userinfo": "upload=0; download=0; total=0; expire=0" + }, + "base64": { + "body": "vless://#4@s3.example.com:#4?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=#2&sid=#10&type=tcp&headerType=none#N3\n", + "contentType": "text/plain; charset=utf-8", + "status": 200, + "userinfo": "upload=0; download=0; total=0; expire=0" + }, + "clash": { + "body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N3\"\n server: \"s3.example.com\"\n port: #4\n type: \"vless\"\n uuid: \"#4\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"#2\"\n short-id: \"#10\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"N3\"\nrules:\n - \"MATCH,节点选择\"\n", + "contentType": "text/yaml; charset=utf-8", + "status": 200, + "userinfo": "upload=0; download=0; total=0; expire=0" + }, + "clash-smart": { + "body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N3\"\n server: \"s3.example.com\"\n port: #4\n type: \"vless\"\n uuid: \"#4\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"#2\"\n short-id: \"#10\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"自动选择\"\n - \"N3\"\n - \"DIRECT\"\n - name: \"自动选择\"\n type: url-test\n url: \"https://www.gstatic.com/generate_204\"\n interval: 300\n tolerance: 80\n proxies:\n - \"N3\"\n - name: \"流媒体\"\n type: select\n proxies:\n - \"节点选择\"\n - \"自动选择\"\n - \"N3\"\n - name: \"AI 服务\"\n type: select\n proxies:\n - \"节点选择\"\n - \"自动选择\"\n - \"N3\"\n - name: \"Meta 服务\"\n type: select\n proxies:\n - \"节点选择\"\n - \"自动选择\"\n - \"N3\"\nrules:\n - \"GEOSITE,category-ads-all,REJECT\"\n - \"GEOSITE,private,DIRECT\"\n - \"GEOSITE,openai,AI 服务\"\n - \"GEOSITE,netflix,流媒体\"\n - \"GEOSITE,facebook,Meta 服务\"\n - \"DOMAIN-SUFFIX,fbcdn.net,Meta 服务\"\n - \"DOMAIN-SUFFIX,instagram.com,Meta 服务\"\n - \"DOMAIN-SUFFIX,whatsapp.net,Meta 服务\"\n - \"GEOSITE,geolocation-!cn,节点选择\"\n - \"GEOSITE,cn,DIRECT\"\n - \"GEOIP,CN,DIRECT\"\n - \"MATCH,节点选择\"\n", + "contentType": "text/yaml; charset=utf-8", + "status": 200, + "userinfo": "upload=0; download=0; total=0; expire=0" + }, + "raw": { + "body": "vless://#4@s3.example.com:#4?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=#2&sid=#10&type=tcp&headerType=none#N3\n", + "contentType": "text/plain; charset=utf-8", + "status": 200, + "userinfo": "upload=0; download=0; total=0; expire=0" + }, + "singbox": { + "body": "{\n \"log\": {\n \"level\": \"warn\"\n },\n \"inbounds\": [\n {\n \"type\": \"mixed\",\n \"tag\": \"local\",\n \"listen\": \"127.0.0.1\",\n \"listen_port\": 2080\n }\n ],\n \"outbounds\": [\n {\n \"type\": \"selector\",\n \"tag\": \"proxy\",\n \"outbounds\": [\n \"node-1\"\n ]\n },\n {\n \"type\": \"vless\",\n \"tag\": \"node-1\",\n \"server\": \"s3.example.com\",\n \"server_port\": #4,\n \"uuid\": \"#4\",\n \"tls\": {\n \"enabled\": true,\n \"server_name\": \"www.amd.com\",\n \"reality\": {\n \"enabled\": true,\n \"public_key\": \"#2\",\n \"short_id\": \"#10\"\n }\n }\n },\n {\n \"type\": \"direct\",\n \"tag\": \"direct\"\n }\n ],\n \"route\": {\n \"final\": \"proxy\"\n }\n}\n", + "contentType": "application/json; charset=utf-8", + "status": 200, + "userinfo": "upload=0; download=0; total=0; expire=0" + }, + "surge": { + "body": "{\"message\":\"当前没有部署成功的入口节点,请在“转发与节点”部署节点\"}", + "contentType": "application/json; charset=utf-8", + "status": 503, + "userinfo": "" + } + }, + "usageReport1": { + "body": { + "ok": true + }, + "status": 200 + }, + "usageReport2": { + "body": { + "ok": true + }, + "status": 200 + }, + "presenceProjection": { + "customers": [ + { + "name": "Alice", + "online": true, + "speedPositive": true, + "hasLastActiveAt": true + }, + { + "name": "Bob", + "online": false, + "speedPositive": false, + "hasLastActiveAt": false + }, + { + "name": "Carol", + "online": false, + "speedPositive": false, + "hasLastActiveAt": false + } + ], + "nodes": [ + { + "name": "N1", + "clientCount": 2, + "onlineCount": 1, + "onlineNames": [ + "Alice" + ], + "speedPositive": true + }, + { + "name": "N2", + "clientCount": 1, + "onlineCount": 1, + "onlineNames": [ + "Alice" + ], + "speedPositive": false + }, + { + "name": "N3", + "clientCount": 1, + "onlineCount": 0, + "onlineNames": [], + "speedPositive": false + } + ] + }, + "customersAfterUsage": { + "body": { + "customers": [ + { + "createdAt": "", + "deviceLimit": 0, + "expiresAt": null, + "group": "", + "id": "#1", + "ipLimit": 0, + "lastActiveAt": "", + "lastOnlineAt": "", + "lastUsageAt": "", + "name": "Alice", + "notes": "", + "observedIpCount": 0, + "online": true, + "serverUsage": { + "srv:#1": { + "lastActiveAt": "", + "lastSampleAt": "", + "retiredUsageEpochs": [ + "diff-epoch-1" + ], + "speedAt": "", + "speedDownBps": "", + "speedUpBps": "", + "usageCounters": { + "downlink": 6300, + "epoch": "golden-epoch-1", + "uplink": 4500 + } + } + }, + "speedDownBps": "", + "speedUpBps": "", + "status": "active", + "subscriptionClientCount": 3, + "subscriptionToken": "#1", + "tags": [], + "trafficLimitBytes": 0, + "updatedAt": "", + "usedBytes": 13800, + "usedDownlinkBytes": 8300, + "usedUplinkBytes": 5500 + }, + { + "createdAt": "", + "deviceLimit": 2, + "expiresAt": null, + "group": "", + "id": "#2", + "ipLimit": 5, + "lastActiveAt": null, + "name": "Bob", + "notes": "", + "observedIpCount": 0, + "online": false, + "speedDownBps": 0, + "speedUpBps": 0, + "status": "active", + "subscriptionClientCount": 2, + "subscriptionToken": "#2", + "tags": [], + "trafficLimitBytes": 1073741824, + "updatedAt": "", + "usedBytes": 0, + "usedDownlinkBytes": 0, + "usedUplinkBytes": 0 + }, + { + "createdAt": "", + "deviceLimit": 0, + "expiresAt": null, + "group": "", + "id": "#3", + "ipLimit": 0, + "lastActiveAt": null, + "name": "Carol", + "notes": "", + "observedIpCount": 0, + "online": false, + "speedDownBps": 0, + "speedUpBps": 0, + "status": "active", + "subscriptionClientCount": 3, + "subscriptionToken": "#3", + "tags": [], + "trafficLimitBytes": 0, + "updatedAt": "", + "usedBytes": 0, + "usedDownlinkBytes": 0, + "usedUplinkBytes": 0 + } + ] + }, + "status": 200 + }, + "nodePatchUiStyle": { + "body": { + "node": { + "artifacts": { + "realityPublicKey": "#1" + }, + "autoRemoving": false, + "createdAt": "", + "entryReady": true, + "error": null, + "exitPort": "#6", + "exitPortMode": "random", + "exitProtocol": "shadowsocks-2022-aes256", + "exitReady": true, + "exitResourceId": "#1", + "exitServerId": "#2", + "id": "#2", + "lastActiveAt": "", + "lastSampleAt": "", + "lastUsageAt": "", + "meteredTraffic": { + "downlink": 14000, + "uplink": 12000 + }, + "name": "N1-renamed", + "networkMode": "ipv4", + "pendingDelete": false, + "port": "#7", + "portMode": "random", + "protocol": "vless-reality-vision", + "reality": { + "destPort": 443, + "keyId": "reality-#2", + "serverName": "www.amd.com" + }, + "realityDestPort": 443, + "realityServerName": "www.amd.com", + "resourceId": "#2", + "retiredUsageEpochs": [ + "diff-epoch-1" + ], + "serverId": "#1", + "sortIndex": 0, + "speedAt": "", + "speedDownBps": "", + "speedUpBps": "", + "status": "deploying", + "topology": "forward", + "updatedAt": "", + "usageCounters": { + "downlink": 9000, + "epoch": "golden-epoch-1", + "uplink": 8000 + }, + "wsPath": "#1" + } + }, + "status": 200 + }, + "nodePatchJobs": { + "entry": [ + { + "complete": { + "job": { + "action": "apply_resource", + "attempts": 1, + "completedAt": "", + "createdAt": "", + "error": null, + "id": "#5", + "leaseUntil": null, + "nodeId": "#2", + "payload": { + "keyMaterial": { + "reality-#2": { + "privateKey": "#2", + "publicKey": "#1" + } + }, + "kind": "node", + "resource": { + "engine": "sing-box", + "id": "#2", + "inbounds": [ + { + "listen": "198.18.187.62", + "listen_port": "#7", + "tag": "ng-#2-in", + "tls": { + "enabled": true, + "reality": { + "enabled": true, + "handshake": { + "server": "www.amd.com", + "server_port": "#5" + }, + "private_key": "${REALITY_PRIVATE:reality-#2}", + "short_id": [ + "#4", + "#8" + ] + }, + "server_name": "www.amd.com" + }, + "type": "vless", + "users": [ + { + "flow": "xtls-rprx-vision", + "name": "ng:#1", + "uuid": "#1" + }, + { + "flow": "xtls-rprx-vision", + "name": "ng:#2", + "uuid": "#2" + } + ] + } + ], + "meta": { + "kind": "node", + "metricsTag": "ng-#2-in", + "nodeId": "#2", + "protocol": "vless-reality-vision" + }, + "outbounds": [ + { + "method": "2022-blake3-aes-256-gcm", + "password": "#11", + "server": "exit.example.com", + "server_port": "#6", + "tag": "ng-#2-exit", + "type": "shadowsocks" + } + ], + "routingRules": [ + { + "action": "route", + "inbound": [ + "ng-#2-in" + ], + "outbound": "ng-#2-exit" + } + ] + } + }, + "serverId": "#1", + "startedAt": "", + "status": "completed", + "updatedAt": "" + } + }, + "job": { + "action": "apply_resource", + "attempts": 1, + "createdAt": "", + "error": null, + "id": "#5", + "leaseUntil": "", + "nodeId": "#2", + "payload": { + "keyMaterial": { + "reality-#2": { + "privateKey": "#2", + "publicKey": "#1" + } + }, + "kind": "node", + "resource": { + "engine": "sing-box", + "id": "#2", + "inbounds": [ + { + "listen": "198.18.187.62", + "listen_port": "#7", + "tag": "ng-#2-in", + "tls": { + "enabled": true, + "reality": { + "enabled": true, + "handshake": { + "server": "www.amd.com", + "server_port": "#5" + }, + "private_key": "${REALITY_PRIVATE:reality-#2}", + "short_id": [ + "#4", + "#8" + ] + }, + "server_name": "www.amd.com" + }, + "type": "vless", + "users": [ + { + "flow": "xtls-rprx-vision", + "name": "ng:#1", + "uuid": "#1" + }, + { + "flow": "xtls-rprx-vision", + "name": "ng:#2", + "uuid": "#2" + } + ] + } + ], + "meta": { + "kind": "node", + "metricsTag": "ng-#2-in", + "nodeId": "#2", + "protocol": "vless-reality-vision" + }, + "outbounds": [ + { + "method": "2022-blake3-aes-256-gcm", + "password": "#11", + "server": "exit.example.com", + "server_port": "#6", + "tag": "ng-#2-exit", + "type": "shadowsocks" + } + ], + "routingRules": [ + { + "action": "route", + "inbound": [ + "ng-#2-in" + ], + "outbound": "ng-#2-exit" + } + ] + } + }, + "serverId": "#1", + "startedAt": "", + "status": "running", + "updatedAt": "" + } + } + ], + "exit": [ + { + "complete": { + "job": { + "action": "apply_resource", + "attempts": 1, + "completedAt": "", + "createdAt": "", + "error": null, + "id": "#6", + "leaseUntil": null, + "nodeId": "#2", + "payload": { + "kind": "node-exit", + "resource": { + "engine": "sing-box", + "id": "#1", + "inbounds": [ + { + "listen": "198.18.187.61", + "listen_port": "#6", + "method": "2022-blake3-aes-256-gcm", + "password": "#11", + "tag": "ng-#2-exit-in", + "type": "shadowsocks" + } + ], + "meta": { + "kind": "node-exit", + "metricsTag": "ng-#2-exit-in", + "nodeId": "#2" + }, + "outbounds": [ + { + "tag": "ng-#2-exit-direct", + "type": "direct" + } + ], + "routingRules": [ + { + "action": "route", + "inbound": [ + "ng-#2-exit-in" + ], + "outbound": "ng-#2-exit-direct" + } + ] + } + }, + "serverId": "#2", + "startedAt": "", + "status": "completed", + "updatedAt": "" + } + }, + "job": { + "action": "apply_resource", + "attempts": 1, + "createdAt": "", + "error": null, + "id": "#6", + "leaseUntil": "", + "nodeId": "#2", + "payload": { + "kind": "node-exit", + "resource": { + "engine": "sing-box", + "id": "#1", + "inbounds": [ + { + "listen": "198.18.187.61", + "listen_port": "#6", + "method": "2022-blake3-aes-256-gcm", + "password": "#11", + "tag": "ng-#2-exit-in", + "type": "shadowsocks" + } + ], + "meta": { + "kind": "node-exit", + "metricsTag": "ng-#2-exit-in", + "nodeId": "#2" + }, + "outbounds": [ + { + "tag": "ng-#2-exit-direct", + "type": "direct" + } + ], + "routingRules": [ + { + "action": "route", + "inbound": [ + "ng-#2-exit-in" + ], + "outbound": "ng-#2-exit-direct" + } + ] + } + }, + "serverId": "#2", + "startedAt": "", + "status": "running", + "updatedAt": "" + } + } + ] + }, + "nodesAfterPatch": { + "body": { + "nodeClients": [ + { + "clientUri": "vless://#1@entry.example.com:#7?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=#1&sid=#4&type=tcp&headerType=none&flow=xtls-rprx-vision#N1-renamed", + "createdAt": "", + "customerId": "#1", + "id": "#1", + "nodeId": "#2", + "status": "active", + "updatedAt": "" + }, + { + "clientUri": "vless://#2@entry.example.com:#7?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=#1&sid=#8&type=tcp&headerType=none&flow=xtls-rprx-vision#N1-renamed", + "createdAt": "", + "customerId": "#2", + "id": "#2", + "nodeId": "#2", + "status": "active", + "updatedAt": "" + }, + { + "clientUri": "ss://#1@entry.example.com:#3#N2", + "createdAt": "", + "customerId": "#1", + "id": "#3", + "nodeId": "#1", + "status": "active", + "updatedAt": "" + }, + { + "clientUri": "vless://#4@s3.example.com:#4?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=#2&sid=#10&type=tcp&headerType=none#N3", + "createdAt": "", + "customerId": "#3", + "id": "#4", + "nodeId": "#3", + "status": "active", + "updatedAt": "" + } + ], + "nodes": [ + { + "artifacts": { + "realityPublicKey": "#1" + }, + "autoRemoving": false, + "clientCount": 2, + "createdAt": "", + "entryReady": true, + "error": null, + "exitPort": "#6", + "exitPortMode": "random", + "exitProtocol": "shadowsocks-2022-aes256", + "exitReady": true, + "exitResourceId": "#1", + "exitServerId": "#2", + "id": "#2", + "lastActiveAt": "", + "lastSampleAt": "", + "lastUsageAt": "", + "meteredTraffic": { + "downlink": 14000, + "uplink": 12000 + }, + "name": "N1-renamed", + "networkMode": "ipv4", + "onlineCount": 1, + "onlineCustomerIds": [ + "#1" + ], + "pendingDelete": false, + "port": "#7", + "portMode": "random", + "protocol": "vless-reality-vision", + "reality": { + "destPort": 443, + "keyId": "reality-#2", + "serverName": "www.amd.com" + }, + "realityDestPort": 443, + "realityServerName": "www.amd.com", + "resourceId": "#2", + "retiredUsageEpochs": [ + "diff-epoch-1" + ], + "serverId": "#1", + "sortIndex": 0, + "speedAt": "", + "speedDownBps": "", + "speedUpBps": "", + "status": "active", + "topology": "forward", + "updatedAt": "", + "usageCounters": { + "downlink": 9000, + "epoch": "golden-epoch-1", + "uplink": 8000 + }, + "wsPath": "#1" + }, + { + "artifacts": {}, + "autoRemoving": false, + "clientCount": 1, + "createdAt": "", + "entryReady": true, + "error": null, + "exitPort": null, + "exitPortMode": null, + "exitProtocol": null, + "exitReady": false, + "exitResourceId": "#3", + "exitServerId": null, + "id": "#1", + "lastUsageAt": null, + "meteredTraffic": null, + "name": "N2", + "networkMode": "ipv4", + "onlineCount": 1, + "onlineCustomerIds": [ + "#1" + ], + "pendingDelete": false, + "port": "#3", + "portMode": "random", + "protocol": "shadowsocks-2022-aes128", + "realityDestPort": 443, + "realityServerName": "www.tesla.com", + "resourceId": "#4", + "retiredUsageEpochs": [], + "serverId": "#1", + "sortIndex": 1, + "speedDownBps": 0, + "speedUpBps": 0, + "status": "active", + "topology": "direct", + "updatedAt": "", + "usageCounters": null, + "wsPath": "#2" + }, + { + "artifacts": { + "realityPublicKey": "#2" + }, + "autoRemoving": false, + "clientCount": 1, + "createdAt": "", + "entryReady": true, + "error": null, + "exitPort": null, + "exitPortMode": null, + "exitProtocol": null, + "exitReady": false, + "exitResourceId": "#5", + "exitServerId": null, + "id": "#3", + "lastUsageAt": null, + "meteredTraffic": null, + "name": "N3", + "networkMode": "ipv4", + "onlineCount": 0, + "onlineCustomerIds": [], + "pendingDelete": false, + "port": "#4", + "portMode": "random", + "protocol": "vless-reality", + "reality": { + "destPort": 443, + "keyId": "reality-#3", + "serverName": "www.amd.com" + }, + "realityDestPort": 443, + "realityServerName": "www.amd.com", + "resourceId": "#6", + "retiredUsageEpochs": [], + "serverId": "#3", + "sortIndex": 2, + "speedDownBps": 0, + "speedUpBps": 0, + "status": "active", + "topology": "direct", + "updatedAt": "", + "usageCounters": null, + "wsPath": "#9" + } + ] + }, + "status": 200 + }, + "systemShape": { + "status": 200, + "body": { + "history": [ + { + "at": "", + "connections": 1, + "cpu": null, + "downBps": "", + "mem": "", + "upBps": "" + } + ], + "host": { + "cpuCount": "", + "cpuPercent": null, + "diskPercent": "", + "diskTotal": "", + "diskUsed": "", + "load1": "", + "memPercent": "", + "memTotal": "", + "memUsed": "" + }, + "hostUptimeSec": "", + "ips": { + "ipv4": "", + "ipv6": null + }, + "network": { + "connections": 1, + "customersTotal": 3, + "downBps": "", + "nodesTotal": 3, + "onlineCustomers": 1, + "onlineNodes": 3, + "upBps": "" + }, + "panelMemBytes": "", + "panelUptimeSec": "", + "startedAt": "", + "today": { + "downBytes": "", + "upBytes": "" + }, + "totals": { + "downBytes": "", + "upBytes": "" + }, + "version": "0.6.8" + } + }, + "backup": { + "body": { + "activity": [ + { + "action": "update_node", + "actor": "admin", + "at": "", + "detail": { + "name": "N1-renamed" + }, + "id": "#13", + "target": "#2" + }, + { + "action": "deploy_node", + "actor": "admin", + "at": "", + "detail": { + "name": "N3" + }, + "id": "#14", + "target": "#3" + }, + { + "action": "assign_customer", + "actor": "admin", + "at": "", + "detail": { + "customer": "Carol", + "customerId": "#3" + }, + "id": "#15", + "target": "#3" + }, + { + "action": "create_node", + "actor": "admin", + "at": "", + "detail": { + "name": "N3" + }, + "id": "#16", + "target": "#3" + }, + { + "action": "create_customer", + "actor": "admin", + "at": "", + "detail": { + "name": "Carol" + }, + "id": "#17", + "target": "#3" + }, + { + "action": "reconcile_server", + "actor": "agent:#2", + "at": "", + "detail": { + "nodes": 0 + }, + "id": "#18", + "target": "#3" + }, + { + "action": "enroll_agent", + "actor": "agent:#2", + "at": "", + "detail": {}, + "id": "#19", + "target": "#3" + }, + { + "action": "create_enrollment", + "actor": "admin", + "at": "", + "detail": {}, + "id": "#20", + "target": "#3" + }, + { + "action": "create_server", + "actor": "admin", + "at": "", + "detail": { + "name": "S3" + }, + "id": "#21", + "target": "#3" + }, + { + "action": "deploy_node", + "actor": "admin", + "at": "", + "detail": { + "name": "N2" + }, + "id": "#1", + "target": "#1" + }, + { + "action": "deploy_node", + "actor": "admin", + "at": "", + "detail": { + "name": "N1" + }, + "id": "#2", + "target": "#2" + }, + { + "action": "assign_customer", + "actor": "admin", + "at": "", + "detail": { + "customer": "Alice", + "customerId": "#1" + }, + "id": "#3", + "target": "#1" + }, + { + "action": "assign_customer", + "actor": "admin", + "at": "", + "detail": { + "customer": "Bob", + "customerId": "#2" + }, + "id": "#4", + "target": "#2" + }, + { + "action": "assign_customer", + "actor": "admin", + "at": "", + "detail": { + "customer": "Alice", + "customerId": "#1" + }, + "id": "#5", + "target": "#2" + }, + { + "action": "create_node", + "actor": "admin", + "at": "", + "detail": { + "name": "N2" + }, + "id": "#6", + "target": "#1" + }, + { + "action": "create_node", + "actor": "admin", + "at": "", + "detail": { + "name": "N1" + }, + "id": "#7", + "target": "#2" + }, + { + "action": "create_customer", + "actor": "admin", + "at": "", + "detail": { + "name": "Bob" + }, + "id": "#8", + "target": "#2" + }, + { + "action": "create_customer", + "actor": "admin", + "at": "", + "detail": { + "name": "Alice" + }, + "id": "#9", + "target": "#1" + }, + { + "action": "reconcile_server", + "actor": "agent:#1", + "at": "", + "detail": { + "nodes": 0 + }, + "id": "#10", + "target": "#2" + }, + { + "action": "enroll_agent", + "actor": "agent:#1", + "at": "", + "detail": {}, + "id": "#11", + "target": "#2" + }, + { + "action": "create_enrollment", + "actor": "admin", + "at": "", + "detail": {}, + "id": "#12", + "target": "#2" + }, + { + "action": "reconcile_server", + "actor": "agent:#3", + "at": "", + "detail": { + "nodes": 0 + }, + "id": "#22", + "target": "#1" + }, + { + "action": "enroll_agent", + "actor": "agent:#3", + "at": "", + "detail": {}, + "id": "#23", + "target": "#1" + }, + { + "action": "create_enrollment", + "actor": "admin", + "at": "", + "detail": {}, + "id": "#24", + "target": "#1" + }, + { + "action": "create_server", + "actor": "admin", + "at": "", + "detail": { + "name": "Exit" + }, + "id": "#25", + "target": "#2" + }, + { + "action": "create_server", + "actor": "admin", + "at": "", + "detail": { + "name": "Entry" + }, + "id": "#26", + "target": "#1" + } + ], + "agents": [ + { + "createdAt": "", + "hostname": "diff-agent-entry", + "id": "#3", + "keyFingerprint": "#1", + "keyHash": "#1", + "lastSeenAt": "", + "serverId": "#1", + "status": "active", + "version": "0.6.8" + }, + { + "createdAt": "", + "hostname": "diff-agent-exit", + "id": "#1", + "keyFingerprint": "#2", + "keyHash": "#3", + "lastSeenAt": "", + "serverId": "#2", + "status": "active", + "version": "0.6.8" + }, + { + "createdAt": "", + "hostname": "golden-agent-s3", + "id": "#2", + "keyFingerprint": "#3", + "keyHash": "#5", + "lastSeenAt": "", + "serverId": "#3", + "status": "active", + "version": "0.6.8" + } + ], + "createdAt": "", + "customers": [ + { + "createdAt": "", + "deviceLimit": 0, + "expiresAt": null, + "group": "", + "id": "#1", + "ipLimit": 0, + "lastOnlineAt": "", + "lastUsageAt": "", + "name": "Alice", + "notes": "", + "serverUsage": { + "srv:#1": { + "lastActiveAt": "", + "lastSampleAt": "", + "retiredUsageEpochs": [ + "diff-epoch-1" + ], + "speedAt": "", + "speedDownBps": "", + "speedUpBps": "", + "usageCounters": { + "downlink": 6300, + "epoch": "golden-epoch-1", + "uplink": 4500 + } + } + }, + "status": "active", + "subscriptionToken": "#1", + "tags": [], + "trafficLimitBytes": 0, + "updatedAt": "", + "usedBytes": 13800, + "usedDownlinkBytes": 8300, + "usedUplinkBytes": 5500 + }, + { + "createdAt": "", + "deviceLimit": 2, + "expiresAt": null, + "group": "", + "id": "#2", + "ipLimit": 5, + "name": "Bob", + "notes": "", + "status": "active", + "subscriptionToken": "#2", + "tags": [], + "trafficLimitBytes": 1073741824, + "updatedAt": "", + "usedBytes": 0, + "usedDownlinkBytes": 0, + "usedUplinkBytes": 0 + }, + { + "createdAt": "", + "deviceLimit": 0, + "expiresAt": null, + "group": "", + "id": "#3", + "ipLimit": 0, + "name": "Carol", + "notes": "", + "status": "active", + "subscriptionToken": "#3", + "tags": [], + "trafficLimitBytes": 0, + "updatedAt": "", + "usedBytes": 0, + "usedDownlinkBytes": 0, + "usedUplinkBytes": 0 + } + ], + "enrollmentTokens": [ + { + "createdAt": "", + "expiresAt": "", + "id": "#1", + "serverId": "#1", + "tokenHash": "#7", + "usedAt": "" + }, + { + "createdAt": "", + "expiresAt": "", + "id": "#2", + "serverId": "#2", + "tokenHash": "#9", + "usedAt": "" + }, + { + "createdAt": "", + "expiresAt": "", + "id": "#3", + "serverId": "#3", + "tokenHash": "#11", + "usedAt": "" + } + ], + "jobs": [ + { + "action": "apply_resource", + "attempts": 1, + "completedAt": "", + "createdAt": "", + "error": null, + "id": "#3", + "leaseUntil": null, + "nodeId": "#2", + "payload": { + "keyMaterial": { + "reality-#2": { + "privateKey": "#2", + "publicKey": "#1" + } + }, + "kind": "node", + "resource": { + "engine": "sing-box", + "id": "#2", + "inbounds": [ + { + "listen": "198.18.60.35", + "listen_port": "#2", + "tag": "ng-#2-in", + "tls": { + "enabled": true, + "reality": { + "enabled": true, + "handshake": { + "server": "www.amd.com", + "server_port": "#5" + }, + "private_key": "${REALITY_PRIVATE:reality-#2}", + "short_id": [ + "#4", + "#8" + ] + }, + "server_name": "www.amd.com" + }, + "type": "vless", + "users": [ + { + "flow": "xtls-rprx-vision", + "name": "ng:#1", + "uuid": "#1" + }, + { + "flow": "xtls-rprx-vision", + "name": "ng:#2", + "uuid": "#2" + } + ] + } + ], + "meta": { + "kind": "node", + "metricsTag": "ng-#2-in", + "nodeId": "#2", + "protocol": "vless-reality-vision" + }, + "outbounds": [ + { + "method": "2022-blake3-aes-256-gcm", + "password": "#11", + "server": "exit.example.com", + "server_port": "#1", + "tag": "ng-#2-exit", + "type": "shadowsocks" + } + ], + "routingRules": [ + { + "action": "route", + "inbound": [ + "ng-#2-in" + ], + "outbound": "ng-#2-exit" + } + ] + } + }, + "serverId": "#1", + "startedAt": "", + "status": "completed", + "updatedAt": "" + }, + { + "action": "apply_resource", + "attempts": 1, + "completedAt": "", + "createdAt": "", + "error": null, + "id": "#2", + "leaseUntil": null, + "nodeId": "#2", + "payload": { + "kind": "node-exit", + "resource": { + "engine": "sing-box", + "id": "#1", + "inbounds": [ + { + "listen": "198.18.60.37", + "listen_port": "#1", + "method": "2022-blake3-aes-256-gcm", + "password": "#11", + "tag": "ng-#2-exit-in", + "type": "shadowsocks" + } + ], + "meta": { + "kind": "node-exit", + "metricsTag": "ng-#2-exit-in", + "nodeId": "#2" + }, + "outbounds": [ + { + "tag": "ng-#2-exit-direct", + "type": "direct" + } + ], + "routingRules": [ + { + "action": "route", + "inbound": [ + "ng-#2-exit-in" + ], + "outbound": "ng-#2-exit-direct" + } + ] + } + }, + "serverId": "#2", + "startedAt": "", + "status": "completed", + "updatedAt": "" + }, + { + "action": "apply_resource", + "attempts": 1, + "completedAt": "", + "createdAt": "", + "error": null, + "id": "#1", + "leaseUntil": null, + "nodeId": "#1", + "payload": { + "kind": "node", + "resource": { + "engine": "sing-box", + "id": "#4", + "inbounds": [ + { + "listen": "198.18.60.35", + "listen_port": "#3", + "method": "2022-blake3-aes-128-gcm", + "password": "#12", + "tag": "ng-#1-in", + "type": "shadowsocks", + "users": [ + { + "name": "ng:#1", + "password": "#5" + } + ] + } + ], + "meta": { + "kind": "node", + "metricsTag": "ng-#1-in", + "nodeId": "#1", + "protocol": "shadowsocks-2022-aes128" + }, + "outbounds": [ + { + "tag": "ng-#1-direct", + "type": "direct" + } + ], + "routingRules": [ + { + "action": "route", + "inbound": [ + "ng-#1-in" + ], + "outbound": "ng-#1-direct" + } + ] + } + }, + "serverId": "#1", + "startedAt": "", + "status": "completed", + "updatedAt": "" + }, + { + "action": "apply_resource", + "attempts": 1, + "completedAt": "", + "createdAt": "", + "error": null, + "id": "#4", + "leaseUntil": null, + "nodeId": "#3", + "payload": { + "keyMaterial": { + "reality-#3": { + "privateKey": "#1", + "publicKey": "#2" + } + }, + "kind": "node", + "resource": { + "engine": "sing-box", + "id": "#6", + "inbounds": [ + { + "listen": "198.18.187.63", + "listen_port": "#4", + "tag": "ng-#3-in", + "tls": { + "enabled": true, + "reality": { + "enabled": true, + "handshake": { + "server": "www.amd.com", + "server_port": "#5" + }, + "private_key": "${REALITY_PRIVATE:reality-#3}", + "short_id": [ + "#10" + ] + }, + "server_name": "www.amd.com" + }, + "type": "vless", + "users": [ + { + "name": "ng:#3", + "uuid": "#4" + } + ] + } + ], + "meta": { + "kind": "node", + "metricsTag": "ng-#3-in", + "nodeId": "#3", + "protocol": "vless-reality" + }, + "outbounds": [ + { + "tag": "ng-#3-direct", + "type": "direct" + } + ], + "routingRules": [ + { + "action": "route", + "inbound": [ + "ng-#3-in" + ], + "outbound": "ng-#3-direct" + } + ] + } + }, + "serverId": "#3", + "startedAt": "", + "status": "completed", + "updatedAt": "" + }, + { + "action": "apply_resource", + "attempts": 1, + "completedAt": "", + "createdAt": "", + "error": null, + "id": "#5", + "leaseUntil": null, + "nodeId": "#2", + "payload": { + "keyMaterial": { + "reality-#2": { + "privateKey": "#2", + "publicKey": "#1" + } + }, + "kind": "node", + "resource": { + "engine": "sing-box", + "id": "#2", + "inbounds": [ + { + "listen": "198.18.187.62", + "listen_port": "#7", + "tag": "ng-#2-in", + "tls": { + "enabled": true, + "reality": { + "enabled": true, + "handshake": { + "server": "www.amd.com", + "server_port": "#5" + }, + "private_key": "${REALITY_PRIVATE:reality-#2}", + "short_id": [ + "#4", + "#8" + ] + }, + "server_name": "www.amd.com" + }, + "type": "vless", + "users": [ + { + "flow": "xtls-rprx-vision", + "name": "ng:#1", + "uuid": "#1" + }, + { + "flow": "xtls-rprx-vision", + "name": "ng:#2", + "uuid": "#2" + } + ] + } + ], + "meta": { + "kind": "node", + "metricsTag": "ng-#2-in", + "nodeId": "#2", + "protocol": "vless-reality-vision" + }, + "outbounds": [ + { + "method": "2022-blake3-aes-256-gcm", + "password": "#11", + "server": "exit.example.com", + "server_port": "#6", + "tag": "ng-#2-exit", + "type": "shadowsocks" + } + ], + "routingRules": [ + { + "action": "route", + "inbound": [ + "ng-#2-in" + ], + "outbound": "ng-#2-exit" + } + ] + } + }, + "serverId": "#1", + "startedAt": "", + "status": "completed", + "updatedAt": "" + }, + { + "action": "apply_resource", + "attempts": 1, + "completedAt": "", + "createdAt": "", + "error": null, + "id": "#6", + "leaseUntil": null, + "nodeId": "#2", + "payload": { + "kind": "node-exit", + "resource": { + "engine": "sing-box", + "id": "#1", + "inbounds": [ + { + "listen": "198.18.187.61", + "listen_port": "#6", + "method": "2022-blake3-aes-256-gcm", + "password": "#11", + "tag": "ng-#2-exit-in", + "type": "shadowsocks" + } + ], + "meta": { + "kind": "node-exit", + "metricsTag": "ng-#2-exit-in", + "nodeId": "#2" + }, + "outbounds": [ + { + "tag": "ng-#2-exit-direct", + "type": "direct" + } + ], + "routingRules": [ + { + "action": "route", + "inbound": [ + "ng-#2-exit-in" + ], + "outbound": "ng-#2-exit-direct" + } + ] + } + }, + "serverId": "#2", + "startedAt": "", + "status": "completed", + "updatedAt": "" + } + ], + "keyStore": { + "reality-#2": { + "privateKey": "#2", + "publicKey": "#1", + "updatedAt": "" + }, + "reality-#3": { + "privateKey": "#1", + "publicKey": "#2", + "updatedAt": "" + } + }, + "nodeClients": [ + { + "createdAt": "", + "credentials": { + "clientId": "#1", + "relayPassword": "#3", + "shortId": "#4" + }, + "customerId": "#1", + "id": "#1", + "nodeId": "#2", + "status": "active", + "updatedAt": "" + }, + { + "createdAt": "", + "credentials": { + "clientId": "#2", + "relayPassword": "#7", + "shortId": "#8" + }, + "customerId": "#2", + "id": "#2", + "nodeId": "#2", + "status": "active", + "updatedAt": "" + }, + { + "createdAt": "", + "credentials": { + "clientId": "#3", + "relayPassword": "#5", + "shortId": "#6" + }, + "customerId": "#1", + "id": "#3", + "nodeId": "#1", + "status": "active", + "updatedAt": "" + }, + { + "createdAt": "", + "credentials": { + "clientId": "#4", + "relayPassword": "#13", + "shortId": "#10" + }, + "customerId": "#3", + "id": "#4", + "nodeId": "#3", + "status": "active", + "updatedAt": "" + } + ], + "nodes": [ + { + "artifacts": { + "realityPublicKey": "#1" + }, + "autoRemoving": false, + "createdAt": "", + "entryReady": true, + "error": null, + "exitCredentials": { + "clientId": "#5", + "password": "#11", + "socksUser": "ng_5b8dd6b991" + }, + "exitPort": "#6", + "exitPortMode": "random", + "exitProtocol": "shadowsocks-2022-aes256", + "exitReady": true, + "exitResourceId": "#1", + "exitServerId": "#2", + "id": "#2", + "lastActiveAt": "", + "lastSampleAt": "", + "lastUsageAt": "", + "meteredTraffic": { + "downlink": 14000, + "uplink": 12000 + }, + "name": "N1-renamed", + "networkMode": "ipv4", + "pendingDelete": false, + "port": "#7", + "portMode": "random", + "protocol": "vless-reality-vision", + "reality": { + "destPort": 443, + "keyId": "reality-#2", + "serverName": "www.amd.com" + }, + "realityDestPort": 443, + "realityServerName": "www.amd.com", + "resourceId": "#2", + "retiredUsageEpochs": [ + "diff-epoch-1" + ], + "serverId": "#1", + "sortIndex": 0, + "speedAt": "", + "speedDownBps": "", + "speedUpBps": "", + "status": "active", + "topology": "forward", + "updatedAt": "", + "usageCounters": { + "downlink": 9000, + "epoch": "golden-epoch-1", + "uplink": 8000 + }, + "wsPath": "#1" + }, + { + "artifacts": {}, + "autoRemoving": false, + "createdAt": "", + "entryReady": true, + "error": null, + "exitPort": null, + "exitPortMode": null, + "exitProtocol": null, + "exitReady": false, + "exitResourceId": "#3", + "exitServerId": null, + "id": "#1", + "lastUsageAt": null, + "meteredTraffic": null, + "name": "N2", + "networkMode": "ipv4", + "pendingDelete": false, + "port": "#3", + "portMode": "random", + "protocol": "shadowsocks-2022-aes128", + "realityDestPort": 443, + "realityServerName": "www.tesla.com", + "resourceId": "#4", + "retiredUsageEpochs": [], + "serverId": "#1", + "sortIndex": 1, + "ssServerKey": "#12", + "status": "active", + "topology": "direct", + "updatedAt": "", + "usageCounters": null, + "wsPath": "#2" + }, + { + "artifacts": { + "realityPublicKey": "#2" + }, + "autoRemoving": false, + "createdAt": "", + "entryReady": true, + "error": null, + "exitPort": null, + "exitPortMode": null, + "exitProtocol": null, + "exitReady": false, + "exitResourceId": "#5", + "exitServerId": null, + "id": "#3", + "lastUsageAt": null, + "meteredTraffic": null, + "name": "N3", + "networkMode": "ipv4", + "pendingDelete": false, + "port": "#4", + "portMode": "random", + "protocol": "vless-reality", + "reality": { + "destPort": 443, + "keyId": "reality-#3", + "serverName": "www.amd.com" + }, + "realityDestPort": 443, + "realityServerName": "www.amd.com", + "resourceId": "#6", + "retiredUsageEpochs": [], + "serverId": "#3", + "sortIndex": 2, + "status": "active", + "topology": "direct", + "updatedAt": "", + "usageCounters": null, + "wsPath": "#9" + } + ], + "observations": [], + "schemaVersion": 1, + "servers": [ + { + "createdAt": "", + "engine": null, + "id": "#1", + "labels": [], + "lastSeenAt": "", + "name": "Entry", + "portRangeEnd": 50000, + "portRangeStart": 20000, + "publicAddress": "entry.example.com", + "publicAddressV6": "v6.example.com", + "region": "", + "role": "relay", + "status": "online", + "system": {}, + "tlsDomain": "tls.example.com", + "updatedAt": "", + "usage": { + "error": null, + "lastReportAt": "", + "sampleCount": 2 + } + }, + { + "createdAt": "", + "engine": null, + "id": "#2", + "labels": [], + "lastSeenAt": "", + "name": "Exit", + "portRangeEnd": 50000, + "portRangeStart": 20000, + "publicAddress": "exit.example.com", + "publicAddressV6": "", + "region": "", + "role": "exit", + "status": "online", + "system": {}, + "tlsDomain": "", + "updatedAt": "" + }, + { + "createdAt": "", + "enabled": true, + "id": "#3", + "labels": [], + "lastSeenAt": "", + "name": "S3", + "portRangeEnd": 50000, + "portRangeStart": 20000, + "publicAddress": "s3.example.com", + "publicAddressV6": "", + "region": "", + "role": "relay", + "status": "online", + "system": {}, + "tlsDomain": "", + "updatedAt": "" + } + ], + "settings": { + "activityRetentionDays": 30, + "brandName": "NexusGate", + "completedJobRetentionDays": 7, + "observationTtlMinutes": 10, + "sessionHours": 12 + }, + "subscriptionAccess": [ + { + "at": "", + "bytes": 378, + "clientKey": "#1", + "customerId": "#1", + "format": "raw", + "id": "#1", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 504, + "clientKey": "#1", + "customerId": "#1", + "format": "base64", + "id": "#2", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 736, + "clientKey": "#1", + "customerId": "#1", + "format": "clash", + "id": "#3", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 1704, + "clientKey": "#1", + "customerId": "#1", + "format": "clash-smart", + "id": "#4", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 1127, + "clientKey": "#1", + "customerId": "#1", + "format": "singbox", + "id": "#5", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 343, + "clientKey": "#1", + "customerId": "#1", + "format": "surge", + "id": "#6", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 736, + "clientKey": "#2", + "customerId": "#1", + "format": "auto", + "id": "#7", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "clash-verge/v2.0.0" + }, + { + "at": "", + "bytes": 504, + "clientKey": "#3", + "customerId": "#1", + "format": "auto", + "id": "#8", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "Mozilla/5.0 (DiffTest)" + }, + { + "at": "", + "bytes": 378, + "clientKey": "#1", + "customerId": "#1", + "format": "raw", + "id": "#9", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 504, + "clientKey": "#1", + "customerId": "#1", + "format": "base64", + "id": "#10", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 736, + "clientKey": "#1", + "customerId": "#1", + "format": "clash", + "id": "#11", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 1704, + "clientKey": "#1", + "customerId": "#1", + "format": "clash-smart", + "id": "#12", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 1127, + "clientKey": "#1", + "customerId": "#1", + "format": "singbox", + "id": "#13", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 343, + "clientKey": "#1", + "customerId": "#1", + "format": "surge", + "id": "#14", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 736, + "clientKey": "#2", + "customerId": "#1", + "format": "auto", + "id": "#15", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "clash-verge/v2.0.0" + }, + { + "at": "", + "bytes": 504, + "clientKey": "#3", + "customerId": "#1", + "format": "auto", + "id": "#16", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "Mozilla/5.0 (DiffTest)" + }, + { + "at": "", + "bytes": 247, + "clientKey": "#1", + "customerId": "#2", + "format": "raw", + "id": "#17", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 332, + "clientKey": "#1", + "customerId": "#2", + "format": "base64", + "id": "#18", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 541, + "clientKey": "#1", + "customerId": "#2", + "format": "clash", + "id": "#19", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 1457, + "clientKey": "#1", + "customerId": "#2", + "format": "clash-smart", + "id": "#20", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 866, + "clientKey": "#1", + "customerId": "#2", + "format": "singbox", + "id": "#21", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 0, + "clientKey": "#1", + "customerId": "#2", + "format": "surge", + "id": "#22", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "没有已部署的入口节点", + "status": 503, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 541, + "clientKey": "#2", + "customerId": "#2", + "format": "auto", + "id": "#23", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "clash-verge/v2.0.0" + }, + { + "at": "", + "bytes": 0, + "clientKey": "#3", + "customerId": "#2", + "format": "auto", + "id": "#24", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "订阅客户端估计数超限", + "status": 429, + "userAgent": "Mozilla/5.0 (DiffTest)" + }, + { + "at": "", + "bytes": 222, + "clientKey": "#1", + "customerId": "#3", + "format": "raw", + "id": "#25", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 296, + "clientKey": "#1", + "customerId": "#3", + "format": "base64", + "id": "#26", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 509, + "clientKey": "#1", + "customerId": "#3", + "format": "clash", + "id": "#27", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 1425, + "clientKey": "#1", + "customerId": "#3", + "format": "clash-smart", + "id": "#28", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 829, + "clientKey": "#1", + "customerId": "#3", + "format": "singbox", + "id": "#29", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 0, + "clientKey": "#1", + "customerId": "#3", + "format": "surge", + "id": "#30", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "没有已部署的入口节点", + "status": 503, + "userAgent": "NexusGate-DiffTest/1.0" + }, + { + "at": "", + "bytes": 509, + "clientKey": "#2", + "customerId": "#3", + "format": "auto", + "id": "#31", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "clash-verge/v2.0.0" + }, + { + "at": "", + "bytes": 296, + "clientKey": "#3", + "customerId": "#3", + "format": "auto", + "id": "#32", + "identityType": "user-agent", + "ip": "127.0.0.1", + "reason": "已返回", + "status": 200, + "userAgent": "Mozilla/5.0 (DiffTest)" + } + ], + "subscriptionClients": [ + { + "clientKey": "#1", + "customerId": "#1", + "firstSeenAt": "", + "identityType": "user-agent", + "lastSeenAt": "" + }, + { + "clientKey": "#2", + "customerId": "#1", + "firstSeenAt": "", + "identityType": "user-agent", + "lastSeenAt": "" + }, + { + "clientKey": "#3", + "customerId": "#1", + "firstSeenAt": "", + "identityType": "user-agent", + "lastSeenAt": "" + }, + { + "clientKey": "#1", + "customerId": "#2", + "firstSeenAt": "", + "identityType": "user-agent", + "lastSeenAt": "" + }, + { + "clientKey": "#2", + "customerId": "#2", + "firstSeenAt": "", + "identityType": "user-agent", + "lastSeenAt": "" + }, + { + "clientKey": "#1", + "customerId": "#3", + "firstSeenAt": "", + "identityType": "user-agent", + "lastSeenAt": "" + }, + { + "clientKey": "#2", + "customerId": "#3", + "firstSeenAt": "", + "identityType": "user-agent", + "lastSeenAt": "" + }, + { + "clientKey": "#3", + "customerId": "#3", + "firstSeenAt": "", + "identityType": "user-agent", + "lastSeenAt": "" + } + ], + "users": [ + { + "createdAt": "", + "id": "#1", + "passwordHash": "#13", + "role": "owner", + "status": "active", + "username": "admin" + } + ] + }, + "status": 200 + }, + "errors": { + "badSubscription": { + "body": "\n\n\n \n \n \n \n amo\n \n \n\n\n
\n
\n
amo
\n
\n \n \n \n
\n

\n \n
\n
\n\n
\n \n\n
\n
\n amo / 系统状态\n \n \n \n \n 管\n
\n
\n
\n
\n\n
\n
\n

\n
\n
\n
\n
\n
\n \n \n\n\n", + "status": 200 + }, + "loginFailure": { + "body": { + "error": "invalid_credentials", + "message": "账号或密码不正确" + }, + "status": 401 + }, + "missingNode": { + "body": { + "error": "not_found", + "message": "接口不存在" + }, + "status": 404 + }, + "unauthenticated": { + "body": { + "error": "unauthorized", + "message": "请先登录" + }, + "status": 401 + } + } + } +} diff --git a/panel-go/golden/golden.mjs b/panel-go/golden/golden.mjs new file mode 100644 index 0000000..09afca0 --- /dev/null +++ b/panel-go/golden/golden.mjs @@ -0,0 +1,248 @@ +// NexusGate Go panel golden-behavior harness. +// +// Freezes the CURRENT Go panel behavior as a regression baseline now that the +// Node implementation is retired (2026-10-21 decision): a single Go panel is +// started on a copy of the difftest seed DB, driven through a fixed script of +// API calls, and every response is passed through the difftest learning +// Normalizer (volatile ids / secrets / ports / timestamps become placeholders) +// before being stored in golden.json. `verify` replays the same script and +// diffs against the frozen file; any diff must be either an intended change +// (re-capture and note it in the commit) or a regression. +// +// Usage: +// node golden.mjs capture [--bin /path/to/nexusgate-panel] +// node golden.mjs verify [--bin /path/to/nexusgate-panel] [--golden golden.json] +// +// The binary defaults to ../nexusgate-panel-linux-amd64 (same resolution as +// the difftest runner). The repo data/ directory is never touched. + +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { + startPanel, waitHealth, stopPanel, makeTempDir, freePort, PanelClient, + Normalizer, firstDiff, fetchAllSubscriptions, drainAgentJobs, pollOnce, + completeJob, DEFAULT_GO_BIN, DIFFTEST_DIR, +} from '../difftest/lib.mjs'; + +const HERE = path.dirname(fileURLToPath(import.meta.url)); +const SEED = path.join(DIFFTEST_DIR, 'seed.json'); +const SEED_LOG = path.join(DIFFTEST_DIR, 'seed-log.json'); + +function parseArgs(argv) { + const args = { mode: argv[2], bin: process.env.GO_PANEL_BIN || DEFAULT_GO_BIN, golden: path.join(HERE, 'golden.json') }; + for (let i = 3; i < argv.length; i += 1) { + if (argv[i] === '--bin') args.bin = argv[++i]; + else if (argv[i] === '--golden') args.golden = argv[++i]; + else throw new Error(`unknown argument: ${argv[i]}`); + } + if (!['capture', 'verify'].includes(args.mode)) throw new Error('mode must be capture or verify'); + return args; +} + +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); + +// View of an admin/agent HTTP response, mirroring difftest's view(). +function view(res) { + return { status: res.status, body: res.json !== null && res.json !== undefined ? res.json : res.text }; +} + +// Keys whose numeric values are wall-clock derived and therefore not part of +// the behavior contract; squashed wherever they appear. +const VOLATILE_NUM_KEYS = /uptime|serverTimeMs|nowMs/i; +function squashVolatile(value) { + if (Array.isArray(value)) return value.map(squashVolatile); + if (value && typeof value === 'object') { + const out = {}; + for (const [k, v] of Object.entries(value)) { + out[k] = typeof v === 'number' && VOLATILE_NUM_KEYS.test(k) ? '' : squashVolatile(v); + } + return out; + } + return value; +} + +// /api/system: almost every number is a live sample. Only structural counts +// that follow deterministically from the script are kept verbatim. +const SYSTEM_KEEP_NUM = new Set(['nodesTotal', 'customersTotal', 'onlineNodes', 'onlineCustomers', 'connections']); +function squashSystem(value, keyName = null) { + if (typeof value === 'number') return keyName && SYSTEM_KEEP_NUM.has(keyName) ? value : ''; + if (typeof value === 'string' && /^\d{1,3}(\.\d{1,3}){3}$/.test(value)) return ''; + if (Array.isArray(value)) return value.map((v) => squashSystem(v, keyName)); + if (value && typeof value === 'object') { + const out = {}; + for (const k of Object.keys(value).sort()) out[k] = squashSystem(value[k], k); + return out; + } + return value; +} + +async function runScript(bin) { + const seedLog = JSON.parse(fs.readFileSync(SEED_LOG, 'utf8')); + const seedIds = seedLog.meta.ids; + const tmp = makeTempDir('nexusgate-golden-'); + const dataFile = path.join(tmp, 'nexusgate.json'); + fs.copyFileSync(SEED, dataFile); + const port = await freePort(); + const proc = startPanel({ kind: 'go', bin, dataFile, port }); + const N = new Normalizer(); + const sections = {}; + const cap = (name, value) => { sections[name] = N.process(squashVolatile(structuredClone(value))); }; + try { + await waitHealth(proc); + const client = new PanelClient(proc.base); + const ids = { entryServer: seedIds.entryServer, exitServer: seedIds.exitServer, n1: seedIds.n1, n2: seedIds.n2, alice: seedIds.alice, bob: seedIds.bob }; + const agentKeys = { entry: seedLog.meta.agentKeys.entry, exit: seedLog.meta.agentKeys.exit }; + const subTokens = {}; + + cap('login', view(await client.login())); + cap('session', view(await client.admin('GET', '/api/session'))); + + // Old-shape heartbeats (no engine object at all): the panel must keep + // accepting them and simply have no engine state to show. + cap('heartbeatSeedAgents', { + entry: view(await client.agentPost('/api/agent/heartbeat', agentKeys.entry, { version: '0.6.8', system: {} })), + exit: view(await client.agentPost('/api/agent/heartbeat', agentKeys.exit, { version: '0.6.8', system: {} })), + }); + + const customersRes = await client.admin('GET', '/api/customers'); + for (const c of customersRes.json.customers) subTokens[c.name.toLowerCase()] = c.subscriptionToken; + const nodesRes = await client.admin('GET', '/api/nodes'); + cap('lists', { + overview: view(await client.admin('GET', '/api/overview')), + servers: view(await client.admin('GET', '/api/servers')), + customers: view(customersRes), + nodes: view(nodesRes), + jobs: view(await client.admin('GET', '/api/jobs')), + }); + + cap('credentials', { + alice: view(await client.admin('GET', `/api/customers/${ids.alice}/credentials`)), + bob: view(await client.admin('GET', `/api/customers/${ids.bob}/credentials`)), + }); + + cap('subscriptionsSeed', { + alice: await fetchAllSubscriptions(client, subTokens.alice), + bob: await fetchAllSubscriptions(client, subTokens.bob), + }); + + // New server + agent enrolment. + const s3res = await client.admin('POST', '/api/servers', { + name: 'S3', role: 'relay', region: '', publicAddress: 's3.example.com', + publicAddressV6: '', tlsDomain: '', portRangeStart: 20000, portRangeEnd: 50000, + }); + ids.s3 = s3res.json.server.id; + cap('createServerS3', view(s3res)); + const ticket = await client.admin('POST', `/api/servers/${ids.s3}/enrollment-token`, {}); + const enrolled = await client.request('POST', '/api/agent/enroll', { + body: { token: ticket.json.token, hostname: 'golden-agent-s3', version: '0.6.8', system: {} }, + cookie: null, + }); + agentKeys.s3 = enrolled.json.agentKey; + cap('enrollS3', { ticket: view(ticket), enroll: view(enrolled) }); + + const carolRes = await client.admin('POST', '/api/customers', { name: 'Carol' }); + ids.carol = carolRes.json.customer.id; + subTokens.carol = carolRes.json.customer.subscriptionToken; + cap('createCarol', view(carolRes)); + + const n3res = await client.admin('POST', '/api/nodes', { + name: 'N3', serverId: ids.s3, topology: 'direct', + protocol: 'vless-reality', realityServerName: 'www.amd.com', + }); + ids.n3 = n3res.json.node.id; + cap('createNodeN3', view(n3res)); + cap('assignN3', view(await client.admin('POST', `/api/nodes/${ids.n3}/assign`, { customerId: ids.carol }))); + cap('deployN3', view(await client.admin('POST', `/api/nodes/${ids.n3}/deploy`, {}))); + const drainedS3 = await drainAgentJobs(client, agentKeys.s3); + cap('deployN3Jobs', drainedS3.map(({ job, complete }) => ({ job, complete }))); + cap('nodesAfterDeploy', view(await client.admin('GET', '/api/nodes'))); + cap('carolSubscriptions', await fetchAllSubscriptions(client, subTokens.carol)); + + // Usage + presence/speed projection (two cumulative reports 6s apart). + const usageBody = (u1, d1, u2, d2) => ({ + epoch: 'golden-epoch-1', + samples: [ + { kind: 'user', id: ids.alice, uplink: u1, downlink: d1 }, + { kind: 'inbound', id: ids.n1, uplink: u2, downlink: d2 }, + ], + }); + cap('usageReport1', view(await client.agentPost('/api/agent/usage', agentKeys.entry, usageBody(1500, 2100, 5000, 6000)))); + await sleep(6000); + cap('usageReport2', view(await client.agentPost('/api/agent/usage', agentKeys.entry, usageBody(4500, 6300, 8000, 9000)))); + const customers2 = await client.admin('GET', '/api/customers'); + const nodes2 = await client.admin('GET', '/api/nodes'); + const nameOf = (id) => { const c = customers2.json.customers.find((x) => x.id === id); return c ? c.name : '?'; }; + sections.presenceProjection = { + customers: customers2.json.customers.map((c) => ({ + name: c.name, online: c.online, + speedPositive: (c.speedUpBps || 0) > 0 && (c.speedDownBps || 0) > 0, + hasLastActiveAt: Boolean(c.lastActiveAt), + })), + nodes: nodes2.json.nodes.map((n) => ({ + name: n.name, clientCount: n.clientCount, onlineCount: n.onlineCount, + onlineNames: (n.onlineCustomerIds || []).map(nameOf), + speedPositive: (n.speedUpBps || 0) > 0, + })), + }; + cap('customersAfterUsage', view(customers2)); + + // UI-style full-payload PATCH of the seed forward node N1 (name change + // only). The browser form always submits portMode + port(null in random + // mode) + serverId; this is the exact traffic shape behind the port + // re-roll bug, frozen here so the fix shows up as a deliberate diff. + const n1 = nodes2.json.nodes.find((n) => n.id === ids.n1); + const uiPatch = { + name: 'N1-renamed', serverId: n1.serverId, protocol: n1.protocol, + portMode: n1.portMode, port: n1.portMode === 'fixed' ? n1.port : null, + networkMode: n1.networkMode, topology: n1.topology, + exitServerId: n1.exitServerId, exitProtocol: n1.exitProtocol, + exitPortMode: n1.exitPortMode, exitPort: n1.exitPortMode === 'fixed' ? n1.exitPort : null, + realityServerName: n1.realityServerName || '', realityDestPort: n1.realityDestPort || null, + }; + cap('nodePatchUiStyle', view(await client.admin('PATCH', `/api/nodes/${ids.n1}`, uiPatch))); + const drainedEntry = await drainAgentJobs(client, agentKeys.entry); + const drainedExit = await drainAgentJobs(client, agentKeys.exit); + cap('nodePatchJobs', { + entry: drainedEntry.map(({ job, complete }) => ({ job, complete })), + exit: drainedExit.map(({ job, complete }) => ({ job, complete })), + }); + cap('nodesAfterPatch', view(await client.admin('GET', '/api/nodes'))); + + const systemRes = await client.admin('GET', '/api/system'); + sections.systemShape = { status: systemRes.status, body: squashSystem(N.process(structuredClone(systemRes.json))) }; + + cap('backup', view(await client.admin('GET', '/api/backup'))); + + const anon = new PanelClient(proc.base); + cap('errors', { + unauthenticated: view(await anon.request('GET', '/api/customers')), + loginFailure: view(await anon.request('POST', '/api/auth/login', { body: { username: 'admin', password: 'wrong-password' }, cookie: null })), + missingNode: view(await client.admin('GET', '/api/nodes/nod_00000000-0000-0000-0000-000000000000')), + badSubscription: await (async () => { + const r = await client.getSubscription('nosuchtoken000000', 'raw', 'NexusGate-Golden/1.0'); + return { status: r.status, body: r.body }; + })(), + }); + + return sections; + } finally { + await stopPanel(proc); + fs.rmSync(tmp, { recursive: true, force: true }); + } +} + +const args = parseArgs(process.argv); +if (!fs.existsSync(args.bin)) { console.error(`Go panel binary not found: ${args.bin}`); process.exit(2); } +const sections = await runScript(args.bin); +if (args.mode === 'capture') { + const payload = { meta: { capturedAt: new Date().toISOString(), note: 'Go panel golden baseline; see README.md in this directory.' }, sections }; + fs.writeFileSync(args.golden, `${JSON.stringify(payload, null, 2)}\n`); + console.log(`CAPTURED ${Object.keys(sections).length} sections -> ${args.golden}`); +} else { + const golden = JSON.parse(fs.readFileSync(args.golden, 'utf8')); + const diff = firstDiff(golden.sections, sections); + if (!diff) { console.log('GOLDEN PASS — behavior identical to frozen baseline'); process.exit(0); } + console.log(`GOLDEN DIFF at ${diff.at}\n golden: ${diff.a}\n actual: ${diff.b}`); + process.exit(1); +}