feat: customer-level credentials shared across all nodes (item 2)
One credential set per customer (UUID + 32-byte root relay key + shortId)
lives on the customer record; node bindings no longer store credentials.
SS2022 user keys derive deterministically from the root key by method
length (16/24/32-byte prefix), so a single password serves every protocol,
matching 3x-ui's per-client credential model. Reality public keys stay a
node property and are shown per binding in the credentials pane.
- store: load-time convergence — first binding's set wins; a converged
password that is not a 32-byte root key is regenerated (uuid/shortId
preserved); bindings are stripped; idempotent, covered by tests
- deploy: the orchestrator stamps the customer's set onto a build copy;
subscriptions resolve the same set; the derivation applies in both the
inbound builder and the URI builder (verified live: Alice's N1 link is
byte-identical to the pre-change seed expectation, her N2 user key is
exactly the root key's 16-byte prefix)
- API: GET /api/customers/:id/credentials returns {credentials, bindings};
regenerate is now POST /api/customers/:id/credentials/regenerate and
rotates the whole set, rebuilding every deployed node of the customer
- UI: credentials pane shows the single shared set plus per-binding
node/protocol/Reality-public-key rows
- golden subscriptions fixture regenerated under one shared set (RF and
SS entries byte-identical to before; five entries audited by hand)
- golden baseline re-captured for the new shapes; difftest normalizer now
treats a base64 root key's prefix-truncation encodings and the Surge
'='-mangled variants as the same secret
This commit is contained in:
@@ -0,0 +1,72 @@
|
|||||||
|
# NexusGate 分发链接迁移报告(GitHub → 自建 Gitea)
|
||||||
|
|
||||||
|
- 日期:2026-10-20
|
||||||
|
- 提交:`f4ef3d9` chore: migrate self-distribution links from GitHub to self-hosted Gitea(8 文件,本地已 commit,**未 push**,等云电脑侧续推)
|
||||||
|
- 新基址:`https://git.881218.xyz/rogue/NexusGate`
|
||||||
|
|
||||||
|
## 映射规则(已按此执行)
|
||||||
|
|
||||||
|
| 类型 | 旧(GitHub) | 新(Gitea) |
|
||||||
|
|---|---|---|
|
||||||
|
| raw | `raw.githubusercontent.com/aaaamoooo/NexusGate/<branch>/<path>` | `git.881218.xyz/rogue/NexusGate/raw/branch/<branch>/<path>` |
|
||||||
|
| release | `github.com/aaaamoooo/NexusGate/releases/download/<tag>/<file>` | `git.881218.xyz/rogue/NexusGate/releases/download/<tag>/<file>` |
|
||||||
|
| archive | `github.com/<REPO>/archive/refs/heads/<branch>.tar.gz` | `git.881218.xyz/rogue/NexusGate/archive/<branch>.tar.gz` |
|
||||||
|
| clone | `github.com/aaaamoooo/NexusGate.git` | `git.881218.xyz/rogue/NexusGate.git` |
|
||||||
|
|
||||||
|
## 逐文件改动
|
||||||
|
|
||||||
|
- `public/app.js`(2 处):面板生成的节点注册命令、旧版卸载兼容命令的 raw 链接 → Gitea raw。
|
||||||
|
- `panel-go/embed_public/app.js`:同上同步改毕,两份 app.js 全文件逐字一致。注意该目录是 panel-go 的构建产物(`.gitignore` 排除,`build.sh` 每次从 `../public` 重新拷贝),不入仓;panel-go 已编译的三个二进制(同为 gitignore 产物)内嵌的仍是旧链接,**需重跑 panel-go/build.sh 重建后才生效**。
|
||||||
|
- `scripts/agent-install.sh` / `scripts/agent-update.sh`:新增 `GIT_BASE="https://git.881218.xyz/${REPO}"`,raw 与 release 链接改由 GIT_BASE 拼装;`REPO` 默认 `aaaamoooo/NexusGate` → `rogue/NexusGate`;`NG_REPO`/`NG_BRANCH`/`--repo`/`--branch`/`NG_AGENT_RELEASE` 语义与默认值不变;raw 的 `?t=` 缓存破坏参数保留。相关注释 "GitHub Release" → "Gitea Release"。
|
||||||
|
- `scripts/install.sh` / `scripts/nexusgate.sh`:同上加 GIT_BASE;归档链接改 Gitea archive 格式(两脚本的解包逻辑对顶层目录的处理与 Gitea 包结构兼容:install.sh 取唯一顶层目录、nexusgate.sh 用 --strip-components=1)。
|
||||||
|
- `scripts/agent-singbox.sh`:release 基址改为 `https://git.881218.xyz/${NG_REPO:-rogue/NexusGate}/releases/download/${release_tag}`(调用方 agent-install.sh 本就透传 NG_REPO);stats-query.go 的 raw 拉取改 Gitea raw 格式;提示文案 GitHub → Gitea。
|
||||||
|
- `README.md`:clone 地址、一键注册命令 raw 链接、"GitHub Release" 文案。
|
||||||
|
- `agent-go/INSTALL-GO.md`:release 下载地址与文案。
|
||||||
|
|
||||||
|
## ⚠️ 判断性改动(请复核)
|
||||||
|
|
||||||
|
以下三处脚本的 `REPO` 默认值原本指向**上游原作者** `a2899882/NexusGate`,本次一并改为 `rogue/NexusGate`:
|
||||||
|
|
||||||
|
- `scripts/install.sh`、`scripts/nexusgate.sh` 的源码归档下载
|
||||||
|
- `scripts/agent-singbox.sh` 中 stats-query.go 的拉取源
|
||||||
|
|
||||||
|
理由:这三处下载的是**本 fork 的面板/Agent 代码本身**(上游原版没有本 fork 的二开改动,装错即装成原版),且 a2899882 在自建 Gitea 上不存在、保留只会 404。调用方均可继续用 `NG_REPO` 覆盖。如认为应保留上游默认,回滚这三行即可。
|
||||||
|
|
||||||
|
## 未改动(残留清单及理由)
|
||||||
|
|
||||||
|
- `agent/stats-query.go` 的 import、`scripts/agent-singbox.sh` 的 `go install`/`go get github.com/sagernet/sing-box@...`、`agent-go/PARITY.md` 中的提及:均为**上游 sing-box 依赖包名**,不是分发链接。
|
||||||
|
- `scripts/agent-singbox.sh` 中 Go 工具链下载 `go.dev/dl/...`:上游官方地址、非 GitHub。
|
||||||
|
- `.github/workflows/ci.yml`:GitHub Actions 配置(actions/checkout 等),与分发无关;Gitea 侧默认不执行,保持原样。
|
||||||
|
- 全仓已复查:无 ghproxy / jsdelivr 等镜像域名、无其他写死的旧分发域名。
|
||||||
|
|
||||||
|
## 自检结果
|
||||||
|
|
||||||
|
- grep 复查:文本文件中已无指向 `aaaamoooo` / `a2899882` / `imamoxilin` 或以 `${REPO}` 拼 GitHub 主机的自有分发链接(残留仅上表上游依赖)。
|
||||||
|
- `bash -n` 五个改过的脚本全部通过。
|
||||||
|
- `public/app.js` 与 `panel-go/embed_public/app.js` diff 为空(逐字一致)。
|
||||||
|
|
||||||
|
## ★ Gitea 上尚不存在、需手工建的 Release 清单
|
||||||
|
|
||||||
|
脚本已指向以下地址,但在把文件传上去之前它们是 404。raw 与源码归档由 Gitea 按仓自动提供,无需手工处理;**只有这两组 release 要建**:
|
||||||
|
|
||||||
|
### 1. 标签 `agent-v0.6.8`(agent-install.sh / agent-update.sh 的 `AGENT_RELEASE` 默认值)
|
||||||
|
|
||||||
|
| 附件名(必须逐字一致) | 本地现存文件 |
|
||||||
|
|---|---|
|
||||||
|
| `nexusgate-agent-linux-amd64` | `~/workspace/backups/2026-10-20/binaries/nexusgate-agent-linux-amd64` |
|
||||||
|
| `nexusgate-agent-linux-arm64` | `~/workspace/backups/2026-10-20/binaries/nexusgate-agent-linux-arm64` |
|
||||||
|
|
||||||
|
(同名同大小副本也在 `~/workspace/NexusGate/agent-go/` 下,任选一处上传,文件名不用改。)
|
||||||
|
|
||||||
|
### 2. 标签 `sing-box-v1.13.21`(agent-singbox.sh 的 `release_tag` 默认值,可由 `NG_SINGBOX_RELEASE` 覆盖)
|
||||||
|
|
||||||
|
| 附件名(必须逐字一致) | 本地现存文件(上传时需改名去掉 `-v1.13.21` 后缀) |
|
||||||
|
|---|---|
|
||||||
|
| `nexusgate-sing-box-linux-amd64` | `~/workspace/nexusgate-release/bin/nexusgate-sing-box-linux-amd64-v1.13.21` |
|
||||||
|
| `nexusgate-sing-box-linux-arm64` | `~/workspace/nexusgate-release/bin/nexusgate-sing-box-linux-arm64-v1.13.21` |
|
||||||
|
| `nexusgate-sing-box-stats-linux-amd64` | `~/workspace/nexusgate-release/bin/nexusgate-sing-box-stats-linux-amd64-v1.13.21` |
|
||||||
|
| `nexusgate-sing-box-stats-linux-arm64` | `~/workspace/nexusgate-release/bin/nexusgate-sing-box-stats-linux-arm64-v1.13.21` |
|
||||||
|
|
||||||
|
(`~/workspace/backups/2026-10-20/binaries/` 下有同名镜像副本。)
|
||||||
|
|
||||||
|
注:sing-box 预编译下载失败时脚本会自动回退现场源码编译,所以这组 release 缺失不会让装机彻底失败,只是慢;agent 那组缺失则 Go 版 agent 无法新装(Node 回退路径走 raw,不受影响)。
|
||||||
@@ -72,3 +72,21 @@ amo 各页主内容区两侧留白偏大;把左右间距(padding/最大宽
|
|||||||
## 10. 删除顶栏全局搜索框(2026-10-21 04:39,用户附顶栏截图)
|
## 10. 删除顶栏全局搜索框(2026-10-21 04:39,用户附顶栏截图)
|
||||||
|
|
||||||
顶栏"搜索服务器、客户、节点…"输入框是写死 disabled 的摆设、无任何功能;且每页都有自己的可用搜索框,此框多余——删除(顶栏共用一处,删一处全站生效)。
|
顶栏"搜索服务器、客户、节点…"输入框是写死 disabled 的摆设、无任何功能;且每页都有自己的可用搜索框,此框多余——删除(顶栏共用一处,删一处全站生效)。
|
||||||
|
|
||||||
|
## 11. 菜单与命名调整 + 页面合并(2026-10-21 04:47,用户附节点页全图)
|
||||||
|
|
||||||
|
- 左侧菜单「客户与订阅」→ 改名「用户订阅」(页面标题/面包屑同步)
|
||||||
|
- 左侧菜单「服务器」→ 改名「节点管理」
|
||||||
|
- 左侧菜单「转发与节点」→ 改名「入站&出站」(页面标题/面包屑同步)
|
||||||
|
- 该页「+ 新建节点」按钮 → 改为「添加入站」;页内计数文案「9 个节点」→「9 个入站」(同页其他"节点"计数文案同步对齐)
|
||||||
|
- 左侧菜单「设置与运维」→ 改名「设置」
|
||||||
|
- 「安装与命令」页面的全部内容合并进「设置」页面,合并后删除「安装与命令」页面(菜单项与路由一并摘除)
|
||||||
|
|
||||||
|
## 12. 删除系统状态页「最近活动」模块(2026-10-21 04:56,用户附截图)
|
||||||
|
|
||||||
|
首页(系统状态页)底部的「最近活动」面板整个删除(该面板是改版时为保旧功能补回的,用户现明确不要)。只删展示模块,后端活动/审计记录本身保留不动。
|
||||||
|
|
||||||
|
## 13. 设置页「任务队列」表格排紧凑(2026-10-21 04:58,用户附截图)
|
||||||
|
|
||||||
|
现状毛病:列宽分配失衡——任务 ID 被挤到换行两行、状态"已完成"竖排三行、时间两行,而错误列大片空白。
|
||||||
|
要求:重配列宽与换行——ID/状态/时间等短内容不换行、单行呈现;空白列不占大宽度;整表紧凑。合并进设置页后保持此排版。
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// Tests for the customer-level credential model (round-2 item 2):
|
||||||
|
// store-load convergence from legacy per-binding credentials, the
|
||||||
|
// deterministic password rule, and the SS2022 user-key derivation.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMigrateCustomerCredentialsConvergesFirstBinding(t *testing.T) {
|
||||||
|
d := &Data{
|
||||||
|
Customers: []*Customer{{ID: "cus_1", Name: "Legacy"}},
|
||||||
|
NodeClients: []*NodeClient{
|
||||||
|
{ID: "ncl_1", NodeID: "nod_a", CustomerID: "cus_1", Credentials: &Credentials{
|
||||||
|
ClientID: "11111111-2222-3333-4444-000000000001",
|
||||||
|
// A 32-byte root key: must be kept verbatim.
|
||||||
|
RelayPassword: base64.StdEncoding.EncodeToString(make([]byte, 32)),
|
||||||
|
ShortID: "abcd0001ef01",
|
||||||
|
}},
|
||||||
|
{ID: "ncl_2", NodeID: "nod_b", CustomerID: "cus_1", Credentials: &Credentials{
|
||||||
|
ClientID: "22222222-2222-3333-4444-000000000002", RelayPassword: "other", ShortID: "ffff0002ef01",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if !MigrateCustomerCredentials(d) {
|
||||||
|
t.Fatalf("migration reported no change")
|
||||||
|
}
|
||||||
|
creds := d.Customers[0].Credentials
|
||||||
|
if creds == nil || creds.ClientID != "11111111-2222-3333-4444-000000000001" || creds.ShortID != "abcd0001ef01" {
|
||||||
|
t.Fatalf("converged set is not the first binding's: %+v", creds)
|
||||||
|
}
|
||||||
|
if creds.RelayPassword != base64.StdEncoding.EncodeToString(make([]byte, 32)) {
|
||||||
|
t.Fatalf("32-byte root key must be kept, got %q", creds.RelayPassword)
|
||||||
|
}
|
||||||
|
for _, nc := range d.NodeClients {
|
||||||
|
if nc.Credentials != nil {
|
||||||
|
t.Fatalf("binding %s still carries credentials", nc.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if MigrateCustomerCredentials(d) {
|
||||||
|
t.Fatalf("migration must be idempotent")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMigrateCustomerCredentialsRegeneratesShortPasswordOnly(t *testing.T) {
|
||||||
|
d := &Data{
|
||||||
|
Customers: []*Customer{{ID: "cus_1"}},
|
||||||
|
NodeClients: []*NodeClient{
|
||||||
|
{ID: "ncl_1", NodeID: "nod_a", CustomerID: "cus_1", Credentials: &Credentials{
|
||||||
|
ClientID: "11111111-2222-3333-4444-000000000001",
|
||||||
|
// 16-byte key (an old SS2022-aes128 binding set): too short
|
||||||
|
// to derive a 32-byte user key from.
|
||||||
|
RelayPassword: "FRYXGBkaGxwdHh8gISIjJA==",
|
||||||
|
ShortID: "abcd0001ef01",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
MigrateCustomerCredentials(d)
|
||||||
|
creds := d.Customers[0].Credentials
|
||||||
|
if creds.ClientID != "11111111-2222-3333-4444-000000000001" || creds.ShortID != "abcd0001ef01" {
|
||||||
|
t.Fatalf("clientId/shortId must be preserved: %+v", creds)
|
||||||
|
}
|
||||||
|
raw, err := base64.StdEncoding.DecodeString(creds.RelayPassword)
|
||||||
|
if err != nil || len(raw) != 32 {
|
||||||
|
t.Fatalf("password must be regenerated as a 32-byte root key, got %q", creds.RelayPassword)
|
||||||
|
}
|
||||||
|
if creds.RelayPassword == "FRYXGBkaGxwdHh8gISIjJA==" {
|
||||||
|
t.Fatalf("short password must not survive convergence")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMigrateCustomerCredentialsFreshForBindingless(t *testing.T) {
|
||||||
|
d := &Data{Customers: []*Customer{{ID: "cus_new"}}}
|
||||||
|
if !MigrateCustomerCredentials(d) {
|
||||||
|
t.Fatalf("expected a change")
|
||||||
|
}
|
||||||
|
creds := d.Customers[0].Credentials
|
||||||
|
if creds == nil || creds.ClientID == "" || creds.ShortID == "" {
|
||||||
|
t.Fatalf("fresh set incomplete: %+v", creds)
|
||||||
|
}
|
||||||
|
if raw, err := base64.StdEncoding.DecodeString(creds.RelayPassword); err != nil || len(raw) != 32 {
|
||||||
|
t.Fatalf("fresh set password is not a 32-byte root key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEffectiveRelayPassword(t *testing.T) {
|
||||||
|
root := make([]byte, 32)
|
||||||
|
for i := range root {
|
||||||
|
root[i] = byte(i)
|
||||||
|
}
|
||||||
|
rootB64 := base64.StdEncoding.EncodeToString(root)
|
||||||
|
// aes128 (16-byte method): deterministic prefix truncation.
|
||||||
|
if got := EffectiveRelayPassword("shadowsocks-2022-aes128", rootB64); got != base64.StdEncoding.EncodeToString(root[:16]) {
|
||||||
|
t.Fatalf("aes128 derivation wrong: %q", got)
|
||||||
|
}
|
||||||
|
// aes256: the root key itself.
|
||||||
|
if got := EffectiveRelayPassword("shadowsocks-2022-aes256", rootB64); got != rootB64 {
|
||||||
|
t.Fatalf("aes256 derivation wrong: %q", got)
|
||||||
|
}
|
||||||
|
// Legacy methods and other protocols: verbatim.
|
||||||
|
if got := EffectiveRelayPassword("shadowsocks-aes256-gcm", rootB64); got != rootB64 {
|
||||||
|
t.Fatalf("legacy SS must be verbatim")
|
||||||
|
}
|
||||||
|
if got := EffectiveRelayPassword("hysteria2", "plain-password"); got != "plain-password" {
|
||||||
|
t.Fatalf("non-SS must be verbatim")
|
||||||
|
}
|
||||||
|
// An exact-length legacy key passes through unchanged (identity).
|
||||||
|
k16 := "FRYXGBkaGxwdHh8gISIjJA=="
|
||||||
|
if got := EffectiveRelayPassword("shadowsocks-2022-aes128", k16); got != k16 {
|
||||||
|
t.Fatalf("16-byte key on aes128 must be identity, got %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewCustomerCredentialsShape(t *testing.T) {
|
||||||
|
c := NewCustomerCredentials()
|
||||||
|
if c.ClientID == "" || c.ShortID == "" {
|
||||||
|
t.Fatalf("incomplete set: %+v", c)
|
||||||
|
}
|
||||||
|
if raw, err := base64.StdEncoding.DecodeString(c.RelayPassword); err != nil || len(raw) != 32 {
|
||||||
|
t.Fatalf("relay password must be a base64 32-byte root key")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -392,11 +392,30 @@ export class Normalizer {
|
|||||||
if (this.panelOrigin) out = out.split(this.panelOrigin).join('<panel>');
|
if (this.panelOrigin) out = out.split(this.panelOrigin).join('<panel>');
|
||||||
// Learned secrets first (longest raw first so a full token wins over its
|
// Learned secrets first (longest raw first so a full token wins over its
|
||||||
// own prefixes), including the 16-char prefix shown on the info page.
|
// own prefixes), including the 16-char prefix shown on the info page.
|
||||||
const secrets = [...this.secrets.entries()].sort((a, b) => b[0].length - a[0].length);
|
// A base64 secret's prefix truncations (the SS2022 user keys derived
|
||||||
for (const [raw, placeholder] of secrets) {
|
// from a customer's 32-byte root key) are the same logical secret, so
|
||||||
if (raw.length >= 8) out = out.split(raw).join(placeholder);
|
// their encodings are replaced with the same placeholder.
|
||||||
if (raw.length > 16) out = out.split(raw.slice(0, 16)).join(placeholder);
|
const replacements = [];
|
||||||
|
for (const [raw, placeholder] of this.secrets) {
|
||||||
|
if (raw.length >= 8) replacements.push([raw, placeholder]);
|
||||||
|
if (raw.length > 16) replacements.push([raw.slice(0, 16), placeholder]);
|
||||||
|
if (/^[A-Za-z0-9+/]+={0,2}$/.test(raw) && raw.length % 4 === 0) {
|
||||||
|
const bytes = Buffer.from(raw, 'base64');
|
||||||
|
if (bytes.toString('base64') === raw) {
|
||||||
|
for (const n of [16, 24]) {
|
||||||
|
if (n < bytes.length) replacements.push([bytes.subarray(0, n).toString('base64'), placeholder]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The Surge formatter mangles '=' to '_' inside passwords, so the
|
||||||
|
// mangled variant of every replacement is the same secret too.
|
||||||
|
for (const [raw, placeholder] of [...replacements]) {
|
||||||
|
const mangled = raw.replace(/=/g, '_');
|
||||||
|
if (mangled !== raw) replacements.push([mangled, placeholder]);
|
||||||
|
}
|
||||||
|
replacements.sort((a, b) => b[0].length - a[0].length);
|
||||||
|
for (const [raw, placeholder] of replacements) out = out.split(raw).join(placeholder);
|
||||||
for (const [raw, placeholder] of this.hashes) out = out.split(raw).join(placeholder);
|
for (const [raw, placeholder] of this.hashes) out = out.split(raw).join(placeholder);
|
||||||
out = out.replace(PREFIXED_ID_RE, (whole, prefix) => this.#idPlaceholder(prefix, whole));
|
out = out.replace(PREFIXED_ID_RE, (whole, prefix) => this.#idPlaceholder(prefix, whole));
|
||||||
for (const [fragment, placeholder] of this.fragments) out = out.split(fragment).join(placeholder);
|
for (const [fragment, placeholder] of this.fragments) out = out.split(fragment).join(placeholder);
|
||||||
|
|||||||
+128
-99
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"meta": {
|
"meta": {
|
||||||
"capturedAt": "2026-10-20T21:02:25.354Z",
|
"capturedAt": "2026-10-20T21:20:39.194Z",
|
||||||
"note": "Go panel golden baseline; see README.md in this directory."
|
"note": "Go panel golden baseline; see README.md in this directory."
|
||||||
},
|
},
|
||||||
"sections": {
|
"sections": {
|
||||||
@@ -49,6 +49,11 @@
|
|||||||
"customers": [
|
"customers": [
|
||||||
{
|
{
|
||||||
"createdAt": "<ts>",
|
"createdAt": "<ts>",
|
||||||
|
"credentials": {
|
||||||
|
"clientId": "<uuid>#1",
|
||||||
|
"relayPassword": "<secret>#1",
|
||||||
|
"shortId": "<secret>#2"
|
||||||
|
},
|
||||||
"deviceLimit": 0,
|
"deviceLimit": 0,
|
||||||
"expiresAt": null,
|
"expiresAt": null,
|
||||||
"group": "",
|
"group": "",
|
||||||
@@ -83,6 +88,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"createdAt": "<ts>",
|
"createdAt": "<ts>",
|
||||||
|
"credentials": {
|
||||||
|
"clientId": "<uuid>#2",
|
||||||
|
"relayPassword": "<secret>#3",
|
||||||
|
"shortId": "<secret>#4"
|
||||||
|
},
|
||||||
"deviceLimit": 2,
|
"deviceLimit": 2,
|
||||||
"expiresAt": null,
|
"expiresAt": null,
|
||||||
"group": "",
|
"group": "",
|
||||||
@@ -170,7 +180,7 @@
|
|||||||
"body": {
|
"body": {
|
||||||
"nodeClients": [
|
"nodeClients": [
|
||||||
{
|
{
|
||||||
"clientUri": "vless://<uuid>#1@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=45584502b5567121&type=tcp&headerType=none&flow=xtls-rprx-vision#N1",
|
"clientUri": "vless://<uuid>#1@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#2&type=tcp&headerType=none&flow=xtls-rprx-vision#N1",
|
||||||
"createdAt": "<ts>",
|
"createdAt": "<ts>",
|
||||||
"customerId": "<cus>#1",
|
"customerId": "<cus>#1",
|
||||||
"id": "<ncl>#1",
|
"id": "<ncl>#1",
|
||||||
@@ -179,7 +189,7 @@
|
|||||||
"updatedAt": "<ts>"
|
"updatedAt": "<ts>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"clientUri": "vless://<uuid>#2@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=0adf5e3d9a002211&type=tcp&headerType=none&flow=xtls-rprx-vision#N1",
|
"clientUri": "vless://<uuid>#2@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#4&type=tcp&headerType=none&flow=xtls-rprx-vision#N1",
|
||||||
"createdAt": "<ts>",
|
"createdAt": "<ts>",
|
||||||
"customerId": "<cus>#2",
|
"customerId": "<cus>#2",
|
||||||
"id": "<ncl>#2",
|
"id": "<ncl>#2",
|
||||||
@@ -248,7 +258,7 @@
|
|||||||
"epoch": "diff-epoch-1",
|
"epoch": "diff-epoch-1",
|
||||||
"uplink": 4000
|
"uplink": 4000
|
||||||
},
|
},
|
||||||
"wsPath": "<secret>#1"
|
"wsPath": "<secret>#5"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artifacts": {},
|
"artifacts": {},
|
||||||
@@ -286,7 +296,7 @@
|
|||||||
"topology": "direct",
|
"topology": "direct",
|
||||||
"updatedAt": "<ts>",
|
"updatedAt": "<ts>",
|
||||||
"usageCounters": null,
|
"usageCounters": null,
|
||||||
"wsPath": "<secret>#2"
|
"wsPath": "<secret>#6"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -555,51 +565,55 @@
|
|||||||
"credentials": {
|
"credentials": {
|
||||||
"alice": {
|
"alice": {
|
||||||
"body": {
|
"body": {
|
||||||
"customerId": "<cus>#1",
|
"bindings": [
|
||||||
"items": [
|
|
||||||
{
|
{
|
||||||
"clientId": "<uuid>#1",
|
|
||||||
"nodeClientId": "<ncl>#1",
|
"nodeClientId": "<ncl>#1",
|
||||||
"nodeId": "<nod>#2",
|
"nodeId": "<nod>#2",
|
||||||
"nodeName": "N1",
|
"nodeName": "N1",
|
||||||
"password": "<secret>#3",
|
|
||||||
"protocol": "vless-reality-vision",
|
"protocol": "vless-reality-vision",
|
||||||
"shortId": "<secret>#4",
|
"realityPublicKey": "<rpub>#1",
|
||||||
"status": "active",
|
"status": "active",
|
||||||
"username": ""
|
"username": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"clientId": "<uuid>#3",
|
|
||||||
"nodeClientId": "<ncl>#3",
|
"nodeClientId": "<ncl>#3",
|
||||||
"nodeId": "<nod>#1",
|
"nodeId": "<nod>#1",
|
||||||
"nodeName": "N2",
|
"nodeName": "N2",
|
||||||
"password": "<secret>#5",
|
|
||||||
"protocol": "shadowsocks-2022-aes128",
|
"protocol": "shadowsocks-2022-aes128",
|
||||||
"shortId": "<secret>#6",
|
"realityPublicKey": null,
|
||||||
"status": "active",
|
"status": "active",
|
||||||
"username": ""
|
"username": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"credentials": {
|
||||||
|
"clientId": "<uuid>#1",
|
||||||
|
"password": "<secret>#1",
|
||||||
|
"shortId": "<secret>#2"
|
||||||
|
},
|
||||||
|
"customerId": "<cus>#1",
|
||||||
"subscriptionToken": "<subtoken>#1"
|
"subscriptionToken": "<subtoken>#1"
|
||||||
},
|
},
|
||||||
"status": 200
|
"status": 200
|
||||||
},
|
},
|
||||||
"bob": {
|
"bob": {
|
||||||
"body": {
|
"body": {
|
||||||
"customerId": "<cus>#2",
|
"bindings": [
|
||||||
"items": [
|
|
||||||
{
|
{
|
||||||
"clientId": "<uuid>#2",
|
|
||||||
"nodeClientId": "<ncl>#2",
|
"nodeClientId": "<ncl>#2",
|
||||||
"nodeId": "<nod>#2",
|
"nodeId": "<nod>#2",
|
||||||
"nodeName": "N1",
|
"nodeName": "N1",
|
||||||
"password": "<secret>#7",
|
|
||||||
"protocol": "vless-reality-vision",
|
"protocol": "vless-reality-vision",
|
||||||
"shortId": "<secret>#8",
|
"realityPublicKey": "<rpub>#1",
|
||||||
"status": "active",
|
"status": "active",
|
||||||
"username": ""
|
"username": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"credentials": {
|
||||||
|
"clientId": "<uuid>#2",
|
||||||
|
"password": "<secret>#3",
|
||||||
|
"shortId": "<secret>#4"
|
||||||
|
},
|
||||||
|
"customerId": "<cus>#2",
|
||||||
"subscriptionToken": "<subtoken>#2"
|
"subscriptionToken": "<subtoken>#2"
|
||||||
},
|
},
|
||||||
"status": 200
|
"status": 200
|
||||||
@@ -608,49 +622,49 @@
|
|||||||
"subscriptionsSeed": {
|
"subscriptionsSeed": {
|
||||||
"alice": {
|
"alice": {
|
||||||
"auto-clash": {
|
"auto-clash": {
|
||||||
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N1\"\n server: \"entry.example.com\"\n port: <port>#2\n type: \"vless\"\n uuid: \"<uuid>#1\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"<rpub>#1\"\n short-id: \"<secret>#4\"\n flow: \"xtls-rprx-vision\"\n - name: \"N2\"\n server: \"entry.example.com\"\n port: <port>#3\n type: \"ss\"\n cipher: \"2022-blake3-aes-128-gcm\"\n password: \"xOcB7XGgo6VpaEHZfNXrlQ==:<secret>#5\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"N1\"\n - \"N2\"\nrules:\n - \"MATCH,节点选择\"\n",
|
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N1\"\n server: \"entry.example.com\"\n port: <port>#2\n type: \"vless\"\n uuid: \"<uuid>#1\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"<rpub>#1\"\n short-id: \"<secret>#2\"\n flow: \"xtls-rprx-vision\"\n - name: \"N2\"\n server: \"entry.example.com\"\n port: <port>#3\n type: \"ss\"\n cipher: \"2022-blake3-aes-128-gcm\"\n password: \"xOcB7XGgo6VpaEHZfNXrlQ==:<secret>#1\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"N1\"\n - \"N2\"\nrules:\n - \"MATCH,节点选择\"\n",
|
||||||
"contentType": "text/yaml; charset=utf-8",
|
"contentType": "text/yaml; charset=utf-8",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
||||||
},
|
},
|
||||||
"auto-generic": {
|
"auto-generic": {
|
||||||
"body": "vless://<uuid>#1@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#4&type=tcp&headerType=none&flow=xtls-rprx-vision#N1\nss://<ssblob>#1@entry.example.com:<port>#3#N2\n",
|
"body": "vless://<uuid>#1@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#2&type=tcp&headerType=none&flow=xtls-rprx-vision#N1\nss://<ssblob>#1@entry.example.com:<port>#3#N2\n",
|
||||||
"contentType": "text/plain; charset=utf-8",
|
"contentType": "text/plain; charset=utf-8",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
||||||
},
|
},
|
||||||
"base64": {
|
"base64": {
|
||||||
"body": "vless://<uuid>#1@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#4&type=tcp&headerType=none&flow=xtls-rprx-vision#N1\nss://<ssblob>#1@entry.example.com:<port>#3#N2\n",
|
"body": "vless://<uuid>#1@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#2&type=tcp&headerType=none&flow=xtls-rprx-vision#N1\nss://<ssblob>#1@entry.example.com:<port>#3#N2\n",
|
||||||
"contentType": "text/plain; charset=utf-8",
|
"contentType": "text/plain; charset=utf-8",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
||||||
},
|
},
|
||||||
"clash": {
|
"clash": {
|
||||||
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N1\"\n server: \"entry.example.com\"\n port: <port>#2\n type: \"vless\"\n uuid: \"<uuid>#1\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"<rpub>#1\"\n short-id: \"<secret>#4\"\n flow: \"xtls-rprx-vision\"\n - name: \"N2\"\n server: \"entry.example.com\"\n port: <port>#3\n type: \"ss\"\n cipher: \"2022-blake3-aes-128-gcm\"\n password: \"xOcB7XGgo6VpaEHZfNXrlQ==:<secret>#5\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"N1\"\n - \"N2\"\nrules:\n - \"MATCH,节点选择\"\n",
|
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N1\"\n server: \"entry.example.com\"\n port: <port>#2\n type: \"vless\"\n uuid: \"<uuid>#1\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"<rpub>#1\"\n short-id: \"<secret>#2\"\n flow: \"xtls-rprx-vision\"\n - name: \"N2\"\n server: \"entry.example.com\"\n port: <port>#3\n type: \"ss\"\n cipher: \"2022-blake3-aes-128-gcm\"\n password: \"xOcB7XGgo6VpaEHZfNXrlQ==:<secret>#1\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"N1\"\n - \"N2\"\nrules:\n - \"MATCH,节点选择\"\n",
|
||||||
"contentType": "text/yaml; charset=utf-8",
|
"contentType": "text/yaml; charset=utf-8",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
||||||
},
|
},
|
||||||
"clash-smart": {
|
"clash-smart": {
|
||||||
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N1\"\n server: \"entry.example.com\"\n port: <port>#2\n type: \"vless\"\n uuid: \"<uuid>#1\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"<rpub>#1\"\n short-id: \"<secret>#4\"\n flow: \"xtls-rprx-vision\"\n - name: \"N2\"\n server: \"entry.example.com\"\n port: <port>#3\n type: \"ss\"\n cipher: \"2022-blake3-aes-128-gcm\"\n password: \"xOcB7XGgo6VpaEHZfNXrlQ==:<secret>#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",
|
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N1\"\n server: \"entry.example.com\"\n port: <port>#2\n type: \"vless\"\n uuid: \"<uuid>#1\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"<rpub>#1\"\n short-id: \"<secret>#2\"\n flow: \"xtls-rprx-vision\"\n - name: \"N2\"\n server: \"entry.example.com\"\n port: <port>#3\n type: \"ss\"\n cipher: \"2022-blake3-aes-128-gcm\"\n password: \"xOcB7XGgo6VpaEHZfNXrlQ==:<secret>#1\"\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",
|
"contentType": "text/yaml; charset=utf-8",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
||||||
},
|
},
|
||||||
"raw": {
|
"raw": {
|
||||||
"body": "vless://<uuid>#1@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#4&type=tcp&headerType=none&flow=xtls-rprx-vision#N1\nss://<ssblob>#1@entry.example.com:<port>#3#N2\n",
|
"body": "vless://<uuid>#1@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#2&type=tcp&headerType=none&flow=xtls-rprx-vision#N1\nss://<ssblob>#1@entry.example.com:<port>#3#N2\n",
|
||||||
"contentType": "text/plain; charset=utf-8",
|
"contentType": "text/plain; charset=utf-8",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
||||||
},
|
},
|
||||||
"singbox": {
|
"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\": <port>#2,\n \"uuid\": \"<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\": \"<rpub>#1\",\n \"short_id\": \"<secret>#4\"\n }\n }\n },\n {\n \"type\": \"shadowsocks\",\n \"tag\": \"node-2\",\n \"server\": \"entry.example.com\",\n \"server_port\": <port>#3,\n \"method\": \"2022-blake3-aes-128-gcm\",\n \"password\": \"xOcB7XGgo6VpaEHZfNXrlQ==:<secret>#5\"\n },\n {\n \"type\": \"direct\",\n \"tag\": \"direct\"\n }\n ],\n \"route\": {\n \"final\": \"proxy\"\n }\n}\n",
|
"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\": <port>#2,\n \"uuid\": \"<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\": \"<rpub>#1\",\n \"short_id\": \"<secret>#2\"\n }\n }\n },\n {\n \"type\": \"shadowsocks\",\n \"tag\": \"node-2\",\n \"server\": \"entry.example.com\",\n \"server_port\": <port>#3,\n \"method\": \"2022-blake3-aes-128-gcm\",\n \"password\": \"xOcB7XGgo6VpaEHZfNXrlQ==:<secret>#1\"\n },\n {\n \"type\": \"direct\",\n \"tag\": \"direct\"\n }\n ],\n \"route\": {\n \"final\": \"proxy\"\n }\n}\n",
|
||||||
"contentType": "application/json; charset=utf-8",
|
"contentType": "application/json; charset=utf-8",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
||||||
},
|
},
|
||||||
"surge": {
|
"surge": {
|
||||||
"body": "[General]\nloglevel = notify\n[Proxy]\nN2 = ss, entry.example.com, <port>#3, encrypt-method=2022-blake3-aes-128-gcm, password=xOcB7XGgo6VpaEHZfNXrlQ__:<secret>#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",
|
"body": "[General]\nloglevel = notify\n[Proxy]\nN2 = ss, entry.example.com, <port>#3, encrypt-method=2022-blake3-aes-128-gcm, password=xOcB7XGgo6VpaEHZfNXrlQ__:<secret>#1\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",
|
"contentType": "text/plain; charset=utf-8",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
||||||
@@ -658,7 +672,7 @@
|
|||||||
},
|
},
|
||||||
"bob": {
|
"bob": {
|
||||||
"auto-clash": {
|
"auto-clash": {
|
||||||
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N1\"\n server: \"entry.example.com\"\n port: <port>#2\n type: \"vless\"\n uuid: \"<uuid>#2\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"<rpub>#1\"\n short-id: \"<secret>#8\"\n flow: \"xtls-rprx-vision\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"N1\"\nrules:\n - \"MATCH,节点选择\"\n",
|
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N1\"\n server: \"entry.example.com\"\n port: <port>#2\n type: \"vless\"\n uuid: \"<uuid>#2\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"<rpub>#1\"\n short-id: \"<secret>#4\"\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",
|
"contentType": "text/yaml; charset=utf-8",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"userinfo": "upload=0; download=0; total=1073741824; expire=0"
|
"userinfo": "upload=0; download=0; total=1073741824; expire=0"
|
||||||
@@ -670,31 +684,31 @@
|
|||||||
"userinfo": ""
|
"userinfo": ""
|
||||||
},
|
},
|
||||||
"base64": {
|
"base64": {
|
||||||
"body": "vless://<uuid>#2@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#8&type=tcp&headerType=none&flow=xtls-rprx-vision#N1\n",
|
"body": "vless://<uuid>#2@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#4&type=tcp&headerType=none&flow=xtls-rprx-vision#N1\n",
|
||||||
"contentType": "text/plain; charset=utf-8",
|
"contentType": "text/plain; charset=utf-8",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"userinfo": "upload=0; download=0; total=1073741824; expire=0"
|
"userinfo": "upload=0; download=0; total=1073741824; expire=0"
|
||||||
},
|
},
|
||||||
"clash": {
|
"clash": {
|
||||||
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N1\"\n server: \"entry.example.com\"\n port: <port>#2\n type: \"vless\"\n uuid: \"<uuid>#2\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"<rpub>#1\"\n short-id: \"<secret>#8\"\n flow: \"xtls-rprx-vision\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"N1\"\nrules:\n - \"MATCH,节点选择\"\n",
|
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N1\"\n server: \"entry.example.com\"\n port: <port>#2\n type: \"vless\"\n uuid: \"<uuid>#2\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"<rpub>#1\"\n short-id: \"<secret>#4\"\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",
|
"contentType": "text/yaml; charset=utf-8",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"userinfo": "upload=0; download=0; total=1073741824; expire=0"
|
"userinfo": "upload=0; download=0; total=1073741824; expire=0"
|
||||||
},
|
},
|
||||||
"clash-smart": {
|
"clash-smart": {
|
||||||
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N1\"\n server: \"entry.example.com\"\n port: <port>#2\n type: \"vless\"\n uuid: \"<uuid>#2\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"<rpub>#1\"\n short-id: \"<secret>#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",
|
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N1\"\n server: \"entry.example.com\"\n port: <port>#2\n type: \"vless\"\n uuid: \"<uuid>#2\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"<rpub>#1\"\n short-id: \"<secret>#4\"\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",
|
"contentType": "text/yaml; charset=utf-8",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"userinfo": "upload=0; download=0; total=1073741824; expire=0"
|
"userinfo": "upload=0; download=0; total=1073741824; expire=0"
|
||||||
},
|
},
|
||||||
"raw": {
|
"raw": {
|
||||||
"body": "vless://<uuid>#2@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#8&type=tcp&headerType=none&flow=xtls-rprx-vision#N1\n",
|
"body": "vless://<uuid>#2@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#4&type=tcp&headerType=none&flow=xtls-rprx-vision#N1\n",
|
||||||
"contentType": "text/plain; charset=utf-8",
|
"contentType": "text/plain; charset=utf-8",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"userinfo": "upload=0; download=0; total=1073741824; expire=0"
|
"userinfo": "upload=0; download=0; total=1073741824; expire=0"
|
||||||
},
|
},
|
||||||
"singbox": {
|
"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\": <port>#2,\n \"uuid\": \"<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\": \"<rpub>#1\",\n \"short_id\": \"<secret>#8\"\n }\n }\n },\n {\n \"type\": \"direct\",\n \"tag\": \"direct\"\n }\n ],\n \"route\": {\n \"final\": \"proxy\"\n }\n}\n",
|
"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\": <port>#2,\n \"uuid\": \"<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\": \"<rpub>#1\",\n \"short_id\": \"<secret>#4\"\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",
|
"contentType": "application/json; charset=utf-8",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"userinfo": "upload=0; download=0; total=1073741824; expire=0"
|
"userinfo": "upload=0; download=0; total=1073741824; expire=0"
|
||||||
@@ -751,6 +765,11 @@
|
|||||||
"body": {
|
"body": {
|
||||||
"customer": {
|
"customer": {
|
||||||
"createdAt": "<ts>",
|
"createdAt": "<ts>",
|
||||||
|
"credentials": {
|
||||||
|
"clientId": "<uuid>#3",
|
||||||
|
"relayPassword": "<secret>#7",
|
||||||
|
"shortId": "<secret>#8"
|
||||||
|
},
|
||||||
"deviceLimit": 0,
|
"deviceLimit": 0,
|
||||||
"expiresAt": null,
|
"expiresAt": null,
|
||||||
"group": "",
|
"group": "",
|
||||||
@@ -869,7 +888,7 @@
|
|||||||
},
|
},
|
||||||
"private_key": "${REALITY_PRIVATE:reality-<nod>#3}",
|
"private_key": "${REALITY_PRIVATE:reality-<nod>#3}",
|
||||||
"short_id": [
|
"short_id": [
|
||||||
"<secret>#10"
|
"<secret>#8"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"server_name": "www.amd.com"
|
"server_name": "www.amd.com"
|
||||||
@@ -878,7 +897,7 @@
|
|||||||
"users": [
|
"users": [
|
||||||
{
|
{
|
||||||
"name": "ng:<cus>#3",
|
"name": "ng:<cus>#3",
|
||||||
"uuid": "<uuid>#4"
|
"uuid": "<uuid>#3"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -946,7 +965,7 @@
|
|||||||
},
|
},
|
||||||
"private_key": "${REALITY_PRIVATE:reality-<nod>#3}",
|
"private_key": "${REALITY_PRIVATE:reality-<nod>#3}",
|
||||||
"short_id": [
|
"short_id": [
|
||||||
"<secret>#10"
|
"<secret>#8"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"server_name": "www.amd.com"
|
"server_name": "www.amd.com"
|
||||||
@@ -955,7 +974,7 @@
|
|||||||
"users": [
|
"users": [
|
||||||
{
|
{
|
||||||
"name": "ng:<cus>#3",
|
"name": "ng:<cus>#3",
|
||||||
"uuid": "<uuid>#4"
|
"uuid": "<uuid>#3"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -994,7 +1013,7 @@
|
|||||||
"body": {
|
"body": {
|
||||||
"nodeClients": [
|
"nodeClients": [
|
||||||
{
|
{
|
||||||
"clientUri": "vless://<uuid>#1@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#4&type=tcp&headerType=none&flow=xtls-rprx-vision#N1",
|
"clientUri": "vless://<uuid>#1@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#2&type=tcp&headerType=none&flow=xtls-rprx-vision#N1",
|
||||||
"createdAt": "<ts>",
|
"createdAt": "<ts>",
|
||||||
"customerId": "<cus>#1",
|
"customerId": "<cus>#1",
|
||||||
"id": "<ncl>#1",
|
"id": "<ncl>#1",
|
||||||
@@ -1003,7 +1022,7 @@
|
|||||||
"updatedAt": "<ts>"
|
"updatedAt": "<ts>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"clientUri": "vless://<uuid>#2@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#8&type=tcp&headerType=none&flow=xtls-rprx-vision#N1",
|
"clientUri": "vless://<uuid>#2@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#4&type=tcp&headerType=none&flow=xtls-rprx-vision#N1",
|
||||||
"createdAt": "<ts>",
|
"createdAt": "<ts>",
|
||||||
"customerId": "<cus>#2",
|
"customerId": "<cus>#2",
|
||||||
"id": "<ncl>#2",
|
"id": "<ncl>#2",
|
||||||
@@ -1021,7 +1040,7 @@
|
|||||||
"updatedAt": "<ts>"
|
"updatedAt": "<ts>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"clientUri": "vless://<uuid>#4@s3.example.com:<port>#4?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#2&sid=<secret>#10&type=tcp&headerType=none#N3",
|
"clientUri": "vless://<uuid>#3@s3.example.com:<port>#4?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#2&sid=<secret>#8&type=tcp&headerType=none#N3",
|
||||||
"createdAt": "<ts>",
|
"createdAt": "<ts>",
|
||||||
"customerId": "<cus>#3",
|
"customerId": "<cus>#3",
|
||||||
"id": "<ncl>#4",
|
"id": "<ncl>#4",
|
||||||
@@ -1081,7 +1100,7 @@
|
|||||||
"epoch": "diff-epoch-1",
|
"epoch": "diff-epoch-1",
|
||||||
"uplink": 4000
|
"uplink": 4000
|
||||||
},
|
},
|
||||||
"wsPath": "<secret>#1"
|
"wsPath": "<secret>#5"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artifacts": {},
|
"artifacts": {},
|
||||||
@@ -1119,7 +1138,7 @@
|
|||||||
"topology": "direct",
|
"topology": "direct",
|
||||||
"updatedAt": "<ts>",
|
"updatedAt": "<ts>",
|
||||||
"usageCounters": null,
|
"usageCounters": null,
|
||||||
"wsPath": "<secret>#2"
|
"wsPath": "<secret>#6"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artifacts": {
|
"artifacts": {
|
||||||
@@ -1172,43 +1191,43 @@
|
|||||||
},
|
},
|
||||||
"carolSubscriptions": {
|
"carolSubscriptions": {
|
||||||
"auto-clash": {
|
"auto-clash": {
|
||||||
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N3\"\n server: \"s3.example.com\"\n port: <port>#4\n type: \"vless\"\n uuid: \"<uuid>#4\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"<rpub>#2\"\n short-id: \"<secret>#10\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"N3\"\nrules:\n - \"MATCH,节点选择\"\n",
|
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N3\"\n server: \"s3.example.com\"\n port: <port>#4\n type: \"vless\"\n uuid: \"<uuid>#3\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"<rpub>#2\"\n short-id: \"<secret>#8\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"N3\"\nrules:\n - \"MATCH,节点选择\"\n",
|
||||||
"contentType": "text/yaml; charset=utf-8",
|
"contentType": "text/yaml; charset=utf-8",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"userinfo": "upload=0; download=0; total=0; expire=0"
|
"userinfo": "upload=0; download=0; total=0; expire=0"
|
||||||
},
|
},
|
||||||
"auto-generic": {
|
"auto-generic": {
|
||||||
"body": "vless://<uuid>#4@s3.example.com:<port>#4?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#2&sid=<secret>#10&type=tcp&headerType=none#N3\n",
|
"body": "vless://<uuid>#3@s3.example.com:<port>#4?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#2&sid=<secret>#8&type=tcp&headerType=none#N3\n",
|
||||||
"contentType": "text/plain; charset=utf-8",
|
"contentType": "text/plain; charset=utf-8",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"userinfo": "upload=0; download=0; total=0; expire=0"
|
"userinfo": "upload=0; download=0; total=0; expire=0"
|
||||||
},
|
},
|
||||||
"base64": {
|
"base64": {
|
||||||
"body": "vless://<uuid>#4@s3.example.com:<port>#4?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#2&sid=<secret>#10&type=tcp&headerType=none#N3\n",
|
"body": "vless://<uuid>#3@s3.example.com:<port>#4?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#2&sid=<secret>#8&type=tcp&headerType=none#N3\n",
|
||||||
"contentType": "text/plain; charset=utf-8",
|
"contentType": "text/plain; charset=utf-8",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"userinfo": "upload=0; download=0; total=0; expire=0"
|
"userinfo": "upload=0; download=0; total=0; expire=0"
|
||||||
},
|
},
|
||||||
"clash": {
|
"clash": {
|
||||||
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N3\"\n server: \"s3.example.com\"\n port: <port>#4\n type: \"vless\"\n uuid: \"<uuid>#4\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"<rpub>#2\"\n short-id: \"<secret>#10\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"N3\"\nrules:\n - \"MATCH,节点选择\"\n",
|
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N3\"\n server: \"s3.example.com\"\n port: <port>#4\n type: \"vless\"\n uuid: \"<uuid>#3\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"<rpub>#2\"\n short-id: \"<secret>#8\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"N3\"\nrules:\n - \"MATCH,节点选择\"\n",
|
||||||
"contentType": "text/yaml; charset=utf-8",
|
"contentType": "text/yaml; charset=utf-8",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"userinfo": "upload=0; download=0; total=0; expire=0"
|
"userinfo": "upload=0; download=0; total=0; expire=0"
|
||||||
},
|
},
|
||||||
"clash-smart": {
|
"clash-smart": {
|
||||||
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N3\"\n server: \"s3.example.com\"\n port: <port>#4\n type: \"vless\"\n uuid: \"<uuid>#4\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"<rpub>#2\"\n short-id: \"<secret>#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",
|
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N3\"\n server: \"s3.example.com\"\n port: <port>#4\n type: \"vless\"\n uuid: \"<uuid>#3\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"<rpub>#2\"\n short-id: \"<secret>#8\"\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",
|
"contentType": "text/yaml; charset=utf-8",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"userinfo": "upload=0; download=0; total=0; expire=0"
|
"userinfo": "upload=0; download=0; total=0; expire=0"
|
||||||
},
|
},
|
||||||
"raw": {
|
"raw": {
|
||||||
"body": "vless://<uuid>#4@s3.example.com:<port>#4?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#2&sid=<secret>#10&type=tcp&headerType=none#N3\n",
|
"body": "vless://<uuid>#3@s3.example.com:<port>#4?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#2&sid=<secret>#8&type=tcp&headerType=none#N3\n",
|
||||||
"contentType": "text/plain; charset=utf-8",
|
"contentType": "text/plain; charset=utf-8",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"userinfo": "upload=0; download=0; total=0; expire=0"
|
"userinfo": "upload=0; download=0; total=0; expire=0"
|
||||||
},
|
},
|
||||||
"singbox": {
|
"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\": <port>#4,\n \"uuid\": \"<uuid>#4\",\n \"tls\": {\n \"enabled\": true,\n \"server_name\": \"www.amd.com\",\n \"reality\": {\n \"enabled\": true,\n \"public_key\": \"<rpub>#2\",\n \"short_id\": \"<secret>#10\"\n }\n }\n },\n {\n \"type\": \"direct\",\n \"tag\": \"direct\"\n }\n ],\n \"route\": {\n \"final\": \"proxy\"\n }\n}\n",
|
"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\": <port>#4,\n \"uuid\": \"<uuid>#3\",\n \"tls\": {\n \"enabled\": true,\n \"server_name\": \"www.amd.com\",\n \"reality\": {\n \"enabled\": true,\n \"public_key\": \"<rpub>#2\",\n \"short_id\": \"<secret>#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",
|
"contentType": "application/json; charset=utf-8",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"userinfo": "upload=0; download=0; total=0; expire=0"
|
"userinfo": "upload=0; download=0; total=0; expire=0"
|
||||||
@@ -1286,6 +1305,11 @@
|
|||||||
"customers": [
|
"customers": [
|
||||||
{
|
{
|
||||||
"createdAt": "<ts>",
|
"createdAt": "<ts>",
|
||||||
|
"credentials": {
|
||||||
|
"clientId": "<uuid>#1",
|
||||||
|
"relayPassword": "<secret>#1",
|
||||||
|
"shortId": "<secret>#2"
|
||||||
|
},
|
||||||
"deviceLimit": 0,
|
"deviceLimit": 0,
|
||||||
"expiresAt": null,
|
"expiresAt": null,
|
||||||
"group": "",
|
"group": "",
|
||||||
@@ -1329,6 +1353,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"createdAt": "<ts>",
|
"createdAt": "<ts>",
|
||||||
|
"credentials": {
|
||||||
|
"clientId": "<uuid>#2",
|
||||||
|
"relayPassword": "<secret>#3",
|
||||||
|
"shortId": "<secret>#4"
|
||||||
|
},
|
||||||
"deviceLimit": 2,
|
"deviceLimit": 2,
|
||||||
"expiresAt": null,
|
"expiresAt": null,
|
||||||
"group": "",
|
"group": "",
|
||||||
@@ -1353,6 +1382,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"createdAt": "<ts>",
|
"createdAt": "<ts>",
|
||||||
|
"credentials": {
|
||||||
|
"clientId": "<uuid>#3",
|
||||||
|
"relayPassword": "<secret>#7",
|
||||||
|
"shortId": "<secret>#8"
|
||||||
|
},
|
||||||
"deviceLimit": 0,
|
"deviceLimit": 0,
|
||||||
"expiresAt": null,
|
"expiresAt": null,
|
||||||
"group": "",
|
"group": "",
|
||||||
@@ -1441,7 +1475,7 @@
|
|||||||
"epoch": "golden-epoch-1",
|
"epoch": "golden-epoch-1",
|
||||||
"uplink": 8000
|
"uplink": 8000
|
||||||
},
|
},
|
||||||
"wsPath": "<secret>#1"
|
"wsPath": "<secret>#5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"status": 200
|
"status": 200
|
||||||
@@ -1485,8 +1519,8 @@
|
|||||||
},
|
},
|
||||||
"private_key": "${REALITY_PRIVATE:reality-<nod>#2}",
|
"private_key": "${REALITY_PRIVATE:reality-<nod>#2}",
|
||||||
"short_id": [
|
"short_id": [
|
||||||
"<secret>#4",
|
"<secret>#2",
|
||||||
"<secret>#8"
|
"<secret>#4"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"server_name": "www.amd.com"
|
"server_name": "www.amd.com"
|
||||||
@@ -1515,7 +1549,7 @@
|
|||||||
"outbounds": [
|
"outbounds": [
|
||||||
{
|
{
|
||||||
"method": "2022-blake3-aes-256-gcm",
|
"method": "2022-blake3-aes-256-gcm",
|
||||||
"password": "<secret>#11",
|
"password": "<secret>#10",
|
||||||
"server": "exit.example.com",
|
"server": "exit.example.com",
|
||||||
"server_port": "<port>#1",
|
"server_port": "<port>#1",
|
||||||
"tag": "ng-<nod>#2-exit",
|
"tag": "ng-<nod>#2-exit",
|
||||||
@@ -1573,8 +1607,8 @@
|
|||||||
},
|
},
|
||||||
"private_key": "${REALITY_PRIVATE:reality-<nod>#2}",
|
"private_key": "${REALITY_PRIVATE:reality-<nod>#2}",
|
||||||
"short_id": [
|
"short_id": [
|
||||||
"<secret>#4",
|
"<secret>#2",
|
||||||
"<secret>#8"
|
"<secret>#4"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"server_name": "www.amd.com"
|
"server_name": "www.amd.com"
|
||||||
@@ -1603,7 +1637,7 @@
|
|||||||
"outbounds": [
|
"outbounds": [
|
||||||
{
|
{
|
||||||
"method": "2022-blake3-aes-256-gcm",
|
"method": "2022-blake3-aes-256-gcm",
|
||||||
"password": "<secret>#11",
|
"password": "<secret>#10",
|
||||||
"server": "exit.example.com",
|
"server": "exit.example.com",
|
||||||
"server_port": "<port>#1",
|
"server_port": "<port>#1",
|
||||||
"tag": "ng-<nod>#2-exit",
|
"tag": "ng-<nod>#2-exit",
|
||||||
@@ -1650,7 +1684,7 @@
|
|||||||
"listen": "<hostip>",
|
"listen": "<hostip>",
|
||||||
"listen_port": "<port>#1",
|
"listen_port": "<port>#1",
|
||||||
"method": "2022-blake3-aes-256-gcm",
|
"method": "2022-blake3-aes-256-gcm",
|
||||||
"password": "<secret>#11",
|
"password": "<secret>#10",
|
||||||
"tag": "ng-<nod>#2-exit-in",
|
"tag": "ng-<nod>#2-exit-in",
|
||||||
"type": "shadowsocks"
|
"type": "shadowsocks"
|
||||||
}
|
}
|
||||||
@@ -1701,7 +1735,7 @@
|
|||||||
"listen": "<hostip>",
|
"listen": "<hostip>",
|
||||||
"listen_port": "<port>#1",
|
"listen_port": "<port>#1",
|
||||||
"method": "2022-blake3-aes-256-gcm",
|
"method": "2022-blake3-aes-256-gcm",
|
||||||
"password": "<secret>#11",
|
"password": "<secret>#10",
|
||||||
"tag": "ng-<nod>#2-exit-in",
|
"tag": "ng-<nod>#2-exit-in",
|
||||||
"type": "shadowsocks"
|
"type": "shadowsocks"
|
||||||
}
|
}
|
||||||
@@ -1740,7 +1774,7 @@
|
|||||||
"body": {
|
"body": {
|
||||||
"nodeClients": [
|
"nodeClients": [
|
||||||
{
|
{
|
||||||
"clientUri": "vless://<uuid>#1@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#4&type=tcp&headerType=none&flow=xtls-rprx-vision#N1-renamed",
|
"clientUri": "vless://<uuid>#1@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#2&type=tcp&headerType=none&flow=xtls-rprx-vision#N1-renamed",
|
||||||
"createdAt": "<ts>",
|
"createdAt": "<ts>",
|
||||||
"customerId": "<cus>#1",
|
"customerId": "<cus>#1",
|
||||||
"id": "<ncl>#1",
|
"id": "<ncl>#1",
|
||||||
@@ -1749,7 +1783,7 @@
|
|||||||
"updatedAt": "<ts>"
|
"updatedAt": "<ts>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"clientUri": "vless://<uuid>#2@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#8&type=tcp&headerType=none&flow=xtls-rprx-vision#N1-renamed",
|
"clientUri": "vless://<uuid>#2@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#4&type=tcp&headerType=none&flow=xtls-rprx-vision#N1-renamed",
|
||||||
"createdAt": "<ts>",
|
"createdAt": "<ts>",
|
||||||
"customerId": "<cus>#2",
|
"customerId": "<cus>#2",
|
||||||
"id": "<ncl>#2",
|
"id": "<ncl>#2",
|
||||||
@@ -1767,7 +1801,7 @@
|
|||||||
"updatedAt": "<ts>"
|
"updatedAt": "<ts>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"clientUri": "vless://<uuid>#4@s3.example.com:<port>#4?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#2&sid=<secret>#10&type=tcp&headerType=none#N3",
|
"clientUri": "vless://<uuid>#3@s3.example.com:<port>#4?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#2&sid=<secret>#8&type=tcp&headerType=none#N3",
|
||||||
"createdAt": "<ts>",
|
"createdAt": "<ts>",
|
||||||
"customerId": "<cus>#3",
|
"customerId": "<cus>#3",
|
||||||
"id": "<ncl>#4",
|
"id": "<ncl>#4",
|
||||||
@@ -1834,7 +1868,7 @@
|
|||||||
"epoch": "golden-epoch-1",
|
"epoch": "golden-epoch-1",
|
||||||
"uplink": 8000
|
"uplink": 8000
|
||||||
},
|
},
|
||||||
"wsPath": "<secret>#1"
|
"wsPath": "<secret>#5"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artifacts": {},
|
"artifacts": {},
|
||||||
@@ -1874,7 +1908,7 @@
|
|||||||
"topology": "direct",
|
"topology": "direct",
|
||||||
"updatedAt": "<ts>",
|
"updatedAt": "<ts>",
|
||||||
"usageCounters": null,
|
"usageCounters": null,
|
||||||
"wsPath": "<secret>#2"
|
"wsPath": "<secret>#6"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artifacts": {
|
"artifacts": {
|
||||||
@@ -2272,6 +2306,11 @@
|
|||||||
"customers": [
|
"customers": [
|
||||||
{
|
{
|
||||||
"createdAt": "<ts>",
|
"createdAt": "<ts>",
|
||||||
|
"credentials": {
|
||||||
|
"clientId": "<uuid>#1",
|
||||||
|
"relayPassword": "<secret>#1",
|
||||||
|
"shortId": "<secret>#2"
|
||||||
|
},
|
||||||
"deviceLimit": 0,
|
"deviceLimit": 0,
|
||||||
"expiresAt": null,
|
"expiresAt": null,
|
||||||
"group": "",
|
"group": "",
|
||||||
@@ -2309,6 +2348,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"createdAt": "<ts>",
|
"createdAt": "<ts>",
|
||||||
|
"credentials": {
|
||||||
|
"clientId": "<uuid>#2",
|
||||||
|
"relayPassword": "<secret>#3",
|
||||||
|
"shortId": "<secret>#4"
|
||||||
|
},
|
||||||
"deviceLimit": 2,
|
"deviceLimit": 2,
|
||||||
"expiresAt": null,
|
"expiresAt": null,
|
||||||
"group": "",
|
"group": "",
|
||||||
@@ -2327,6 +2371,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"createdAt": "<ts>",
|
"createdAt": "<ts>",
|
||||||
|
"credentials": {
|
||||||
|
"clientId": "<uuid>#3",
|
||||||
|
"relayPassword": "<secret>#7",
|
||||||
|
"shortId": "<secret>#8"
|
||||||
|
},
|
||||||
"deviceLimit": 0,
|
"deviceLimit": 0,
|
||||||
"expiresAt": null,
|
"expiresAt": null,
|
||||||
"group": "",
|
"group": "",
|
||||||
@@ -2406,8 +2455,8 @@
|
|||||||
},
|
},
|
||||||
"private_key": "${REALITY_PRIVATE:reality-<nod>#2}",
|
"private_key": "${REALITY_PRIVATE:reality-<nod>#2}",
|
||||||
"short_id": [
|
"short_id": [
|
||||||
"<secret>#4",
|
"<secret>#2",
|
||||||
"<secret>#8"
|
"<secret>#4"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"server_name": "www.amd.com"
|
"server_name": "www.amd.com"
|
||||||
@@ -2436,7 +2485,7 @@
|
|||||||
"outbounds": [
|
"outbounds": [
|
||||||
{
|
{
|
||||||
"method": "2022-blake3-aes-256-gcm",
|
"method": "2022-blake3-aes-256-gcm",
|
||||||
"password": "<secret>#11",
|
"password": "<secret>#10",
|
||||||
"server": "exit.example.com",
|
"server": "exit.example.com",
|
||||||
"server_port": "<port>#1",
|
"server_port": "<port>#1",
|
||||||
"tag": "ng-<nod>#2-exit",
|
"tag": "ng-<nod>#2-exit",
|
||||||
@@ -2478,7 +2527,7 @@
|
|||||||
"listen": "<hostip>",
|
"listen": "<hostip>",
|
||||||
"listen_port": "<port>#1",
|
"listen_port": "<port>#1",
|
||||||
"method": "2022-blake3-aes-256-gcm",
|
"method": "2022-blake3-aes-256-gcm",
|
||||||
"password": "<secret>#11",
|
"password": "<secret>#10",
|
||||||
"tag": "ng-<nod>#2-exit-in",
|
"tag": "ng-<nod>#2-exit-in",
|
||||||
"type": "shadowsocks"
|
"type": "shadowsocks"
|
||||||
}
|
}
|
||||||
@@ -2529,13 +2578,13 @@
|
|||||||
"listen": "<hostip>",
|
"listen": "<hostip>",
|
||||||
"listen_port": "<port>#3",
|
"listen_port": "<port>#3",
|
||||||
"method": "2022-blake3-aes-128-gcm",
|
"method": "2022-blake3-aes-128-gcm",
|
||||||
"password": "<secret>#12",
|
"password": "<secret>#11",
|
||||||
"tag": "ng-<nod>#1-in",
|
"tag": "ng-<nod>#1-in",
|
||||||
"type": "shadowsocks",
|
"type": "shadowsocks",
|
||||||
"users": [
|
"users": [
|
||||||
{
|
{
|
||||||
"name": "ng:<cus>#1",
|
"name": "ng:<cus>#1",
|
||||||
"password": "<secret>#5"
|
"password": "<secret>#12"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -2603,7 +2652,7 @@
|
|||||||
},
|
},
|
||||||
"private_key": "${REALITY_PRIVATE:reality-<nod>#3}",
|
"private_key": "${REALITY_PRIVATE:reality-<nod>#3}",
|
||||||
"short_id": [
|
"short_id": [
|
||||||
"<secret>#10"
|
"<secret>#8"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"server_name": "www.amd.com"
|
"server_name": "www.amd.com"
|
||||||
@@ -2612,7 +2661,7 @@
|
|||||||
"users": [
|
"users": [
|
||||||
{
|
{
|
||||||
"name": "ng:<cus>#3",
|
"name": "ng:<cus>#3",
|
||||||
"uuid": "<uuid>#4"
|
"uuid": "<uuid>#3"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -2680,8 +2729,8 @@
|
|||||||
},
|
},
|
||||||
"private_key": "${REALITY_PRIVATE:reality-<nod>#2}",
|
"private_key": "${REALITY_PRIVATE:reality-<nod>#2}",
|
||||||
"short_id": [
|
"short_id": [
|
||||||
"<secret>#4",
|
"<secret>#2",
|
||||||
"<secret>#8"
|
"<secret>#4"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"server_name": "www.amd.com"
|
"server_name": "www.amd.com"
|
||||||
@@ -2710,7 +2759,7 @@
|
|||||||
"outbounds": [
|
"outbounds": [
|
||||||
{
|
{
|
||||||
"method": "2022-blake3-aes-256-gcm",
|
"method": "2022-blake3-aes-256-gcm",
|
||||||
"password": "<secret>#11",
|
"password": "<secret>#10",
|
||||||
"server": "exit.example.com",
|
"server": "exit.example.com",
|
||||||
"server_port": "<port>#1",
|
"server_port": "<port>#1",
|
||||||
"tag": "ng-<nod>#2-exit",
|
"tag": "ng-<nod>#2-exit",
|
||||||
@@ -2752,7 +2801,7 @@
|
|||||||
"listen": "<hostip>",
|
"listen": "<hostip>",
|
||||||
"listen_port": "<port>#1",
|
"listen_port": "<port>#1",
|
||||||
"method": "2022-blake3-aes-256-gcm",
|
"method": "2022-blake3-aes-256-gcm",
|
||||||
"password": "<secret>#11",
|
"password": "<secret>#10",
|
||||||
"tag": "ng-<nod>#2-exit-in",
|
"tag": "ng-<nod>#2-exit-in",
|
||||||
"type": "shadowsocks"
|
"type": "shadowsocks"
|
||||||
}
|
}
|
||||||
@@ -2800,11 +2849,6 @@
|
|||||||
"nodeClients": [
|
"nodeClients": [
|
||||||
{
|
{
|
||||||
"createdAt": "<ts>",
|
"createdAt": "<ts>",
|
||||||
"credentials": {
|
|
||||||
"clientId": "<uuid>#1",
|
|
||||||
"relayPassword": "<secret>#3",
|
|
||||||
"shortId": "<secret>#4"
|
|
||||||
},
|
|
||||||
"customerId": "<cus>#1",
|
"customerId": "<cus>#1",
|
||||||
"id": "<ncl>#1",
|
"id": "<ncl>#1",
|
||||||
"nodeId": "<nod>#2",
|
"nodeId": "<nod>#2",
|
||||||
@@ -2813,11 +2857,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"createdAt": "<ts>",
|
"createdAt": "<ts>",
|
||||||
"credentials": {
|
|
||||||
"clientId": "<uuid>#2",
|
|
||||||
"relayPassword": "<secret>#7",
|
|
||||||
"shortId": "<secret>#8"
|
|
||||||
},
|
|
||||||
"customerId": "<cus>#2",
|
"customerId": "<cus>#2",
|
||||||
"id": "<ncl>#2",
|
"id": "<ncl>#2",
|
||||||
"nodeId": "<nod>#2",
|
"nodeId": "<nod>#2",
|
||||||
@@ -2826,11 +2865,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"createdAt": "<ts>",
|
"createdAt": "<ts>",
|
||||||
"credentials": {
|
|
||||||
"clientId": "<uuid>#3",
|
|
||||||
"relayPassword": "<secret>#5",
|
|
||||||
"shortId": "<secret>#6"
|
|
||||||
},
|
|
||||||
"customerId": "<cus>#1",
|
"customerId": "<cus>#1",
|
||||||
"id": "<ncl>#3",
|
"id": "<ncl>#3",
|
||||||
"nodeId": "<nod>#1",
|
"nodeId": "<nod>#1",
|
||||||
@@ -2839,11 +2873,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"createdAt": "<ts>",
|
"createdAt": "<ts>",
|
||||||
"credentials": {
|
|
||||||
"clientId": "<uuid>#4",
|
|
||||||
"relayPassword": "<secret>#13",
|
|
||||||
"shortId": "<secret>#10"
|
|
||||||
},
|
|
||||||
"customerId": "<cus>#3",
|
"customerId": "<cus>#3",
|
||||||
"id": "<ncl>#4",
|
"id": "<ncl>#4",
|
||||||
"nodeId": "<nod>#3",
|
"nodeId": "<nod>#3",
|
||||||
@@ -2861,8 +2890,8 @@
|
|||||||
"entryReady": true,
|
"entryReady": true,
|
||||||
"error": null,
|
"error": null,
|
||||||
"exitCredentials": {
|
"exitCredentials": {
|
||||||
"clientId": "<uuid>#5",
|
"clientId": "<uuid>#4",
|
||||||
"password": "<secret>#11",
|
"password": "<secret>#10",
|
||||||
"socksUser": "ng_5b8dd6b991"
|
"socksUser": "ng_5b8dd6b991"
|
||||||
},
|
},
|
||||||
"exitPort": "<port>#1",
|
"exitPort": "<port>#1",
|
||||||
@@ -2909,7 +2938,7 @@
|
|||||||
"epoch": "golden-epoch-1",
|
"epoch": "golden-epoch-1",
|
||||||
"uplink": 8000
|
"uplink": 8000
|
||||||
},
|
},
|
||||||
"wsPath": "<secret>#1"
|
"wsPath": "<secret>#5"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artifacts": {},
|
"artifacts": {},
|
||||||
@@ -2938,12 +2967,12 @@
|
|||||||
"retiredUsageEpochs": [],
|
"retiredUsageEpochs": [],
|
||||||
"serverId": "<srv>#1",
|
"serverId": "<srv>#1",
|
||||||
"sortIndex": 1,
|
"sortIndex": 1,
|
||||||
"ssServerKey": "<secret>#12",
|
"ssServerKey": "<secret>#11",
|
||||||
"status": "active",
|
"status": "active",
|
||||||
"topology": "direct",
|
"topology": "direct",
|
||||||
"updatedAt": "<ts>",
|
"updatedAt": "<ts>",
|
||||||
"usageCounters": null,
|
"usageCounters": null,
|
||||||
"wsPath": "<secret>#2"
|
"wsPath": "<secret>#6"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artifacts": {
|
"artifacts": {
|
||||||
|
|||||||
+65
-56
@@ -603,9 +603,40 @@ func CustomerSummary(data *Data, nowMs int64) map[string]any {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// credentialItems mirrors credentialItems in server.js.
|
// realityPublicKeyOf returns the node's panel-hosted Reality public key as
|
||||||
func credentialItems(d *Data, customer *Customer) []map[string]any {
|
// reported back by the agent ("" when unknown). The keypair is the server's
|
||||||
items := []map[string]any{}
|
// identity — it stays per-node even though customer credentials are shared.
|
||||||
|
func realityPublicKeyOf(node *Node) string {
|
||||||
|
if node == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if raw, ok := node.Artifacts["realityPublicKey"]; ok && len(raw) > 0 {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(raw, &s); err == nil {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// customerCredentialsView is the credentials-tab payload (round-2 item 2):
|
||||||
|
// the customer's ONE credential set, plus per-binding node facts. The
|
||||||
|
// Reality public key is shown per node because it belongs to the server,
|
||||||
|
// not to the customer.
|
||||||
|
func customerCredentialsView(d *Data, customer *Customer) map[string]any {
|
||||||
|
creds := map[string]any{"clientId": nil, "password": nil, "shortId": nil}
|
||||||
|
if customer.Credentials != nil {
|
||||||
|
if customer.Credentials.ClientID != "" {
|
||||||
|
creds["clientId"] = customer.Credentials.ClientID
|
||||||
|
}
|
||||||
|
if customer.Credentials.RelayPassword != "" {
|
||||||
|
creds["password"] = customer.Credentials.RelayPassword
|
||||||
|
}
|
||||||
|
if customer.Credentials.ShortID != "" {
|
||||||
|
creds["shortId"] = customer.Credentials.ShortID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bindings := []map[string]any{}
|
||||||
for _, nc := range d.NodeClients {
|
for _, nc := range d.NodeClients {
|
||||||
if nc.CustomerID != customer.ID {
|
if nc.CustomerID != customer.ID {
|
||||||
continue
|
continue
|
||||||
@@ -618,30 +649,26 @@ func credentialItems(d *Data, customer *Customer) []map[string]any {
|
|||||||
if node.Protocol == "socks5-auth" {
|
if node.Protocol == "socks5-auth" {
|
||||||
username = NodeEmail(customer.ID)
|
username = NodeEmail(customer.ID)
|
||||||
}
|
}
|
||||||
creds := nc.Credentials
|
var publicKey any
|
||||||
var clientID, password, shortID any
|
if key := realityPublicKeyOf(node); key != "" {
|
||||||
if creds.ClientID != "" {
|
publicKey = key
|
||||||
clientID = creds.ClientID
|
|
||||||
}
|
}
|
||||||
if creds.RelayPassword != "" {
|
bindings = append(bindings, map[string]any{
|
||||||
password = creds.RelayPassword
|
|
||||||
}
|
|
||||||
if creds.ShortID != "" {
|
|
||||||
shortID = creds.ShortID
|
|
||||||
}
|
|
||||||
items = append(items, map[string]any{
|
|
||||||
"nodeClientId": nc.ID,
|
"nodeClientId": nc.ID,
|
||||||
"nodeId": node.ID,
|
"nodeId": node.ID,
|
||||||
"nodeName": node.Name,
|
"nodeName": node.Name,
|
||||||
"protocol": node.Protocol,
|
"protocol": node.Protocol,
|
||||||
"status": nc.Status,
|
"status": nc.Status,
|
||||||
"clientId": clientID,
|
|
||||||
"password": password,
|
|
||||||
"shortId": shortID,
|
|
||||||
"username": username,
|
"username": username,
|
||||||
|
"realityPublicKey": publicKey,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return items
|
return map[string]any{
|
||||||
|
"customerId": customer.ID,
|
||||||
|
"subscriptionToken": customer.SubscriptionToken,
|
||||||
|
"credentials": creds,
|
||||||
|
"bindings": bindings,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CustomerBlockReason is customerBlockReason: "expired" / "traffic_limit" /
|
// CustomerBlockReason is customerBlockReason: "expired" / "traffic_limit" /
|
||||||
@@ -1926,6 +1953,7 @@ func (a *App) handleAdminApi(w http.ResponseWriter, r *http.Request, pathname st
|
|||||||
Notes: CleanText(body["notes"], 1000),
|
Notes: CleanText(body["notes"], 1000),
|
||||||
CreatedAt: NowISO(),
|
CreatedAt: NowISO(),
|
||||||
UpdatedAt: NowISO(),
|
UpdatedAt: NowISO(),
|
||||||
|
Credentials: func() *Credentials { c := NewCustomerCredentials(); return &c }(),
|
||||||
}
|
}
|
||||||
// Arm the monthly-reset schedule: it starts with the next
|
// Arm the monthly-reset schedule: it starts with the next
|
||||||
// reset instant, it never fires retroactively at creation.
|
// reset instant, it never fires retroactively at creation.
|
||||||
@@ -2131,54 +2159,44 @@ func (a *App) handleAdminApi(w http.ResponseWriter, r *http.Request, pathname st
|
|||||||
if customer == nil {
|
if customer == nil {
|
||||||
return true, httpErr(404, "客户不存在")
|
return true, httpErr(404, "客户不存在")
|
||||||
}
|
}
|
||||||
SendJSON(w, 200, map[string]any{
|
SendJSON(w, 200, customerCredentialsView(data, customer), nil)
|
||||||
"customerId": customer.ID,
|
|
||||||
"subscriptionToken": customer.SubscriptionToken,
|
|
||||||
"items": credentialItems(data, customer),
|
|
||||||
}, nil)
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
if params, ok := RoutePattern("/api/customers/:id/credentials/:ncid/regenerate", pathname); ok && r.Method == http.MethodPost {
|
if params, ok := RoutePattern("/api/customers/:id/credentials/regenerate", pathname); ok && r.Method == http.MethodPost {
|
||||||
var responseItem map[string]any
|
var view map[string]any
|
||||||
_, err := a.store.Transaction(func(d *Data) (any, error) {
|
_, err := a.store.Transaction(func(d *Data) (any, error) {
|
||||||
customer := findCustomer(d, params["id"])
|
customer := findCustomer(d, params["id"])
|
||||||
if customer == nil {
|
if customer == nil {
|
||||||
return nil, httpErr(404, "客户不存在")
|
return nil, httpErr(404, "客户不存在")
|
||||||
}
|
}
|
||||||
var binding *NodeClient
|
// One set for the whole customer: rotating it rotates every
|
||||||
|
// node at once, and every deployed node is rebuilt so the new
|
||||||
|
// users land on the agents.
|
||||||
|
creds := NewCustomerCredentials()
|
||||||
|
customer.Credentials = &creds
|
||||||
|
customer.UpdatedAt = NowISO()
|
||||||
|
rebuilt := map[string]bool{}
|
||||||
for _, nc := range d.NodeClients {
|
for _, nc := range d.NodeClients {
|
||||||
if nc.ID == params["ncid"] && nc.CustomerID == customer.ID {
|
if nc.CustomerID != customer.ID || nc.Status != "active" {
|
||||||
binding = nc
|
continue
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
node := findNode(d, nc.NodeID)
|
||||||
|
if node == nil || node.Status == "draft" || node.Status == "removing" || rebuilt[node.ID] {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if binding == nil {
|
rebuilt[node.ID] = true
|
||||||
return nil, httpErr(404, "凭据不存在")
|
|
||||||
}
|
|
||||||
node := findNode(d, binding.NodeID)
|
|
||||||
if node == nil {
|
|
||||||
return nil, httpErr(404, "节点不存在")
|
|
||||||
}
|
|
||||||
binding.Credentials = NewNodeClientCredentials(node.Protocol)
|
|
||||||
binding.UpdatedAt = NowISO()
|
|
||||||
if node.Status != "draft" && node.Status != "removing" {
|
|
||||||
if err := RebuildNode(d, node, false); err != nil {
|
if err := RebuildNode(d, node, false); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Audit(d, actor, "regenerate_credentials", customer.ID, map[string]any{"nodeId": node.ID, "node": node.Name})
|
Audit(d, actor, "regenerate_credentials", customer.ID, map[string]any{"nodesRebuilt": len(rebuilt)})
|
||||||
for _, entry := range credentialItems(d, customer) {
|
view = customerCredentialsView(d, customer)
|
||||||
if entry["nodeClientId"] == binding.ID {
|
|
||||||
responseItem = entry
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
SendJSON(w, 200, map[string]any{"item": responseItem}, nil)
|
SendJSON(w, 200, view, nil)
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
if params, ok := RoutePattern("/api/customers/:id/external-sources/:sid/refresh", pathname); ok && r.Method == http.MethodPost {
|
if params, ok := RoutePattern("/api/customers/:id/external-sources/:sid/refresh", pathname); ok && r.Method == http.MethodPost {
|
||||||
@@ -2437,21 +2455,12 @@ func (a *App) handleAdminApi(w http.ResponseWriter, r *http.Request, pathname st
|
|||||||
oldServerID := item.ServerID
|
oldServerID := item.ServerID
|
||||||
oldExitServerID := strVal(item.ExitServerID)
|
oldExitServerID := strVal(item.ExitServerID)
|
||||||
oldForward := forwardNode(item)
|
oldForward := forwardNode(item)
|
||||||
oldProtocol := item.Protocol
|
|
||||||
next, err := NormalizeNode(body, item, d)
|
next, err := NormalizeNode(body, item, d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
*item = *next
|
*item = *next
|
||||||
EnsureNodeSecrets(item)
|
EnsureNodeSecrets(item)
|
||||||
if item.Protocol != oldProtocol {
|
|
||||||
for _, nc := range d.NodeClients {
|
|
||||||
if nc.NodeID == item.ID {
|
|
||||||
nc.Credentials = NewNodeClientCredentials(item.Protocol)
|
|
||||||
nc.UpdatedAt = NowISO()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if item.ServerID != oldServerID {
|
if item.ServerID != oldServerID {
|
||||||
QueueJob(d, oldServerID, item.ID, "delete_resource", &JobPayload{ResourceID: item.ResourceID, Kind: "node"})
|
QueueJob(d, oldServerID, item.ID, "delete_resource", &JobPayload{ResourceID: item.ResourceID, Kind: "node"})
|
||||||
item.EntryReady = false
|
item.EntryReady = false
|
||||||
|
|||||||
@@ -239,6 +239,16 @@ func payloadWithResource(resource map[string]any, kind string, keyMaterial map[s
|
|||||||
return payload
|
return payload
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensureCustomerCredentials fills a missing customer-level credential set
|
||||||
|
// (defensive: the store-load migration and customer creation provide one).
|
||||||
|
func ensureCustomerCredentials(d *Data, customerID string) {
|
||||||
|
customer := findCustomer(d, customerID)
|
||||||
|
if customer != nil && (customer.Credentials == nil || customer.Credentials.ClientID == "") {
|
||||||
|
creds := NewCustomerCredentials()
|
||||||
|
customer.Credentials = &creds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func resourceForNode(d *Data, node *Node, clients []*NodeClient) (map[string]any, error) {
|
func resourceForNode(d *Data, node *Node, clients []*NodeClient) (map[string]any, error) {
|
||||||
server := orchFindServer(d, node.ServerID)
|
server := orchFindServer(d, node.ServerID)
|
||||||
if server == nil {
|
if server == nil {
|
||||||
@@ -262,11 +272,26 @@ func resourceForNode(d *Data, node *Node, clients []*NodeClient) (map[string]any
|
|||||||
if clients == nil {
|
if clients == nil {
|
||||||
clients = activeNodeClients(d, node)
|
clients = activeNodeClients(d, node)
|
||||||
}
|
}
|
||||||
|
// Credentials live on the customer (round-2 item 2); stored bindings
|
||||||
|
// carry none. Stamp each customer's set onto throwaway copies for the
|
||||||
|
// resource builder so the stored records stay clean.
|
||||||
|
resolved := make([]*NodeClient, len(clients))
|
||||||
|
for i, nc := range clients {
|
||||||
|
ensureCustomerCredentials(d, nc.CustomerID)
|
||||||
|
cp := *nc
|
||||||
|
if customer := findCustomer(d, nc.CustomerID); customer != nil && customer.Credentials != nil {
|
||||||
|
creds := *customer.Credentials
|
||||||
|
cp.Credentials = &creds
|
||||||
|
} else {
|
||||||
|
cp.Credentials = nil
|
||||||
|
}
|
||||||
|
resolved[i] = &cp
|
||||||
|
}
|
||||||
return BuildNodeResource(BuildNodeResourceParams{
|
return BuildNodeResource(BuildNodeResourceParams{
|
||||||
ResourceID: node.ResourceID,
|
ResourceID: node.ResourceID,
|
||||||
TagPrefix: tagForNode(node),
|
TagPrefix: tagForNode(node),
|
||||||
Node: node,
|
Node: node,
|
||||||
NodeClients: clients,
|
NodeClients: resolved,
|
||||||
Server: server,
|
Server: server,
|
||||||
BindAddress: bindAddress,
|
BindAddress: bindAddress,
|
||||||
ExitServer: exitServer,
|
ExitServer: exitServer,
|
||||||
@@ -977,11 +1002,11 @@ func assignCustomerData(d *Data, nodeID, customerID, actor string) (*NodeClient,
|
|||||||
nc.UpdatedAt = NowISO()
|
nc.UpdatedAt = NowISO()
|
||||||
} else {
|
} else {
|
||||||
now := NowISO()
|
now := NowISO()
|
||||||
|
ensureCustomerCredentials(d, customerID)
|
||||||
nc = &NodeClient{
|
nc = &NodeClient{
|
||||||
ID: NewID("ncl"),
|
ID: NewID("ncl"),
|
||||||
NodeID: nodeID,
|
NodeID: nodeID,
|
||||||
CustomerID: customerID,
|
CustomerID: customerID,
|
||||||
Credentials: NewNodeClientCredentials(node.Protocol),
|
|
||||||
Status: "active",
|
Status: "active",
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
@@ -1077,11 +1102,11 @@ func AlignCustomerNodes(d *Data, customer *Customer, desiredIds []string, actor
|
|||||||
status = "active"
|
status = "active"
|
||||||
}
|
}
|
||||||
now := NowISO()
|
now := NowISO()
|
||||||
|
ensureCustomerCredentials(d, customer.ID)
|
||||||
d.NodeClients = append(d.NodeClients, &NodeClient{
|
d.NodeClients = append(d.NodeClients, &NodeClient{
|
||||||
ID: NewID("ncl"),
|
ID: NewID("ncl"),
|
||||||
NodeID: nodeID,
|
NodeID: nodeID,
|
||||||
CustomerID: customer.ID,
|
CustomerID: customer.ID,
|
||||||
Credentials: NewNodeClientCredentials(node.Protocol),
|
|
||||||
Status: status,
|
Status: status,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
|
|||||||
+45
-15
@@ -196,18 +196,47 @@ func (q xbQuery) String() string {
|
|||||||
|
|
||||||
func NodeEmail(customerID string) string { return "ng:" + customerID }
|
func NodeEmail(customerID string) string { return "ng:" + customerID }
|
||||||
|
|
||||||
func NewNodeClientCredentials(protocol string) Credentials {
|
// NewCustomerCredentials mints the single credential set a customer uses on
|
||||||
keyBytes := 24
|
// every node (round-2 item 2, 3x-ui model). The relay password is always a
|
||||||
if cipher, ok := ShadowsocksMethods[protocol]; ok {
|
// base64 32-byte root key: SS2022 user keys are derived from it per method
|
||||||
keyBytes = cipher.KeyBytes
|
// (see EffectiveRelayPassword), so one set serves aes128 and aes256 nodes
|
||||||
}
|
// alike; every other protocol uses it verbatim.
|
||||||
|
func NewCustomerCredentials() Credentials {
|
||||||
return Credentials{
|
return Credentials{
|
||||||
ClientID: NewUUID(),
|
ClientID: NewUUID(),
|
||||||
RelayPassword: b64(keyBytes),
|
RelayPassword: b64(32),
|
||||||
ShortID: shortId(),
|
ShortID: shortId(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EffectiveRelayPassword maps a customer's relay password onto a node's
|
||||||
|
// protocol. SS2022 methods require the user key to be base64 of exactly the
|
||||||
|
// method's key length, so the user key is the deterministic prefix
|
||||||
|
// truncation of the 32-byte root key. Legacy SS methods and all other
|
||||||
|
// protocols take the password verbatim.
|
||||||
|
func EffectiveRelayPassword(protocol, relayPassword string) string {
|
||||||
|
cipher, ok := ShadowsocksMethods[protocol]
|
||||||
|
if !ok || !strings.HasPrefix(cipher.Method, "2022-blake3-") {
|
||||||
|
return relayPassword
|
||||||
|
}
|
||||||
|
raw, err := base64.StdEncoding.DecodeString(relayPassword)
|
||||||
|
if err != nil || len(raw) < cipher.KeyBytes {
|
||||||
|
return relayPassword
|
||||||
|
}
|
||||||
|
return base64.StdEncoding.EncodeToString(raw[:cipher.KeyBytes])
|
||||||
|
}
|
||||||
|
|
||||||
|
// clientCredentials resolves the credentials stamped onto a binding for
|
||||||
|
// resource building. Stored bindings no longer carry credentials (they live
|
||||||
|
// on the customer); builders receive resolved copies, and this accessor
|
||||||
|
// keeps a nil hole from becoming a panic.
|
||||||
|
func clientCredentials(nc *NodeClient) Credentials {
|
||||||
|
if nc == nil || nc.Credentials == nil {
|
||||||
|
return Credentials{}
|
||||||
|
}
|
||||||
|
return *nc.Credentials
|
||||||
|
}
|
||||||
|
|
||||||
func EnsureNodeSecrets(node *Node) {
|
func EnsureNodeSecrets(node *Node) {
|
||||||
if node.WSPath == nil || *node.WSPath == "" {
|
if node.WSPath == nil || *node.WSPath == "" {
|
||||||
node.WSPath = StrPtr("/" + hex.EncodeToString(xbRandBytes(9)))
|
node.WSPath = StrPtr("/" + hex.EncodeToString(xbRandBytes(9)))
|
||||||
@@ -352,12 +381,13 @@ func buildSingBoxInbound(tag string, node *Node, nodeClients []*NodeClient, serv
|
|||||||
users := make([]any, 0, len(nodeClients))
|
users := make([]any, 0, len(nodeClients))
|
||||||
shortIDs := make([]any, 0, len(nodeClients))
|
shortIDs := make([]any, 0, len(nodeClients))
|
||||||
for _, nc := range nodeClients {
|
for _, nc := range nodeClients {
|
||||||
user := map[string]any{"name": NodeEmail(nc.CustomerID), "uuid": nc.Credentials.ClientID}
|
creds := clientCredentials(nc)
|
||||||
|
user := map[string]any{"name": NodeEmail(nc.CustomerID), "uuid": creds.ClientID}
|
||||||
if protocol == "vless-reality-vision" {
|
if protocol == "vless-reality-vision" {
|
||||||
user["flow"] = "xtls-rprx-vision"
|
user["flow"] = "xtls-rprx-vision"
|
||||||
}
|
}
|
||||||
users = append(users, user)
|
users = append(users, user)
|
||||||
shortIDs = append(shortIDs, nc.Credentials.ShortID)
|
shortIDs = append(shortIDs, creds.ShortID)
|
||||||
}
|
}
|
||||||
destPort := node.Reality.DestPort
|
destPort := node.Reality.DestPort
|
||||||
if destPort == 0 {
|
if destPort == 0 {
|
||||||
@@ -383,7 +413,7 @@ func buildSingBoxInbound(tag string, node *Node, nodeClients []*NodeClient, serv
|
|||||||
}
|
}
|
||||||
users := make([]any, 0, len(nodeClients))
|
users := make([]any, 0, len(nodeClients))
|
||||||
for _, nc := range nodeClients {
|
for _, nc := range nodeClients {
|
||||||
users = append(users, map[string]any{"name": NodeEmail(nc.CustomerID), "uuid": nc.Credentials.ClientID})
|
users = append(users, map[string]any{"name": NodeEmail(nc.CustomerID), "uuid": clientCredentials(nc).ClientID})
|
||||||
}
|
}
|
||||||
wsPath := xbDeref(node.WSPath)
|
wsPath := xbDeref(node.WSPath)
|
||||||
if wsPath == "" {
|
if wsPath == "" {
|
||||||
@@ -406,7 +436,7 @@ func buildSingBoxInbound(tag string, node *Node, nodeClients []*NodeClient, serv
|
|||||||
if cipher, ok := ShadowsocksMethods[protocol]; ok {
|
if cipher, ok := ShadowsocksMethods[protocol]; ok {
|
||||||
users := make([]any, 0, len(nodeClients))
|
users := make([]any, 0, len(nodeClients))
|
||||||
for _, nc := range nodeClients {
|
for _, nc := range nodeClients {
|
||||||
users = append(users, map[string]any{"name": NodeEmail(nc.CustomerID), "password": nc.Credentials.RelayPassword})
|
users = append(users, map[string]any{"name": NodeEmail(nc.CustomerID), "password": EffectiveRelayPassword(protocol, clientCredentials(nc).RelayPassword)})
|
||||||
}
|
}
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"type": "shadowsocks", "tag": tag, "listen": listen, "listen_port": port,
|
"type": "shadowsocks", "tag": tag, "listen": listen, "listen_port": port,
|
||||||
@@ -417,7 +447,7 @@ func buildSingBoxInbound(tag string, node *Node, nodeClients []*NodeClient, serv
|
|||||||
if protocol == "socks5-auth" {
|
if protocol == "socks5-auth" {
|
||||||
users := make([]any, 0, len(nodeClients))
|
users := make([]any, 0, len(nodeClients))
|
||||||
for _, nc := range nodeClients {
|
for _, nc := range nodeClients {
|
||||||
users = append(users, map[string]any{"username": NodeEmail(nc.CustomerID), "password": nc.Credentials.RelayPassword})
|
users = append(users, map[string]any{"username": NodeEmail(nc.CustomerID), "password": clientCredentials(nc).RelayPassword})
|
||||||
}
|
}
|
||||||
return map[string]any{"type": "socks", "tag": tag, "listen": listen, "listen_port": port, "users": users}, nil
|
return map[string]any{"type": "socks", "tag": tag, "listen": listen, "listen_port": port, "users": users}, nil
|
||||||
}
|
}
|
||||||
@@ -428,7 +458,7 @@ func buildSingBoxInbound(tag string, node *Node, nodeClients []*NodeClient, serv
|
|||||||
}
|
}
|
||||||
users := make([]any, 0, len(nodeClients))
|
users := make([]any, 0, len(nodeClients))
|
||||||
for _, nc := range nodeClients {
|
for _, nc := range nodeClients {
|
||||||
users = append(users, map[string]any{"name": NodeEmail(nc.CustomerID), "password": nc.Credentials.RelayPassword})
|
users = append(users, map[string]any{"name": NodeEmail(nc.CustomerID), "password": clientCredentials(nc).RelayPassword})
|
||||||
}
|
}
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"type": "hysteria2", "tag": tag, "listen": listen, "listen_port": port, "users": users,
|
"type": "hysteria2", "tag": tag, "listen": listen, "listen_port": port, "users": users,
|
||||||
@@ -446,7 +476,7 @@ func buildSingBoxInbound(tag string, node *Node, nodeClients []*NodeClient, serv
|
|||||||
}
|
}
|
||||||
users := make([]any, 0, len(nodeClients))
|
users := make([]any, 0, len(nodeClients))
|
||||||
for _, nc := range nodeClients {
|
for _, nc := range nodeClients {
|
||||||
users = append(users, map[string]any{"name": NodeEmail(nc.CustomerID), "password": nc.Credentials.RelayPassword})
|
users = append(users, map[string]any{"name": NodeEmail(nc.CustomerID), "password": clientCredentials(nc).RelayPassword})
|
||||||
}
|
}
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"type": "anytls", "tag": tag, "listen": listen, "listen_port": port, "users": users,
|
"type": "anytls", "tag": tag, "listen": listen, "listen_port": port, "users": users,
|
||||||
@@ -682,9 +712,9 @@ func BuildClientURI(p BuildClientURIParams) (string, bool) {
|
|||||||
return fmt.Sprintf("vless://%s@%s:%d?%s#%s", p.ClientID, host, p.RelayPort, q.String(), label), true
|
return fmt.Sprintf("vless://%s@%s:%d?%s#%s", p.ClientID, host, p.RelayPort, q.String(), label), true
|
||||||
}
|
}
|
||||||
if cipher, ok := ShadowsocksMethods[protocol]; ok {
|
if cipher, ok := ShadowsocksMethods[protocol]; ok {
|
||||||
password := p.RelayPassword
|
password := EffectiveRelayPassword(protocol, p.RelayPassword)
|
||||||
if strings.HasPrefix(cipher.Method, "2022-blake3-") && p.SSServerKey != "" {
|
if strings.HasPrefix(cipher.Method, "2022-blake3-") && p.SSServerKey != "" {
|
||||||
password = p.SSServerKey + ":" + p.RelayPassword
|
password = p.SSServerKey + ":" + password
|
||||||
}
|
}
|
||||||
user := base64.RawURLEncoding.EncodeToString([]byte(cipher.Method + ":" + password))
|
user := base64.RawURLEncoding.EncodeToString([]byte(cipher.Method + ":" + password))
|
||||||
return fmt.Sprintf("ss://%s@%s:%d#%s", user, host, p.RelayPort, label), true
|
return fmt.Sprintf("ss://%s@%s:%d#%s", user, host, p.RelayPort, label), true
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ func TestGoldenResources(t *testing.T) {
|
|||||||
ExitCredentials: &ExitCredentials{ClientID: "11111111-2222-3333-4444-0000000000ff", Password: exitPassword, SocksUser: "ng_0123456789"},
|
ExitCredentials: &ExitCredentials{ClientID: "11111111-2222-3333-4444-0000000000ff", Password: exitPassword, SocksUser: "ng_0123456789"},
|
||||||
}
|
}
|
||||||
rfClients := []*NodeClient{
|
rfClients := []*NodeClient{
|
||||||
{NodeID: "nod_rf", CustomerID: "cus_1", Credentials: Credentials{ClientID: "11111111-2222-3333-4444-000000000001", RelayPassword: "unused1", ShortID: "abcd0001ef01"}},
|
{NodeID: "nod_rf", CustomerID: "cus_1", Credentials: &Credentials{ClientID: "11111111-2222-3333-4444-000000000001", RelayPassword: "unused1", ShortID: "abcd0001ef01"}},
|
||||||
{NodeID: "nod_rf", CustomerID: "cus_2", Credentials: Credentials{ClientID: "11111111-2222-3333-4444-000000000002", RelayPassword: "unused2", ShortID: "abcd0002ef01"}},
|
{NodeID: "nod_rf", CustomerID: "cus_2", Credentials: &Credentials{ClientID: "11111111-2222-3333-4444-000000000002", RelayPassword: "unused2", ShortID: "abcd0002ef01"}},
|
||||||
}
|
}
|
||||||
entryServer := &Server{ID: "srv_entry", PublicAddress: "203.0.113.10"}
|
entryServer := &Server{ID: "srv_entry", PublicAddress: "203.0.113.10"}
|
||||||
exitServer := &Server{ID: "srv_exit", PublicAddress: "exit.example.com"}
|
exitServer := &Server{ID: "srv_exit", PublicAddress: "exit.example.com"}
|
||||||
@@ -102,7 +102,7 @@ func TestGoldenResources(t *testing.T) {
|
|||||||
SSServerKey: StrPtr("CwwNDg8QERITFBUWFxgZGg=="),
|
SSServerKey: StrPtr("CwwNDg8QERITFBUWFxgZGg=="),
|
||||||
}
|
}
|
||||||
ssClients := []*NodeClient{
|
ssClients := []*NodeClient{
|
||||||
{NodeID: "nod_ss", CustomerID: "cus_1", Credentials: Credentials{ClientID: "11111111-2222-3333-4444-000000000001", RelayPassword: "FRYXGBkaGxwdHh8gISIjJA==", ShortID: "abcd0001ef01"}},
|
{NodeID: "nod_ss", CustomerID: "cus_1", Credentials: &Credentials{ClientID: "11111111-2222-3333-4444-000000000001", RelayPassword: "FRYXGBkaGxwdHh8gISIjJA==", ShortID: "abcd0001ef01"}},
|
||||||
}
|
}
|
||||||
ssServer := &Server{ID: "srv_entry", PublicAddress: "203.0.113.11"}
|
ssServer := &Server{ID: "srv_entry", PublicAddress: "203.0.113.11"}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package main
|
|||||||
// the same serialization here.
|
// the same serialization here.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@@ -133,10 +134,70 @@ func (s *Store) Init() error {
|
|||||||
if err := validateData(&data); err != nil {
|
if err := validateData(&data); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if MigrateCustomerCredentials(&data) {
|
||||||
|
// Persist the converged credential model immediately, so a crash
|
||||||
|
// before the next transaction cannot resurrect per-binding sets.
|
||||||
|
if err := s.persist(&data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
s.Data = &data
|
s.Data = &data
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MigrateCustomerCredentials converges legacy per-binding credentials into
|
||||||
|
// each customer's single set (round-2 item 2; the 3x-ui model of one
|
||||||
|
// credential row per client). Deterministic rules:
|
||||||
|
// - A customer with a complete stored set keeps it.
|
||||||
|
// - Otherwise the set is copied from the customer's first binding (in
|
||||||
|
// stored order) that carries credentials; a customer with no usable
|
||||||
|
// binding credentials gets a fresh set.
|
||||||
|
// - The relay password must be a base64 32-byte root key, because SS2022
|
||||||
|
// user keys derive from it by prefix truncation (EffectiveRelayPassword).
|
||||||
|
// If the converged password does not decode to exactly 32 bytes, only
|
||||||
|
// the password is regenerated — clientId and shortId are preserved.
|
||||||
|
// - Every binding's stored credentials are then cleared, so bindings stop
|
||||||
|
// carrying credentials at all.
|
||||||
|
//
|
||||||
|
// Returns true when anything changed.
|
||||||
|
func MigrateCustomerCredentials(d *Data) bool {
|
||||||
|
changed := false
|
||||||
|
for _, customer := range d.Customers {
|
||||||
|
var creds *Credentials
|
||||||
|
if customer.Credentials != nil && customer.Credentials.ClientID != "" {
|
||||||
|
copied := *customer.Credentials
|
||||||
|
creds = &copied
|
||||||
|
} else {
|
||||||
|
for _, nc := range d.NodeClients {
|
||||||
|
if nc.CustomerID == customer.ID && nc.Credentials != nil && nc.Credentials.ClientID != "" {
|
||||||
|
copied := *nc.Credentials
|
||||||
|
creds = &copied
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if creds == nil {
|
||||||
|
fresh := NewCustomerCredentials()
|
||||||
|
creds = &fresh
|
||||||
|
}
|
||||||
|
if raw, err := base64.StdEncoding.DecodeString(creds.RelayPassword); err != nil || len(raw) != 32 {
|
||||||
|
creds.RelayPassword = b64(32)
|
||||||
|
}
|
||||||
|
old := customer.Credentials
|
||||||
|
if old == nil || old.ClientID != creds.ClientID || old.RelayPassword != creds.RelayPassword || old.ShortID != creds.ShortID {
|
||||||
|
customer.Credentials = creds
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, nc := range d.NodeClients {
|
||||||
|
if nc.Credentials != nil {
|
||||||
|
nc.Credentials = nil
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed
|
||||||
|
}
|
||||||
|
|
||||||
// Snapshot returns a deep copy of the current data (JS structuredClone).
|
// Snapshot returns a deep copy of the current data (JS structuredClone).
|
||||||
func (s *Store) Snapshot() *Data {
|
func (s *Store) Snapshot() *Data {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
|
|||||||
@@ -108,13 +108,17 @@ func NodeClientURI(d *Data, nc *NodeClient) string {
|
|||||||
if networkMode == "" {
|
if networkMode == "" {
|
||||||
networkMode = "ipv4"
|
networkMode = "ipv4"
|
||||||
}
|
}
|
||||||
|
creds := Credentials{}
|
||||||
|
if customer := findCustomer(d, nc.CustomerID); customer != nil && customer.Credentials != nil {
|
||||||
|
creds = *customer.Credentials
|
||||||
|
}
|
||||||
uri, ok := BuildClientURI(BuildClientURIParams{
|
uri, ok := BuildClientURI(BuildClientURIParams{
|
||||||
Protocol: node.Protocol,
|
Protocol: node.Protocol,
|
||||||
RelayServer: server,
|
RelayServer: server,
|
||||||
RelayPort: node.Port,
|
RelayPort: node.Port,
|
||||||
ClientID: nc.Credentials.ClientID,
|
ClientID: creds.ClientID,
|
||||||
RelayPassword: nc.Credentials.RelayPassword,
|
RelayPassword: creds.RelayPassword,
|
||||||
ShortID: nc.Credentials.ShortID,
|
ShortID: creds.ShortID,
|
||||||
WSPath: xbDeref(node.WSPath),
|
WSPath: xbDeref(node.WSPath),
|
||||||
SocksUser: NodeEmail(nc.CustomerID),
|
SocksUser: NodeEmail(nc.CustomerID),
|
||||||
Reality: node.Reality,
|
Reality: node.Reality,
|
||||||
|
|||||||
@@ -36,26 +36,25 @@ func goldenSubData() *Data {
|
|||||||
Reality: &RealityConfig{KeyID: "reality-nod_rv", ServerName: "www.apple.com", DestPort: 443},
|
Reality: &RealityConfig{KeyID: "reality-nod_rv", ServerName: "www.apple.com", DestPort: 443},
|
||||||
Artifacts: art("FIXEDPBKRvKey0123456789abcdefghijklmnopqrstu")},
|
Artifacts: art("FIXEDPBKRvKey0123456789abcdefghijklmnopqrstu")},
|
||||||
}
|
}
|
||||||
nc := func(nodeID, clientID, relayPassword, shortID string) *NodeClient {
|
nc := func(nodeID string) *NodeClient {
|
||||||
return &NodeClient{
|
return &NodeClient{NodeID: nodeID, CustomerID: "cus_1", Status: "active"}
|
||||||
NodeID: nodeID, CustomerID: "cus_1", Status: "active",
|
|
||||||
Credentials: Credentials{ClientID: clientID, RelayPassword: relayPassword, ShortID: shortID},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
clients := []*NodeClient{
|
clients := []*NodeClient{
|
||||||
nc("nod_rf", "11111111-2222-3333-4444-000000000001", "unused", "abcd0001ef01"),
|
nc("nod_rf"), nc("nod_ss"), nc("nod_hy"), nc("nod_any"),
|
||||||
nc("nod_ss", "11111111-2222-3333-4444-000000000003", "FRYXGBkaGxwdHh8gISIjJA==", "abcd0003ef01"),
|
nc("nod_ws"), nc("nod_sk"), nc("nod_rv"),
|
||||||
nc("nod_hy", "11111111-2222-3333-4444-000000000004", "HB0eHyAhIiMkJSYnKCkqKywtLi8wMTIz", "abcd0004ef01"),
|
|
||||||
nc("nod_any", "11111111-2222-3333-4444-000000000005", "IyQlJicoKSorLC0uLzAxMjM0NTY3ODk6", "abcd0005ef01"),
|
|
||||||
nc("nod_ws", "11111111-2222-3333-4444-000000000006", "unused", "abcd0006ef01"),
|
|
||||||
nc("nod_sk", "11111111-2222-3333-4444-000000000007", "MTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkdI", "abcd0007ef01"),
|
|
||||||
nc("nod_rv", "11111111-2222-3333-4444-000000000008", "unused", "abcd0008ef01"),
|
|
||||||
}
|
}
|
||||||
return &Data{
|
return &Data{
|
||||||
Servers: []*Server{entryServer, v6Server},
|
Servers: []*Server{entryServer, v6Server},
|
||||||
Nodes: nodes,
|
Nodes: nodes,
|
||||||
NodeClients: clients,
|
NodeClients: clients,
|
||||||
Customers: []*Customer{{ID: "cus_1"}},
|
// One credential set for the whole customer (round-2 item 2). The
|
||||||
|
// values are the old RF binding's uuid/shortId and the old SS
|
||||||
|
// binding's password, so the RF and SS entries are byte-stable
|
||||||
|
// against the previous golden file; every other entry now carries
|
||||||
|
// this same set.
|
||||||
|
Customers: []*Customer{{ID: "cus_1", Credentials: &Credentials{
|
||||||
|
ClientID: "11111111-2222-3333-4444-000000000001", RelayPassword: "FRYXGBkaGxwdHh8gISIjJA==", ShortID: "abcd0001ef01",
|
||||||
|
}}},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+32
-32
@@ -2,7 +2,7 @@
|
|||||||
"entries": [
|
"entries": [
|
||||||
{
|
{
|
||||||
"nodeId": "nod_rf",
|
"nodeId": "nod_rf",
|
||||||
"clientUri": "vless://11111111-2222-3333-4444-000000000001@entry.example.com:23456?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=FIXEDPBKRfKey0123456789abcdefghijklmnopqrstu&sid=abcd0001ef01&type=tcp&headerType=none&flow=xtls-rprx-vision#RF-Reality"
|
"clientUri": "vless://11111111-2222-3333-4444-000000000001@entry.example.com:23456?encryption=none\u0026security=reality\u0026sni=www.amd.com\u0026fp=chrome\u0026pbk=FIXEDPBKRfKey0123456789abcdefghijklmnopqrstu\u0026sid=abcd0001ef01\u0026type=tcp\u0026headerType=none\u0026flow=xtls-rprx-vision#RF-Reality"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"nodeId": "nod_ss",
|
"nodeId": "nod_ss",
|
||||||
@@ -10,56 +10,56 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"nodeId": "nod_hy",
|
"nodeId": "nod_hy",
|
||||||
"clientUri": "hysteria2://HB0eHyAhIiMkJSYnKCkqKywtLi8wMTIz@entry.example.com:23459?sni=tls.example.com&alpn=h3#HY2"
|
"clientUri": "hysteria2://FRYXGBkaGxwdHh8gISIjJA%3D%3D@entry.example.com:23459?sni=tls.example.com\u0026alpn=h3#HY2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"nodeId": "nod_any",
|
"nodeId": "nod_any",
|
||||||
"clientUri": "anytls://IyQlJicoKSorLC0uLzAxMjM0NTY3ODk6@entry.example.com:23460?sni=tls.example.com#AnyTLS"
|
"clientUri": "anytls://FRYXGBkaGxwdHh8gISIjJA%3D%3D@entry.example.com:23460?sni=tls.example.com#AnyTLS"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"nodeId": "nod_ws",
|
"nodeId": "nod_ws",
|
||||||
"clientUri": "vless://11111111-2222-3333-4444-000000000006@entry.example.com:23461?encryption=none&security=tls&type=ws&path=%2Fwspath123&sni=tls.example.com#WS-TLS"
|
"clientUri": "vless://11111111-2222-3333-4444-000000000001@entry.example.com:23461?encryption=none\u0026security=tls\u0026type=ws\u0026path=%2Fwspath123\u0026sni=tls.example.com#WS-TLS"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"nodeId": "nod_sk",
|
"nodeId": "nod_sk",
|
||||||
"clientUri": "socks5://ng%3Acus_1:MTIzNDU2Nzg5Ojs8PT4%2FQEFCQ0RFRkdI@entry.example.com:23462#Socks"
|
"clientUri": "socks5://ng%3Acus_1:FRYXGBkaGxwdHh8gISIjJA%3D%3D@entry.example.com:23462#Socks"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"nodeId": "nod_rv",
|
"nodeId": "nod_rv",
|
||||||
"clientUri": "vless://11111111-2222-3333-4444-000000000008@v6.example.com:23463?encryption=none&security=reality&sni=www.apple.com&fp=chrome&pbk=FIXEDPBKRvKey0123456789abcdefghijklmnopqrstu&sid=abcd0008ef01&type=tcp&headerType=none#RV-Plain"
|
"clientUri": "vless://11111111-2222-3333-4444-000000000001@v6.example.com:23463?encryption=none\u0026security=reality\u0026sni=www.apple.com\u0026fp=chrome\u0026pbk=FIXEDPBKRvKey0123456789abcdefghijklmnopqrstu\u0026sid=abcd0001ef01\u0026type=tcp\u0026headerType=none#RV-Plain"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"formats": {
|
"formats": {
|
||||||
"raw": {
|
|
||||||
"body": "vless://11111111-2222-3333-4444-000000000001@entry.example.com:23456?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=FIXEDPBKRfKey0123456789abcdefghijklmnopqrstu&sid=abcd0001ef01&type=tcp&headerType=none&flow=xtls-rprx-vision#RF-Reality\nss://MjAyMi1ibGFrZTMtYWVzLTEyOC1nY206Q3d3TkRnOFFFUklURkJVV0Z4Z1pHZz09OkZSWVhHQmthR3h3ZEhoOGdJU0lqSkE9PQ@entry.example.com:23458#SS-Direct\nhysteria2://HB0eHyAhIiMkJSYnKCkqKywtLi8wMTIz@entry.example.com:23459?sni=tls.example.com&alpn=h3#HY2\nanytls://IyQlJicoKSorLC0uLzAxMjM0NTY3ODk6@entry.example.com:23460?sni=tls.example.com#AnyTLS\nvless://11111111-2222-3333-4444-000000000006@entry.example.com:23461?encryption=none&security=tls&type=ws&path=%2Fwspath123&sni=tls.example.com#WS-TLS\nsocks5://ng%3Acus_1:MTIzNDU2Nzg5Ojs8PT4%2FQEFCQ0RFRkdI@entry.example.com:23462#Socks\nvless://11111111-2222-3333-4444-000000000008@v6.example.com:23463?encryption=none&security=reality&sni=www.apple.com&fp=chrome&pbk=FIXEDPBKRvKey0123456789abcdefghijklmnopqrstu&sid=abcd0008ef01&type=tcp&headerType=none#RV-Plain\n",
|
|
||||||
"contentType": "text/plain; charset=utf-8"
|
|
||||||
},
|
|
||||||
"base64": {
|
|
||||||
"body": "dmxlc3M6Ly8xMTExMTExMS0yMjIyLTMzMzMtNDQ0NC0wMDAwMDAwMDAwMDFAZW50cnkuZXhhbXBsZS5jb206MjM0NTY/ZW5jcnlwdGlvbj1ub25lJnNlY3VyaXR5PXJlYWxpdHkmc25pPXd3dy5hbWQuY29tJmZwPWNocm9tZSZwYms9RklYRURQQktSZktleTAxMjM0NTY3ODlhYmNkZWZnaGlqa2xtbm9wcXJzdHUmc2lkPWFiY2QwMDAxZWYwMSZ0eXBlPXRjcCZoZWFkZXJUeXBlPW5vbmUmZmxvdz14dGxzLXJwcngtdmlzaW9uI1JGLVJlYWxpdHkKc3M6Ly9NakF5TWkxaWJHRnJaVE10WVdWekxURXlPQzFuWTIwNlEzZDNUa1JuT0ZGRlVrbFVSa0pWVjBaNFoxcEhaejA5T2taU1dWaEhRbXRoUjNoM1pFaG9PR2RKVTBscVNrRTlQUUBlbnRyeS5leGFtcGxlLmNvbToyMzQ1OCNTUy1EaXJlY3QKaHlzdGVyaWEyOi8vSEIwZUh5QWhJaU1rSlNZbktDa3FLeXd0TGk4d01USXpAZW50cnkuZXhhbXBsZS5jb206MjM0NTk/c25pPXRscy5leGFtcGxlLmNvbSZhbHBuPWgzI0hZMgphbnl0bHM6Ly9JeVFsSmljb0tTb3JMQzB1THpBeE1qTTBOVFkzT0RrNkBlbnRyeS5leGFtcGxlLmNvbToyMzQ2MD9zbmk9dGxzLmV4YW1wbGUuY29tI0FueVRMUwp2bGVzczovLzExMTExMTExLTIyMjItMzMzMy00NDQ0LTAwMDAwMDAwMDAwNkBlbnRyeS5leGFtcGxlLmNvbToyMzQ2MT9lbmNyeXB0aW9uPW5vbmUmc2VjdXJpdHk9dGxzJnR5cGU9d3MmcGF0aD0lMkZ3c3BhdGgxMjMmc25pPXRscy5leGFtcGxlLmNvbSNXUy1UTFMKc29ja3M1Oi8vbmclM0FjdXNfMTpNVEl6TkRVMk56ZzVPanM4UFQ0JTJGUUVGQ1EwUkZSa2RJQGVudHJ5LmV4YW1wbGUuY29tOjIzNDYyI1NvY2tzCnZsZXNzOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtMDAwMDAwMDAwMDA4QHY2LmV4YW1wbGUuY29tOjIzNDYzP2VuY3J5cHRpb249bm9uZSZzZWN1cml0eT1yZWFsaXR5JnNuaT13d3cuYXBwbGUuY29tJmZwPWNocm9tZSZwYms9RklYRURQQktSdktleTAxMjM0NTY3ODlhYmNkZWZnaGlqa2xtbm9wcXJzdHUmc2lkPWFiY2QwMDA4ZWYwMSZ0eXBlPXRjcCZoZWFkZXJUeXBlPW5vbmUjUlYtUGxhaW4K",
|
|
||||||
"contentType": "text/plain; charset=utf-8"
|
|
||||||
},
|
|
||||||
"clash": {
|
|
||||||
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"RF-Reality\"\n server: \"entry.example.com\"\n port: 23456\n type: \"vless\"\n uuid: \"11111111-2222-3333-4444-000000000001\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"FIXEDPBKRfKey0123456789abcdefghijklmnopqrstu\"\n short-id: \"abcd0001ef01\"\n flow: \"xtls-rprx-vision\"\n - name: \"SS-Direct\"\n server: \"entry.example.com\"\n port: 23458\n type: \"ss\"\n cipher: \"2022-blake3-aes-128-gcm\"\n password: \"CwwNDg8QERITFBUWFxgZGg==:FRYXGBkaGxwdHh8gISIjJA==\"\n - name: \"HY2\"\n server: \"entry.example.com\"\n port: 23459\n type: \"hysteria2\"\n password: \"HB0eHyAhIiMkJSYnKCkqKywtLi8wMTIz\"\n sni: \"tls.example.com\"\n alpn: [\"h3\"]\n - name: \"AnyTLS\"\n server: \"entry.example.com\"\n port: 23460\n type: \"anytls\"\n password: \"IyQlJicoKSorLC0uLzAxMjM0NTY3ODk6\"\n sni: \"tls.example.com\"\n udp: true\n - name: \"WS-TLS\"\n server: \"entry.example.com\"\n port: 23461\n type: \"vless\"\n uuid: \"11111111-2222-3333-4444-000000000006\"\n tls: true\n network: \"ws\"\n ws-opts: \n path: \"/wspath123\"\n servername: \"tls.example.com\"\n - name: \"Socks\"\n server: \"entry.example.com\"\n port: 23462\n type: \"socks5\"\n username: \"ng:cus_1\"\n password: \"MTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkdI\"\n - name: \"RV-Plain\"\n server: \"v6.example.com\"\n port: 23463\n type: \"vless\"\n uuid: \"11111111-2222-3333-4444-000000000008\"\n tls: true\n network: \"tcp\"\n servername: \"www.apple.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"FIXEDPBKRvKey0123456789abcdefghijklmnopqrstu\"\n short-id: \"abcd0008ef01\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"RF-Reality\"\n - \"SS-Direct\"\n - \"HY2\"\n - \"AnyTLS\"\n - \"WS-TLS\"\n - \"Socks\"\n - \"RV-Plain\"\nrules:\n - \"MATCH,节点选择\"\n",
|
|
||||||
"contentType": "text/yaml; charset=utf-8"
|
|
||||||
},
|
|
||||||
"clash-smart": {
|
|
||||||
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"RF-Reality\"\n server: \"entry.example.com\"\n port: 23456\n type: \"vless\"\n uuid: \"11111111-2222-3333-4444-000000000001\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"FIXEDPBKRfKey0123456789abcdefghijklmnopqrstu\"\n short-id: \"abcd0001ef01\"\n flow: \"xtls-rprx-vision\"\n - name: \"SS-Direct\"\n server: \"entry.example.com\"\n port: 23458\n type: \"ss\"\n cipher: \"2022-blake3-aes-128-gcm\"\n password: \"CwwNDg8QERITFBUWFxgZGg==:FRYXGBkaGxwdHh8gISIjJA==\"\n - name: \"HY2\"\n server: \"entry.example.com\"\n port: 23459\n type: \"hysteria2\"\n password: \"HB0eHyAhIiMkJSYnKCkqKywtLi8wMTIz\"\n sni: \"tls.example.com\"\n alpn: [\"h3\"]\n - name: \"AnyTLS\"\n server: \"entry.example.com\"\n port: 23460\n type: \"anytls\"\n password: \"IyQlJicoKSorLC0uLzAxMjM0NTY3ODk6\"\n sni: \"tls.example.com\"\n udp: true\n - name: \"WS-TLS\"\n server: \"entry.example.com\"\n port: 23461\n type: \"vless\"\n uuid: \"11111111-2222-3333-4444-000000000006\"\n tls: true\n network: \"ws\"\n ws-opts: \n path: \"/wspath123\"\n servername: \"tls.example.com\"\n - name: \"Socks\"\n server: \"entry.example.com\"\n port: 23462\n type: \"socks5\"\n username: \"ng:cus_1\"\n password: \"MTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkdI\"\n - name: \"RV-Plain\"\n server: \"v6.example.com\"\n port: 23463\n type: \"vless\"\n uuid: \"11111111-2222-3333-4444-000000000008\"\n tls: true\n network: \"tcp\"\n servername: \"www.apple.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"FIXEDPBKRvKey0123456789abcdefghijklmnopqrstu\"\n short-id: \"abcd0008ef01\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"自动选择\"\n - \"RF-Reality\"\n - \"SS-Direct\"\n - \"HY2\"\n - \"AnyTLS\"\n - \"WS-TLS\"\n - \"Socks\"\n - \"RV-Plain\"\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 - \"RF-Reality\"\n - \"SS-Direct\"\n - \"HY2\"\n - \"AnyTLS\"\n - \"WS-TLS\"\n - \"Socks\"\n - \"RV-Plain\"\n - name: \"流媒体\"\n type: select\n proxies:\n - \"节点选择\"\n - \"自动选择\"\n - \"RF-Reality\"\n - \"SS-Direct\"\n - \"HY2\"\n - \"AnyTLS\"\n - \"WS-TLS\"\n - \"Socks\"\n - \"RV-Plain\"\n - name: \"AI 服务\"\n type: select\n proxies:\n - \"节点选择\"\n - \"自动选择\"\n - \"RF-Reality\"\n - \"SS-Direct\"\n - \"HY2\"\n - \"AnyTLS\"\n - \"WS-TLS\"\n - \"Socks\"\n - \"RV-Plain\"\n - name: \"Meta 服务\"\n type: select\n proxies:\n - \"节点选择\"\n - \"自动选择\"\n - \"RF-Reality\"\n - \"SS-Direct\"\n - \"HY2\"\n - \"AnyTLS\"\n - \"WS-TLS\"\n - \"Socks\"\n - \"RV-Plain\"\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"
|
|
||||||
},
|
|
||||||
"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 \"node-3\",\n \"node-4\",\n \"node-5\",\n \"node-6\",\n \"node-7\"\n ]\n },\n {\n \"type\": \"vless\",\n \"tag\": \"node-1\",\n \"server\": \"entry.example.com\",\n \"server_port\": 23456,\n \"uuid\": \"11111111-2222-3333-4444-000000000001\",\n \"flow\": \"xtls-rprx-vision\",\n \"tls\": {\n \"enabled\": true,\n \"server_name\": \"www.amd.com\",\n \"reality\": {\n \"enabled\": true,\n \"public_key\": \"FIXEDPBKRfKey0123456789abcdefghijklmnopqrstu\",\n \"short_id\": \"abcd0001ef01\"\n }\n }\n },\n {\n \"type\": \"shadowsocks\",\n \"tag\": \"node-2\",\n \"server\": \"entry.example.com\",\n \"server_port\": 23458,\n \"method\": \"2022-blake3-aes-128-gcm\",\n \"password\": \"CwwNDg8QERITFBUWFxgZGg==:FRYXGBkaGxwdHh8gISIjJA==\"\n },\n {\n \"type\": \"hysteria2\",\n \"tag\": \"node-3\",\n \"server\": \"entry.example.com\",\n \"server_port\": 23459,\n \"password\": \"HB0eHyAhIiMkJSYnKCkqKywtLi8wMTIz\",\n \"tls\": {\n \"enabled\": true,\n \"server_name\": \"tls.example.com\",\n \"alpn\": [\n \"h3\"\n ]\n }\n },\n {\n \"type\": \"anytls\",\n \"tag\": \"node-4\",\n \"server\": \"entry.example.com\",\n \"server_port\": 23460,\n \"password\": \"IyQlJicoKSorLC0uLzAxMjM0NTY3ODk6\",\n \"tls\": {\n \"enabled\": true,\n \"server_name\": \"tls.example.com\"\n }\n },\n {\n \"type\": \"vless\",\n \"tag\": \"node-5\",\n \"server\": \"entry.example.com\",\n \"server_port\": 23461,\n \"uuid\": \"11111111-2222-3333-4444-000000000006\",\n \"transport\": {\n \"type\": \"ws\",\n \"path\": \"/wspath123\"\n },\n \"tls\": {\n \"enabled\": true,\n \"server_name\": \"tls.example.com\"\n }\n },\n {\n \"type\": \"socks\",\n \"tag\": \"node-6\",\n \"server\": \"entry.example.com\",\n \"server_port\": 23462,\n \"version\": \"5\",\n \"username\": \"ng:cus_1\",\n \"password\": \"MTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkdI\"\n },\n {\n \"type\": \"vless\",\n \"tag\": \"node-7\",\n \"server\": \"v6.example.com\",\n \"server_port\": 23463,\n \"uuid\": \"11111111-2222-3333-4444-000000000008\",\n \"tls\": {\n \"enabled\": true,\n \"server_name\": \"www.apple.com\",\n \"reality\": {\n \"enabled\": true,\n \"public_key\": \"FIXEDPBKRvKey0123456789abcdefghijklmnopqrstu\",\n \"short_id\": \"abcd0008ef01\"\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"
|
|
||||||
},
|
|
||||||
"surge": {
|
|
||||||
"body": "[General]\nloglevel = notify\n[Proxy]\nSS-Direct = ss, entry.example.com, 23458, encrypt-method=2022-blake3-aes-128-gcm, password=CwwNDg8QERITFBUWFxgZGg__:FRYXGBkaGxwdHh8gISIjJA__\nSocks = socks5, entry.example.com, 23462, ng:cus_1, MTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkdI\n[Proxy Group]\n节点选择 = select, SS-Direct, Socks, DIRECT\n自动选择 = url-test, SS-Direct, Socks, url=http://www.gstatic.com/generate_204, interval=600\n[Rule]\nGEOIP,CN,DIRECT\nFINAL,节点选择\n",
|
|
||||||
"contentType": "text/plain; charset=utf-8"
|
|
||||||
},
|
|
||||||
"auto-clash-ua": {
|
"auto-clash-ua": {
|
||||||
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"RF-Reality\"\n server: \"entry.example.com\"\n port: 23456\n type: \"vless\"\n uuid: \"11111111-2222-3333-4444-000000000001\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"FIXEDPBKRfKey0123456789abcdefghijklmnopqrstu\"\n short-id: \"abcd0001ef01\"\n flow: \"xtls-rprx-vision\"\n - name: \"SS-Direct\"\n server: \"entry.example.com\"\n port: 23458\n type: \"ss\"\n cipher: \"2022-blake3-aes-128-gcm\"\n password: \"CwwNDg8QERITFBUWFxgZGg==:FRYXGBkaGxwdHh8gISIjJA==\"\n - name: \"HY2\"\n server: \"entry.example.com\"\n port: 23459\n type: \"hysteria2\"\n password: \"HB0eHyAhIiMkJSYnKCkqKywtLi8wMTIz\"\n sni: \"tls.example.com\"\n alpn: [\"h3\"]\n - name: \"AnyTLS\"\n server: \"entry.example.com\"\n port: 23460\n type: \"anytls\"\n password: \"IyQlJicoKSorLC0uLzAxMjM0NTY3ODk6\"\n sni: \"tls.example.com\"\n udp: true\n - name: \"WS-TLS\"\n server: \"entry.example.com\"\n port: 23461\n type: \"vless\"\n uuid: \"11111111-2222-3333-4444-000000000006\"\n tls: true\n network: \"ws\"\n ws-opts: \n path: \"/wspath123\"\n servername: \"tls.example.com\"\n - name: \"Socks\"\n server: \"entry.example.com\"\n port: 23462\n type: \"socks5\"\n username: \"ng:cus_1\"\n password: \"MTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkdI\"\n - name: \"RV-Plain\"\n server: \"v6.example.com\"\n port: 23463\n type: \"vless\"\n uuid: \"11111111-2222-3333-4444-000000000008\"\n tls: true\n network: \"tcp\"\n servername: \"www.apple.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"FIXEDPBKRvKey0123456789abcdefghijklmnopqrstu\"\n short-id: \"abcd0008ef01\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"RF-Reality\"\n - \"SS-Direct\"\n - \"HY2\"\n - \"AnyTLS\"\n - \"WS-TLS\"\n - \"Socks\"\n - \"RV-Plain\"\nrules:\n - \"MATCH,节点选择\"\n",
|
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"RF-Reality\"\n server: \"entry.example.com\"\n port: 23456\n type: \"vless\"\n uuid: \"11111111-2222-3333-4444-000000000001\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"FIXEDPBKRfKey0123456789abcdefghijklmnopqrstu\"\n short-id: \"abcd0001ef01\"\n flow: \"xtls-rprx-vision\"\n - name: \"SS-Direct\"\n server: \"entry.example.com\"\n port: 23458\n type: \"ss\"\n cipher: \"2022-blake3-aes-128-gcm\"\n password: \"CwwNDg8QERITFBUWFxgZGg==:FRYXGBkaGxwdHh8gISIjJA==\"\n - name: \"HY2\"\n server: \"entry.example.com\"\n port: 23459\n type: \"hysteria2\"\n password: \"FRYXGBkaGxwdHh8gISIjJA==\"\n sni: \"tls.example.com\"\n alpn: [\"h3\"]\n - name: \"AnyTLS\"\n server: \"entry.example.com\"\n port: 23460\n type: \"anytls\"\n password: \"FRYXGBkaGxwdHh8gISIjJA==\"\n sni: \"tls.example.com\"\n udp: true\n - name: \"WS-TLS\"\n server: \"entry.example.com\"\n port: 23461\n type: \"vless\"\n uuid: \"11111111-2222-3333-4444-000000000001\"\n tls: true\n network: \"ws\"\n ws-opts: \n path: \"/wspath123\"\n servername: \"tls.example.com\"\n - name: \"Socks\"\n server: \"entry.example.com\"\n port: 23462\n type: \"socks5\"\n username: \"ng:cus_1\"\n password: \"FRYXGBkaGxwdHh8gISIjJA==\"\n - name: \"RV-Plain\"\n server: \"v6.example.com\"\n port: 23463\n type: \"vless\"\n uuid: \"11111111-2222-3333-4444-000000000001\"\n tls: true\n network: \"tcp\"\n servername: \"www.apple.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"FIXEDPBKRvKey0123456789abcdefghijklmnopqrstu\"\n short-id: \"abcd0001ef01\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"RF-Reality\"\n - \"SS-Direct\"\n - \"HY2\"\n - \"AnyTLS\"\n - \"WS-TLS\"\n - \"Socks\"\n - \"RV-Plain\"\nrules:\n - \"MATCH,节点选择\"\n",
|
||||||
"contentType": "text/yaml; charset=utf-8"
|
"contentType": "text/yaml; charset=utf-8"
|
||||||
},
|
},
|
||||||
"auto-plain-ua": {
|
"auto-plain-ua": {
|
||||||
"body": "dmxlc3M6Ly8xMTExMTExMS0yMjIyLTMzMzMtNDQ0NC0wMDAwMDAwMDAwMDFAZW50cnkuZXhhbXBsZS5jb206MjM0NTY/ZW5jcnlwdGlvbj1ub25lJnNlY3VyaXR5PXJlYWxpdHkmc25pPXd3dy5hbWQuY29tJmZwPWNocm9tZSZwYms9RklYRURQQktSZktleTAxMjM0NTY3ODlhYmNkZWZnaGlqa2xtbm9wcXJzdHUmc2lkPWFiY2QwMDAxZWYwMSZ0eXBlPXRjcCZoZWFkZXJUeXBlPW5vbmUmZmxvdz14dGxzLXJwcngtdmlzaW9uI1JGLVJlYWxpdHkKc3M6Ly9NakF5TWkxaWJHRnJaVE10WVdWekxURXlPQzFuWTIwNlEzZDNUa1JuT0ZGRlVrbFVSa0pWVjBaNFoxcEhaejA5T2taU1dWaEhRbXRoUjNoM1pFaG9PR2RKVTBscVNrRTlQUUBlbnRyeS5leGFtcGxlLmNvbToyMzQ1OCNTUy1EaXJlY3QKaHlzdGVyaWEyOi8vSEIwZUh5QWhJaU1rSlNZbktDa3FLeXd0TGk4d01USXpAZW50cnkuZXhhbXBsZS5jb206MjM0NTk/c25pPXRscy5leGFtcGxlLmNvbSZhbHBuPWgzI0hZMgphbnl0bHM6Ly9JeVFsSmljb0tTb3JMQzB1THpBeE1qTTBOVFkzT0RrNkBlbnRyeS5leGFtcGxlLmNvbToyMzQ2MD9zbmk9dGxzLmV4YW1wbGUuY29tI0FueVRMUwp2bGVzczovLzExMTExMTExLTIyMjItMzMzMy00NDQ0LTAwMDAwMDAwMDAwNkBlbnRyeS5leGFtcGxlLmNvbToyMzQ2MT9lbmNyeXB0aW9uPW5vbmUmc2VjdXJpdHk9dGxzJnR5cGU9d3MmcGF0aD0lMkZ3c3BhdGgxMjMmc25pPXRscy5leGFtcGxlLmNvbSNXUy1UTFMKc29ja3M1Oi8vbmclM0FjdXNfMTpNVEl6TkRVMk56ZzVPanM4UFQ0JTJGUUVGQ1EwUkZSa2RJQGVudHJ5LmV4YW1wbGUuY29tOjIzNDYyI1NvY2tzCnZsZXNzOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtMDAwMDAwMDAwMDA4QHY2LmV4YW1wbGUuY29tOjIzNDYzP2VuY3J5cHRpb249bm9uZSZzZWN1cml0eT1yZWFsaXR5JnNuaT13d3cuYXBwbGUuY29tJmZwPWNocm9tZSZwYms9RklYRURQQktSdktleTAxMjM0NTY3ODlhYmNkZWZnaGlqa2xtbm9wcXJzdHUmc2lkPWFiY2QwMDA4ZWYwMSZ0eXBlPXRjcCZoZWFkZXJUeXBlPW5vbmUjUlYtUGxhaW4K",
|
"body": "dmxlc3M6Ly8xMTExMTExMS0yMjIyLTMzMzMtNDQ0NC0wMDAwMDAwMDAwMDFAZW50cnkuZXhhbXBsZS5jb206MjM0NTY/ZW5jcnlwdGlvbj1ub25lJnNlY3VyaXR5PXJlYWxpdHkmc25pPXd3dy5hbWQuY29tJmZwPWNocm9tZSZwYms9RklYRURQQktSZktleTAxMjM0NTY3ODlhYmNkZWZnaGlqa2xtbm9wcXJzdHUmc2lkPWFiY2QwMDAxZWYwMSZ0eXBlPXRjcCZoZWFkZXJUeXBlPW5vbmUmZmxvdz14dGxzLXJwcngtdmlzaW9uI1JGLVJlYWxpdHkKc3M6Ly9NakF5TWkxaWJHRnJaVE10WVdWekxURXlPQzFuWTIwNlEzZDNUa1JuT0ZGRlVrbFVSa0pWVjBaNFoxcEhaejA5T2taU1dWaEhRbXRoUjNoM1pFaG9PR2RKVTBscVNrRTlQUUBlbnRyeS5leGFtcGxlLmNvbToyMzQ1OCNTUy1EaXJlY3QKaHlzdGVyaWEyOi8vRlJZWEdCa2FHeHdkSGg4Z0lTSWpKQSUzRCUzREBlbnRyeS5leGFtcGxlLmNvbToyMzQ1OT9zbmk9dGxzLmV4YW1wbGUuY29tJmFscG49aDMjSFkyCmFueXRsczovL0ZSWVhHQmthR3h3ZEhoOGdJU0lqSkElM0QlM0RAZW50cnkuZXhhbXBsZS5jb206MjM0NjA/c25pPXRscy5leGFtcGxlLmNvbSNBbnlUTFMKdmxlc3M6Ly8xMTExMTExMS0yMjIyLTMzMzMtNDQ0NC0wMDAwMDAwMDAwMDFAZW50cnkuZXhhbXBsZS5jb206MjM0NjE/ZW5jcnlwdGlvbj1ub25lJnNlY3VyaXR5PXRscyZ0eXBlPXdzJnBhdGg9JTJGd3NwYXRoMTIzJnNuaT10bHMuZXhhbXBsZS5jb20jV1MtVExTCnNvY2tzNTovL25nJTNBY3VzXzE6RlJZWEdCa2FHeHdkSGg4Z0lTSWpKQSUzRCUzREBlbnRyeS5leGFtcGxlLmNvbToyMzQ2MiNTb2Nrcwp2bGVzczovLzExMTExMTExLTIyMjItMzMzMy00NDQ0LTAwMDAwMDAwMDAwMUB2Ni5leGFtcGxlLmNvbToyMzQ2Mz9lbmNyeXB0aW9uPW5vbmUmc2VjdXJpdHk9cmVhbGl0eSZzbmk9d3d3LmFwcGxlLmNvbSZmcD1jaHJvbWUmcGJrPUZJWEVEUEJLUnZLZXkwMTIzNDU2Nzg5YWJjZGVmZ2hpamtsbW5vcHFyc3R1JnNpZD1hYmNkMDAwMWVmMDEmdHlwZT10Y3AmaGVhZGVyVHlwZT1ub25lI1JWLVBsYWluCg==",
|
||||||
|
"contentType": "text/plain; charset=utf-8"
|
||||||
|
},
|
||||||
|
"base64": {
|
||||||
|
"body": "dmxlc3M6Ly8xMTExMTExMS0yMjIyLTMzMzMtNDQ0NC0wMDAwMDAwMDAwMDFAZW50cnkuZXhhbXBsZS5jb206MjM0NTY/ZW5jcnlwdGlvbj1ub25lJnNlY3VyaXR5PXJlYWxpdHkmc25pPXd3dy5hbWQuY29tJmZwPWNocm9tZSZwYms9RklYRURQQktSZktleTAxMjM0NTY3ODlhYmNkZWZnaGlqa2xtbm9wcXJzdHUmc2lkPWFiY2QwMDAxZWYwMSZ0eXBlPXRjcCZoZWFkZXJUeXBlPW5vbmUmZmxvdz14dGxzLXJwcngtdmlzaW9uI1JGLVJlYWxpdHkKc3M6Ly9NakF5TWkxaWJHRnJaVE10WVdWekxURXlPQzFuWTIwNlEzZDNUa1JuT0ZGRlVrbFVSa0pWVjBaNFoxcEhaejA5T2taU1dWaEhRbXRoUjNoM1pFaG9PR2RKVTBscVNrRTlQUUBlbnRyeS5leGFtcGxlLmNvbToyMzQ1OCNTUy1EaXJlY3QKaHlzdGVyaWEyOi8vRlJZWEdCa2FHeHdkSGg4Z0lTSWpKQSUzRCUzREBlbnRyeS5leGFtcGxlLmNvbToyMzQ1OT9zbmk9dGxzLmV4YW1wbGUuY29tJmFscG49aDMjSFkyCmFueXRsczovL0ZSWVhHQmthR3h3ZEhoOGdJU0lqSkElM0QlM0RAZW50cnkuZXhhbXBsZS5jb206MjM0NjA/c25pPXRscy5leGFtcGxlLmNvbSNBbnlUTFMKdmxlc3M6Ly8xMTExMTExMS0yMjIyLTMzMzMtNDQ0NC0wMDAwMDAwMDAwMDFAZW50cnkuZXhhbXBsZS5jb206MjM0NjE/ZW5jcnlwdGlvbj1ub25lJnNlY3VyaXR5PXRscyZ0eXBlPXdzJnBhdGg9JTJGd3NwYXRoMTIzJnNuaT10bHMuZXhhbXBsZS5jb20jV1MtVExTCnNvY2tzNTovL25nJTNBY3VzXzE6RlJZWEdCa2FHeHdkSGg4Z0lTSWpKQSUzRCUzREBlbnRyeS5leGFtcGxlLmNvbToyMzQ2MiNTb2Nrcwp2bGVzczovLzExMTExMTExLTIyMjItMzMzMy00NDQ0LTAwMDAwMDAwMDAwMUB2Ni5leGFtcGxlLmNvbToyMzQ2Mz9lbmNyeXB0aW9uPW5vbmUmc2VjdXJpdHk9cmVhbGl0eSZzbmk9d3d3LmFwcGxlLmNvbSZmcD1jaHJvbWUmcGJrPUZJWEVEUEJLUnZLZXkwMTIzNDU2Nzg5YWJjZGVmZ2hpamtsbW5vcHFyc3R1JnNpZD1hYmNkMDAwMWVmMDEmdHlwZT10Y3AmaGVhZGVyVHlwZT1ub25lI1JWLVBsYWluCg==",
|
||||||
|
"contentType": "text/plain; charset=utf-8"
|
||||||
|
},
|
||||||
|
"clash": {
|
||||||
|
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"RF-Reality\"\n server: \"entry.example.com\"\n port: 23456\n type: \"vless\"\n uuid: \"11111111-2222-3333-4444-000000000001\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"FIXEDPBKRfKey0123456789abcdefghijklmnopqrstu\"\n short-id: \"abcd0001ef01\"\n flow: \"xtls-rprx-vision\"\n - name: \"SS-Direct\"\n server: \"entry.example.com\"\n port: 23458\n type: \"ss\"\n cipher: \"2022-blake3-aes-128-gcm\"\n password: \"CwwNDg8QERITFBUWFxgZGg==:FRYXGBkaGxwdHh8gISIjJA==\"\n - name: \"HY2\"\n server: \"entry.example.com\"\n port: 23459\n type: \"hysteria2\"\n password: \"FRYXGBkaGxwdHh8gISIjJA==\"\n sni: \"tls.example.com\"\n alpn: [\"h3\"]\n - name: \"AnyTLS\"\n server: \"entry.example.com\"\n port: 23460\n type: \"anytls\"\n password: \"FRYXGBkaGxwdHh8gISIjJA==\"\n sni: \"tls.example.com\"\n udp: true\n - name: \"WS-TLS\"\n server: \"entry.example.com\"\n port: 23461\n type: \"vless\"\n uuid: \"11111111-2222-3333-4444-000000000001\"\n tls: true\n network: \"ws\"\n ws-opts: \n path: \"/wspath123\"\n servername: \"tls.example.com\"\n - name: \"Socks\"\n server: \"entry.example.com\"\n port: 23462\n type: \"socks5\"\n username: \"ng:cus_1\"\n password: \"FRYXGBkaGxwdHh8gISIjJA==\"\n - name: \"RV-Plain\"\n server: \"v6.example.com\"\n port: 23463\n type: \"vless\"\n uuid: \"11111111-2222-3333-4444-000000000001\"\n tls: true\n network: \"tcp\"\n servername: \"www.apple.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"FIXEDPBKRvKey0123456789abcdefghijklmnopqrstu\"\n short-id: \"abcd0001ef01\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"RF-Reality\"\n - \"SS-Direct\"\n - \"HY2\"\n - \"AnyTLS\"\n - \"WS-TLS\"\n - \"Socks\"\n - \"RV-Plain\"\nrules:\n - \"MATCH,节点选择\"\n",
|
||||||
|
"contentType": "text/yaml; charset=utf-8"
|
||||||
|
},
|
||||||
|
"clash-smart": {
|
||||||
|
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"RF-Reality\"\n server: \"entry.example.com\"\n port: 23456\n type: \"vless\"\n uuid: \"11111111-2222-3333-4444-000000000001\"\n tls: true\n network: \"tcp\"\n servername: \"www.amd.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"FIXEDPBKRfKey0123456789abcdefghijklmnopqrstu\"\n short-id: \"abcd0001ef01\"\n flow: \"xtls-rprx-vision\"\n - name: \"SS-Direct\"\n server: \"entry.example.com\"\n port: 23458\n type: \"ss\"\n cipher: \"2022-blake3-aes-128-gcm\"\n password: \"CwwNDg8QERITFBUWFxgZGg==:FRYXGBkaGxwdHh8gISIjJA==\"\n - name: \"HY2\"\n server: \"entry.example.com\"\n port: 23459\n type: \"hysteria2\"\n password: \"FRYXGBkaGxwdHh8gISIjJA==\"\n sni: \"tls.example.com\"\n alpn: [\"h3\"]\n - name: \"AnyTLS\"\n server: \"entry.example.com\"\n port: 23460\n type: \"anytls\"\n password: \"FRYXGBkaGxwdHh8gISIjJA==\"\n sni: \"tls.example.com\"\n udp: true\n - name: \"WS-TLS\"\n server: \"entry.example.com\"\n port: 23461\n type: \"vless\"\n uuid: \"11111111-2222-3333-4444-000000000001\"\n tls: true\n network: \"ws\"\n ws-opts: \n path: \"/wspath123\"\n servername: \"tls.example.com\"\n - name: \"Socks\"\n server: \"entry.example.com\"\n port: 23462\n type: \"socks5\"\n username: \"ng:cus_1\"\n password: \"FRYXGBkaGxwdHh8gISIjJA==\"\n - name: \"RV-Plain\"\n server: \"v6.example.com\"\n port: 23463\n type: \"vless\"\n uuid: \"11111111-2222-3333-4444-000000000001\"\n tls: true\n network: \"tcp\"\n servername: \"www.apple.com\"\n client-fingerprint: \"chrome\"\n reality-opts: \n public-key: \"FIXEDPBKRvKey0123456789abcdefghijklmnopqrstu\"\n short-id: \"abcd0001ef01\"\nproxy-groups:\n - name: \"节点选择\"\n type: select\n proxies:\n - \"自动选择\"\n - \"RF-Reality\"\n - \"SS-Direct\"\n - \"HY2\"\n - \"AnyTLS\"\n - \"WS-TLS\"\n - \"Socks\"\n - \"RV-Plain\"\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 - \"RF-Reality\"\n - \"SS-Direct\"\n - \"HY2\"\n - \"AnyTLS\"\n - \"WS-TLS\"\n - \"Socks\"\n - \"RV-Plain\"\n - name: \"流媒体\"\n type: select\n proxies:\n - \"节点选择\"\n - \"自动选择\"\n - \"RF-Reality\"\n - \"SS-Direct\"\n - \"HY2\"\n - \"AnyTLS\"\n - \"WS-TLS\"\n - \"Socks\"\n - \"RV-Plain\"\n - name: \"AI 服务\"\n type: select\n proxies:\n - \"节点选择\"\n - \"自动选择\"\n - \"RF-Reality\"\n - \"SS-Direct\"\n - \"HY2\"\n - \"AnyTLS\"\n - \"WS-TLS\"\n - \"Socks\"\n - \"RV-Plain\"\n - name: \"Meta 服务\"\n type: select\n proxies:\n - \"节点选择\"\n - \"自动选择\"\n - \"RF-Reality\"\n - \"SS-Direct\"\n - \"HY2\"\n - \"AnyTLS\"\n - \"WS-TLS\"\n - \"Socks\"\n - \"RV-Plain\"\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"
|
||||||
|
},
|
||||||
|
"raw": {
|
||||||
|
"body": "vless://11111111-2222-3333-4444-000000000001@entry.example.com:23456?encryption=none\u0026security=reality\u0026sni=www.amd.com\u0026fp=chrome\u0026pbk=FIXEDPBKRfKey0123456789abcdefghijklmnopqrstu\u0026sid=abcd0001ef01\u0026type=tcp\u0026headerType=none\u0026flow=xtls-rprx-vision#RF-Reality\nss://MjAyMi1ibGFrZTMtYWVzLTEyOC1nY206Q3d3TkRnOFFFUklURkJVV0Z4Z1pHZz09OkZSWVhHQmthR3h3ZEhoOGdJU0lqSkE9PQ@entry.example.com:23458#SS-Direct\nhysteria2://FRYXGBkaGxwdHh8gISIjJA%3D%3D@entry.example.com:23459?sni=tls.example.com\u0026alpn=h3#HY2\nanytls://FRYXGBkaGxwdHh8gISIjJA%3D%3D@entry.example.com:23460?sni=tls.example.com#AnyTLS\nvless://11111111-2222-3333-4444-000000000001@entry.example.com:23461?encryption=none\u0026security=tls\u0026type=ws\u0026path=%2Fwspath123\u0026sni=tls.example.com#WS-TLS\nsocks5://ng%3Acus_1:FRYXGBkaGxwdHh8gISIjJA%3D%3D@entry.example.com:23462#Socks\nvless://11111111-2222-3333-4444-000000000001@v6.example.com:23463?encryption=none\u0026security=reality\u0026sni=www.apple.com\u0026fp=chrome\u0026pbk=FIXEDPBKRvKey0123456789abcdefghijklmnopqrstu\u0026sid=abcd0001ef01\u0026type=tcp\u0026headerType=none#RV-Plain\n",
|
||||||
|
"contentType": "text/plain; charset=utf-8"
|
||||||
|
},
|
||||||
|
"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 \"node-3\",\n \"node-4\",\n \"node-5\",\n \"node-6\",\n \"node-7\"\n ]\n },\n {\n \"type\": \"vless\",\n \"tag\": \"node-1\",\n \"server\": \"entry.example.com\",\n \"server_port\": 23456,\n \"uuid\": \"11111111-2222-3333-4444-000000000001\",\n \"flow\": \"xtls-rprx-vision\",\n \"tls\": {\n \"enabled\": true,\n \"server_name\": \"www.amd.com\",\n \"reality\": {\n \"enabled\": true,\n \"public_key\": \"FIXEDPBKRfKey0123456789abcdefghijklmnopqrstu\",\n \"short_id\": \"abcd0001ef01\"\n }\n }\n },\n {\n \"type\": \"shadowsocks\",\n \"tag\": \"node-2\",\n \"server\": \"entry.example.com\",\n \"server_port\": 23458,\n \"method\": \"2022-blake3-aes-128-gcm\",\n \"password\": \"CwwNDg8QERITFBUWFxgZGg==:FRYXGBkaGxwdHh8gISIjJA==\"\n },\n {\n \"type\": \"hysteria2\",\n \"tag\": \"node-3\",\n \"server\": \"entry.example.com\",\n \"server_port\": 23459,\n \"password\": \"FRYXGBkaGxwdHh8gISIjJA==\",\n \"tls\": {\n \"enabled\": true,\n \"server_name\": \"tls.example.com\",\n \"alpn\": [\n \"h3\"\n ]\n }\n },\n {\n \"type\": \"anytls\",\n \"tag\": \"node-4\",\n \"server\": \"entry.example.com\",\n \"server_port\": 23460,\n \"password\": \"FRYXGBkaGxwdHh8gISIjJA==\",\n \"tls\": {\n \"enabled\": true,\n \"server_name\": \"tls.example.com\"\n }\n },\n {\n \"type\": \"vless\",\n \"tag\": \"node-5\",\n \"server\": \"entry.example.com\",\n \"server_port\": 23461,\n \"uuid\": \"11111111-2222-3333-4444-000000000001\",\n \"transport\": {\n \"type\": \"ws\",\n \"path\": \"/wspath123\"\n },\n \"tls\": {\n \"enabled\": true,\n \"server_name\": \"tls.example.com\"\n }\n },\n {\n \"type\": \"socks\",\n \"tag\": \"node-6\",\n \"server\": \"entry.example.com\",\n \"server_port\": 23462,\n \"version\": \"5\",\n \"username\": \"ng:cus_1\",\n \"password\": \"FRYXGBkaGxwdHh8gISIjJA==\"\n },\n {\n \"type\": \"vless\",\n \"tag\": \"node-7\",\n \"server\": \"v6.example.com\",\n \"server_port\": 23463,\n \"uuid\": \"11111111-2222-3333-4444-000000000001\",\n \"tls\": {\n \"enabled\": true,\n \"server_name\": \"www.apple.com\",\n \"reality\": {\n \"enabled\": true,\n \"public_key\": \"FIXEDPBKRvKey0123456789abcdefghijklmnopqrstu\",\n \"short_id\": \"abcd0001ef01\"\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"
|
||||||
|
},
|
||||||
|
"surge": {
|
||||||
|
"body": "[General]\nloglevel = notify\n[Proxy]\nSS-Direct = ss, entry.example.com, 23458, encrypt-method=2022-blake3-aes-128-gcm, password=CwwNDg8QERITFBUWFxgZGg__:FRYXGBkaGxwdHh8gISIjJA__\nSocks = socks5, entry.example.com, 23462, ng:cus_1, FRYXGBkaGxwdHh8gISIjJA__\n[Proxy Group]\n节点选择 = select, SS-Direct, Socks, DIRECT\n自动选择 = url-test, SS-Direct, Socks, url=http://www.gstatic.com/generate_204, interval=600\n[Rule]\nGEOIP,CN,DIRECT\nFINAL,节点选择\n",
|
||||||
"contentType": "text/plain; charset=utf-8"
|
"contentType": "text/plain; charset=utf-8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-1
@@ -560,6 +560,10 @@ type Customer struct {
|
|||||||
ServerUsage map[string]*UsageSlot `json:"serverUsage,omitempty"`
|
ServerUsage map[string]*UsageSlot `json:"serverUsage,omitempty"`
|
||||||
LastUsageAt *string `json:"lastUsageAt,omitempty"`
|
LastUsageAt *string `json:"lastUsageAt,omitempty"`
|
||||||
LastOnlineAt *string `json:"lastOnlineAt,omitempty"`
|
LastOnlineAt *string `json:"lastOnlineAt,omitempty"`
|
||||||
|
// The customer's single credential set, shared by all of their node
|
||||||
|
// bindings (round-2 item 2). Populated by the store-load migration for
|
||||||
|
// legacy records and at creation for new ones.
|
||||||
|
Credentials *Credentials `json:"credentials,omitempty"`
|
||||||
ExternalSources []ExtSource `json:"externalSources,omitempty"`
|
ExternalSources []ExtSource `json:"externalSources,omitempty"`
|
||||||
ManualLinks []ManualLink `json:"manualLinks,omitempty"`
|
ManualLinks []ManualLink `json:"manualLinks,omitempty"`
|
||||||
Extra extraFields `json:"-"`
|
Extra extraFields `json:"-"`
|
||||||
@@ -784,7 +788,11 @@ type NodeClient struct {
|
|||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
NodeID string `json:"nodeId"`
|
NodeID string `json:"nodeId"`
|
||||||
CustomerID string `json:"customerId"`
|
CustomerID string `json:"customerId"`
|
||||||
Credentials Credentials `json:"credentials"`
|
// Deprecated: credentials live on the Customer (round-2 item 2). This
|
||||||
|
// field is only a migration input — the store-load migration converges
|
||||||
|
// it into Customer.Credentials and clears it, so it disappears from
|
||||||
|
// disk on the next save. Builders receive resolved copies instead.
|
||||||
|
Credentials *Credentials `json:"credentials,omitempty"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
CreatedAt string `json:"createdAt"`
|
CreatedAt string `json:"createdAt"`
|
||||||
UpdatedAt string `json:"updatedAt"`
|
UpdatedAt string `json:"updatedAt"`
|
||||||
|
|||||||
@@ -53,8 +53,8 @@ func TestAggregateEntriesOrderAndDedup(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Servers: []*Server{{ID: "srv_1", Name: "S", PublicAddress: "s.example.com"}},
|
Servers: []*Server{{ID: "srv_1", Name: "S", PublicAddress: "s.example.com"}},
|
||||||
NodeClients: []*NodeClient{
|
NodeClients: []*NodeClient{
|
||||||
{ID: "ncl_b", NodeID: "nod_b", CustomerID: "cus_1", Status: "active", Credentials: Credentials{RelayPassword: "pw-b"}},
|
{ID: "ncl_b", NodeID: "nod_b", CustomerID: "cus_1", Status: "active"},
|
||||||
{ID: "ncl_a", NodeID: "nod_a", CustomerID: "cus_1", Status: "active", Credentials: Credentials{RelayPassword: "pw-a"}},
|
{ID: "ncl_a", NodeID: "nod_a", CustomerID: "cus_1", Status: "active"},
|
||||||
},
|
},
|
||||||
SubSourceCache: map[string]*SubSourceCacheEntry{
|
SubSourceCache: map[string]*SubSourceCacheEntry{
|
||||||
"src_1": {Items: []SubCacheItem{{URI: "ss://YWVzLTI1Ni1nY206cHctYQ@s.example.com:20002#Dup", Name: "Dup"}}},
|
"src_1": {Items: []SubCacheItem{{URI: "ss://YWVzLTI1Ni1nY206cHctYQ@s.example.com:20002#Dup", Name: "Dup"}}},
|
||||||
@@ -63,12 +63,13 @@ func TestAggregateEntriesOrderAndDedup(t *testing.T) {
|
|||||||
okAt := NowISO()
|
okAt := NowISO()
|
||||||
d.SubSourceCache["src_1"].OkAt = &okAt
|
d.SubSourceCache["src_1"].OkAt = &okAt
|
||||||
customer := &Customer{
|
customer := &Customer{
|
||||||
ID: "cus_1",
|
ID: "cus_1", Credentials: &Credentials{ClientID: "11111111-2222-3333-4444-000000000001", RelayPassword: "pw-a", ShortID: "abcd0001ef01"},
|
||||||
ManualLinks: []ManualLink{
|
ManualLinks: []ManualLink{
|
||||||
{ID: "lnk_1", URI: "ss://YWVzLTI1Ni1nY206cHctYQ@s.example.com:20002#Whatever", Remark: "手动一号", Enabled: true},
|
{ID: "lnk_1", URI: "ss://YWVzLTI1Ni1nY206cHctYQ@s.example.com:20002#Whatever", Remark: "手动一号", Enabled: true},
|
||||||
},
|
},
|
||||||
ExternalSources: []ExtSource{{ID: "src_1", URL: "https://example.com/sub", Remark: "机场", Enabled: true}},
|
ExternalSources: []ExtSource{{ID: "src_1", URL: "https://example.com/sub", Remark: "机场", Enabled: true}},
|
||||||
}
|
}
|
||||||
|
d.Customers = []*Customer{customer}
|
||||||
entries := AggregateEntries(d, customer)
|
entries := AggregateEntries(d, customer)
|
||||||
names := []string{}
|
names := []string{}
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
|
|||||||
+13
-11
@@ -636,19 +636,22 @@ function renderCredentialsPane(customer) {
|
|||||||
if (!customer) { pane.innerHTML = '<div class="empty"><b>创建客户后生成凭据</b></div>'; return; }
|
if (!customer) { pane.innerHTML = '<div class="empty"><b>创建客户后生成凭据</b></div>'; return; }
|
||||||
const data = customerFormState.credentials;
|
const data = customerFormState.credentials;
|
||||||
if (!data) { pane.innerHTML = '<div class="empty"><b>加载中…</b></div>'; return; }
|
if (!data) { pane.innerHTML = '<div class="empty"><b>加载中…</b></div>'; return; }
|
||||||
const groups = data.items.map((entry) => `<div class="cred-group">
|
const creds = data.credentials || {};
|
||||||
<div class="g-head">${esc(entry.nodeName)}<span class="badge ${entry.status === 'active' ? 'green' : 'gray'}">${esc(statusText[entry.status] || entry.status)}</span><span class="mut" style="font-weight:400;font-size:12px">${esc(entry.protocol)}</span>
|
const bindings = (data.bindings || []).map((entry) => `<div class="cred-group">
|
||||||
<button type="button" class="icon-btn" style="margin-left:auto" data-tip="重新生成凭据" data-action="regen-credential" data-ncid="${esc(entry.nodeClientId)}" data-node="${esc(entry.nodeName)}">${ICONS.reset}</button></div>
|
<div class="g-head">${esc(entry.nodeName)}<span class="badge ${entry.status === 'active' ? 'green' : 'gray'}">${esc(statusText[entry.status] || entry.status)}</span><span class="mut" style="font-weight:400;font-size:12px">${esc(entry.protocol)}</span></div>
|
||||||
${credRowHTML('UUID', entry.clientId)}
|
|
||||||
${credRowHTML('密码', entry.password)}
|
|
||||||
${entry.shortId ? credRowHTML('Reality Short ID', entry.shortId) : ''}
|
|
||||||
${entry.username ? credRowHTML('用户名', entry.username) : ''}
|
${entry.username ? credRowHTML('用户名', entry.username) : ''}
|
||||||
|
${entry.realityPublicKey ? credRowHTML('Reality 公钥', entry.realityPublicKey) : ''}
|
||||||
</div>`).join('');
|
</div>`).join('');
|
||||||
pane.innerHTML = `<div class="cred-group"><div class="g-head">订阅</div>
|
pane.innerHTML = `<div class="cred-group"><div class="g-head">订阅</div>
|
||||||
<div class="cred-row"><span class="k">订阅令牌</span><span class="v">${esc(data.subscriptionToken || '—')}</span>
|
<div class="cred-row"><span class="k">订阅令牌</span><span class="v">${esc(data.subscriptionToken || '—')}</span>
|
||||||
<button type="button" class="icon-btn" data-tip="复制" data-action="copy-text" data-value="${esc(data.subscriptionToken || '')}">${ICONS.copy}</button>
|
<button type="button" class="icon-btn" data-tip="复制" data-action="copy-text" data-value="${esc(data.subscriptionToken || '')}">${ICONS.copy}</button>
|
||||||
<button type="button" class="icon-btn" data-tip="重置订阅令牌" data-action="rotate-subscription" data-id="${esc(customer.id)}">${ICONS.reset}</button></div></div>
|
<button type="button" class="icon-btn" data-tip="重置订阅令牌" data-action="rotate-subscription" data-id="${esc(customer.id)}">${ICONS.reset}</button></div></div>
|
||||||
${groups || '<div class="empty"><b>尚未关联节点</b></div>'}`;
|
<div class="cred-group"><div class="g-head">凭据(全部节点共用)<button type="button" class="icon-btn" style="margin-left:auto" data-tip="重新生成凭据" data-action="regen-credentials" data-id="${esc(customer.id)}">${ICONS.reset}</button></div>
|
||||||
|
${credRowHTML('UUID', creds.clientId)}
|
||||||
|
${credRowHTML('密码', creds.password)}
|
||||||
|
${credRowHTML('Reality Short ID', creds.shortId)}
|
||||||
|
</div>
|
||||||
|
${bindings || '<div class="empty"><b>尚未关联节点</b></div>'}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function linkStatusLine(customer, source) {
|
function linkStatusLine(customer, source) {
|
||||||
@@ -1147,11 +1150,10 @@ document.addEventListener('click', async (event) => {
|
|||||||
api(`/api/customers/${itemId}/credentials`).then((data) => { customerFormState.credentials = data; renderCredentialsPane(result.customer); }).catch(() => {});
|
api(`/api/customers/${itemId}/credentials`).then((data) => { customerFormState.credentials = data; renderCredentialsPane(result.customer); }).catch(() => {});
|
||||||
} else if (modalOpen()) subscriptionModal(result.customer);
|
} else if (modalOpen()) subscriptionModal(result.customer);
|
||||||
}
|
}
|
||||||
else if (action === 'regen-credential') {
|
else if (action === 'regen-credentials') {
|
||||||
const customerId = $('#customer-form').dataset.customerId;
|
const customerId = $('#customer-form').dataset.customerId;
|
||||||
if (!await confirmModal(`重新生成「${button.dataset.node}」的凭据?该客户在此节点的现有连接将立即失效,需要重新导入订阅。`, '重新生成')) return;
|
if (!await confirmModal('重新生成该客户的凭据?全部节点同时更换,现有连接将立即失效,需要重新导入订阅。', '重新生成')) return;
|
||||||
await api(`/api/customers/${customerId}/credentials/${button.dataset.ncid}/regenerate`, { method: 'POST', body: '{}' });
|
const data = await api(`/api/customers/${customerId}/credentials/regenerate`, { method: 'POST', body: '{}' });
|
||||||
const data = await api(`/api/customers/${customerId}/credentials`);
|
|
||||||
customerFormState.credentials = data;
|
customerFormState.credentials = data;
|
||||||
renderCredentialsPane(customerById(customerId));
|
renderCredentialsPane(customerById(customerId));
|
||||||
toast('凭据已重新生成');
|
toast('凭据已重新生成');
|
||||||
|
|||||||
Reference in New Issue
Block a user