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,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>');
|
||||
// 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.
|
||||
const secrets = [...this.secrets.entries()].sort((a, b) => b[0].length - a[0].length);
|
||||
for (const [raw, placeholder] of secrets) {
|
||||
if (raw.length >= 8) out = out.split(raw).join(placeholder);
|
||||
if (raw.length > 16) out = out.split(raw.slice(0, 16)).join(placeholder);
|
||||
// A base64 secret's prefix truncations (the SS2022 user keys derived
|
||||
// from a customer's 32-byte root key) are the same logical secret, so
|
||||
// their encodings are replaced with the same 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);
|
||||
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);
|
||||
|
||||
+128
-99
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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."
|
||||
},
|
||||
"sections": {
|
||||
@@ -49,6 +49,11 @@
|
||||
"customers": [
|
||||
{
|
||||
"createdAt": "<ts>",
|
||||
"credentials": {
|
||||
"clientId": "<uuid>#1",
|
||||
"relayPassword": "<secret>#1",
|
||||
"shortId": "<secret>#2"
|
||||
},
|
||||
"deviceLimit": 0,
|
||||
"expiresAt": null,
|
||||
"group": "",
|
||||
@@ -83,6 +88,11 @@
|
||||
},
|
||||
{
|
||||
"createdAt": "<ts>",
|
||||
"credentials": {
|
||||
"clientId": "<uuid>#2",
|
||||
"relayPassword": "<secret>#3",
|
||||
"shortId": "<secret>#4"
|
||||
},
|
||||
"deviceLimit": 2,
|
||||
"expiresAt": null,
|
||||
"group": "",
|
||||
@@ -170,7 +180,7 @@
|
||||
"body": {
|
||||
"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>",
|
||||
"customerId": "<cus>#1",
|
||||
"id": "<ncl>#1",
|
||||
@@ -179,7 +189,7 @@
|
||||
"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>",
|
||||
"customerId": "<cus>#2",
|
||||
"id": "<ncl>#2",
|
||||
@@ -248,7 +258,7 @@
|
||||
"epoch": "diff-epoch-1",
|
||||
"uplink": 4000
|
||||
},
|
||||
"wsPath": "<secret>#1"
|
||||
"wsPath": "<secret>#5"
|
||||
},
|
||||
{
|
||||
"artifacts": {},
|
||||
@@ -286,7 +296,7 @@
|
||||
"topology": "direct",
|
||||
"updatedAt": "<ts>",
|
||||
"usageCounters": null,
|
||||
"wsPath": "<secret>#2"
|
||||
"wsPath": "<secret>#6"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -555,51 +565,55 @@
|
||||
"credentials": {
|
||||
"alice": {
|
||||
"body": {
|
||||
"customerId": "<cus>#1",
|
||||
"items": [
|
||||
"bindings": [
|
||||
{
|
||||
"clientId": "<uuid>#1",
|
||||
"nodeClientId": "<ncl>#1",
|
||||
"nodeId": "<nod>#2",
|
||||
"nodeName": "N1",
|
||||
"password": "<secret>#3",
|
||||
"protocol": "vless-reality-vision",
|
||||
"shortId": "<secret>#4",
|
||||
"realityPublicKey": "<rpub>#1",
|
||||
"status": "active",
|
||||
"username": ""
|
||||
},
|
||||
{
|
||||
"clientId": "<uuid>#3",
|
||||
"nodeClientId": "<ncl>#3",
|
||||
"nodeId": "<nod>#1",
|
||||
"nodeName": "N2",
|
||||
"password": "<secret>#5",
|
||||
"protocol": "shadowsocks-2022-aes128",
|
||||
"shortId": "<secret>#6",
|
||||
"realityPublicKey": null,
|
||||
"status": "active",
|
||||
"username": ""
|
||||
}
|
||||
],
|
||||
"credentials": {
|
||||
"clientId": "<uuid>#1",
|
||||
"password": "<secret>#1",
|
||||
"shortId": "<secret>#2"
|
||||
},
|
||||
"customerId": "<cus>#1",
|
||||
"subscriptionToken": "<subtoken>#1"
|
||||
},
|
||||
"status": 200
|
||||
},
|
||||
"bob": {
|
||||
"body": {
|
||||
"customerId": "<cus>#2",
|
||||
"items": [
|
||||
"bindings": [
|
||||
{
|
||||
"clientId": "<uuid>#2",
|
||||
"nodeClientId": "<ncl>#2",
|
||||
"nodeId": "<nod>#2",
|
||||
"nodeName": "N1",
|
||||
"password": "<secret>#7",
|
||||
"protocol": "vless-reality-vision",
|
||||
"shortId": "<secret>#8",
|
||||
"realityPublicKey": "<rpub>#1",
|
||||
"status": "active",
|
||||
"username": ""
|
||||
}
|
||||
],
|
||||
"credentials": {
|
||||
"clientId": "<uuid>#2",
|
||||
"password": "<secret>#3",
|
||||
"shortId": "<secret>#4"
|
||||
},
|
||||
"customerId": "<cus>#2",
|
||||
"subscriptionToken": "<subtoken>#2"
|
||||
},
|
||||
"status": 200
|
||||
@@ -608,49 +622,49 @@
|
||||
"subscriptionsSeed": {
|
||||
"alice": {
|
||||
"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",
|
||||
"status": 200,
|
||||
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
||||
},
|
||||
"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",
|
||||
"status": 200,
|
||||
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
||||
},
|
||||
"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",
|
||||
"status": 200,
|
||||
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
||||
},
|
||||
"clash": {
|
||||
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N1\"\n server: \"entry.example.com\"\n port: <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",
|
||||
"status": 200,
|
||||
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
||||
},
|
||||
"clash-smart": {
|
||||
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N1\"\n server: \"entry.example.com\"\n port: <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",
|
||||
"status": 200,
|
||||
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
||||
},
|
||||
"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",
|
||||
"status": 200,
|
||||
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
||||
},
|
||||
"singbox": {
|
||||
"body": "{\n \"log\": {\n \"level\": \"warn\"\n },\n \"inbounds\": [\n {\n \"type\": \"mixed\",\n \"tag\": \"local\",\n \"listen\": \"127.0.0.1\",\n \"listen_port\": 2080\n }\n ],\n \"outbounds\": [\n {\n \"type\": \"selector\",\n \"tag\": \"proxy\",\n \"outbounds\": [\n \"node-1\",\n \"node-2\"\n ]\n },\n {\n \"type\": \"vless\",\n \"tag\": \"node-1\",\n \"server\": \"entry.example.com\",\n \"server_port\": <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",
|
||||
"status": 200,
|
||||
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
||||
},
|
||||
"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",
|
||||
"status": 200,
|
||||
"userinfo": "upload=1000; download=2000; total=0; expire=0"
|
||||
@@ -658,7 +672,7 @@
|
||||
},
|
||||
"bob": {
|
||||
"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",
|
||||
"status": 200,
|
||||
"userinfo": "upload=0; download=0; total=1073741824; expire=0"
|
||||
@@ -670,31 +684,31 @@
|
||||
"userinfo": ""
|
||||
},
|
||||
"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",
|
||||
"status": 200,
|
||||
"userinfo": "upload=0; download=0; total=1073741824; expire=0"
|
||||
},
|
||||
"clash": {
|
||||
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N1\"\n server: \"entry.example.com\"\n port: <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",
|
||||
"status": 200,
|
||||
"userinfo": "upload=0; download=0; total=1073741824; expire=0"
|
||||
},
|
||||
"clash-smart": {
|
||||
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N1\"\n server: \"entry.example.com\"\n port: <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",
|
||||
"status": 200,
|
||||
"userinfo": "upload=0; download=0; total=1073741824; expire=0"
|
||||
},
|
||||
"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",
|
||||
"status": 200,
|
||||
"userinfo": "upload=0; download=0; total=1073741824; expire=0"
|
||||
},
|
||||
"singbox": {
|
||||
"body": "{\n \"log\": {\n \"level\": \"warn\"\n },\n \"inbounds\": [\n {\n \"type\": \"mixed\",\n \"tag\": \"local\",\n \"listen\": \"127.0.0.1\",\n \"listen_port\": 2080\n }\n ],\n \"outbounds\": [\n {\n \"type\": \"selector\",\n \"tag\": \"proxy\",\n \"outbounds\": [\n \"node-1\"\n ]\n },\n {\n \"type\": \"vless\",\n \"tag\": \"node-1\",\n \"server\": \"entry.example.com\",\n \"server_port\": <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",
|
||||
"status": 200,
|
||||
"userinfo": "upload=0; download=0; total=1073741824; expire=0"
|
||||
@@ -751,6 +765,11 @@
|
||||
"body": {
|
||||
"customer": {
|
||||
"createdAt": "<ts>",
|
||||
"credentials": {
|
||||
"clientId": "<uuid>#3",
|
||||
"relayPassword": "<secret>#7",
|
||||
"shortId": "<secret>#8"
|
||||
},
|
||||
"deviceLimit": 0,
|
||||
"expiresAt": null,
|
||||
"group": "",
|
||||
@@ -869,7 +888,7 @@
|
||||
},
|
||||
"private_key": "${REALITY_PRIVATE:reality-<nod>#3}",
|
||||
"short_id": [
|
||||
"<secret>#10"
|
||||
"<secret>#8"
|
||||
]
|
||||
},
|
||||
"server_name": "www.amd.com"
|
||||
@@ -878,7 +897,7 @@
|
||||
"users": [
|
||||
{
|
||||
"name": "ng:<cus>#3",
|
||||
"uuid": "<uuid>#4"
|
||||
"uuid": "<uuid>#3"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -946,7 +965,7 @@
|
||||
},
|
||||
"private_key": "${REALITY_PRIVATE:reality-<nod>#3}",
|
||||
"short_id": [
|
||||
"<secret>#10"
|
||||
"<secret>#8"
|
||||
]
|
||||
},
|
||||
"server_name": "www.amd.com"
|
||||
@@ -955,7 +974,7 @@
|
||||
"users": [
|
||||
{
|
||||
"name": "ng:<cus>#3",
|
||||
"uuid": "<uuid>#4"
|
||||
"uuid": "<uuid>#3"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -994,7 +1013,7 @@
|
||||
"body": {
|
||||
"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>",
|
||||
"customerId": "<cus>#1",
|
||||
"id": "<ncl>#1",
|
||||
@@ -1003,7 +1022,7 @@
|
||||
"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>",
|
||||
"customerId": "<cus>#2",
|
||||
"id": "<ncl>#2",
|
||||
@@ -1021,7 +1040,7 @@
|
||||
"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>",
|
||||
"customerId": "<cus>#3",
|
||||
"id": "<ncl>#4",
|
||||
@@ -1081,7 +1100,7 @@
|
||||
"epoch": "diff-epoch-1",
|
||||
"uplink": 4000
|
||||
},
|
||||
"wsPath": "<secret>#1"
|
||||
"wsPath": "<secret>#5"
|
||||
},
|
||||
{
|
||||
"artifacts": {},
|
||||
@@ -1119,7 +1138,7 @@
|
||||
"topology": "direct",
|
||||
"updatedAt": "<ts>",
|
||||
"usageCounters": null,
|
||||
"wsPath": "<secret>#2"
|
||||
"wsPath": "<secret>#6"
|
||||
},
|
||||
{
|
||||
"artifacts": {
|
||||
@@ -1172,43 +1191,43 @@
|
||||
},
|
||||
"carolSubscriptions": {
|
||||
"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",
|
||||
"status": 200,
|
||||
"userinfo": "upload=0; download=0; total=0; expire=0"
|
||||
},
|
||||
"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",
|
||||
"status": 200,
|
||||
"userinfo": "upload=0; download=0; total=0; expire=0"
|
||||
},
|
||||
"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",
|
||||
"status": 200,
|
||||
"userinfo": "upload=0; download=0; total=0; expire=0"
|
||||
},
|
||||
"clash": {
|
||||
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N3\"\n server: \"s3.example.com\"\n port: <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",
|
||||
"status": 200,
|
||||
"userinfo": "upload=0; download=0; total=0; expire=0"
|
||||
},
|
||||
"clash-smart": {
|
||||
"body": "mixed-port: 7890\nallow-lan: false\nmode: rule\nproxies:\n - name: \"N3\"\n server: \"s3.example.com\"\n port: <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",
|
||||
"status": 200,
|
||||
"userinfo": "upload=0; download=0; total=0; expire=0"
|
||||
},
|
||||
"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",
|
||||
"status": 200,
|
||||
"userinfo": "upload=0; download=0; total=0; expire=0"
|
||||
},
|
||||
"singbox": {
|
||||
"body": "{\n \"log\": {\n \"level\": \"warn\"\n },\n \"inbounds\": [\n {\n \"type\": \"mixed\",\n \"tag\": \"local\",\n \"listen\": \"127.0.0.1\",\n \"listen_port\": 2080\n }\n ],\n \"outbounds\": [\n {\n \"type\": \"selector\",\n \"tag\": \"proxy\",\n \"outbounds\": [\n \"node-1\"\n ]\n },\n {\n \"type\": \"vless\",\n \"tag\": \"node-1\",\n \"server\": \"s3.example.com\",\n \"server_port\": <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",
|
||||
"status": 200,
|
||||
"userinfo": "upload=0; download=0; total=0; expire=0"
|
||||
@@ -1286,6 +1305,11 @@
|
||||
"customers": [
|
||||
{
|
||||
"createdAt": "<ts>",
|
||||
"credentials": {
|
||||
"clientId": "<uuid>#1",
|
||||
"relayPassword": "<secret>#1",
|
||||
"shortId": "<secret>#2"
|
||||
},
|
||||
"deviceLimit": 0,
|
||||
"expiresAt": null,
|
||||
"group": "",
|
||||
@@ -1329,6 +1353,11 @@
|
||||
},
|
||||
{
|
||||
"createdAt": "<ts>",
|
||||
"credentials": {
|
||||
"clientId": "<uuid>#2",
|
||||
"relayPassword": "<secret>#3",
|
||||
"shortId": "<secret>#4"
|
||||
},
|
||||
"deviceLimit": 2,
|
||||
"expiresAt": null,
|
||||
"group": "",
|
||||
@@ -1353,6 +1382,11 @@
|
||||
},
|
||||
{
|
||||
"createdAt": "<ts>",
|
||||
"credentials": {
|
||||
"clientId": "<uuid>#3",
|
||||
"relayPassword": "<secret>#7",
|
||||
"shortId": "<secret>#8"
|
||||
},
|
||||
"deviceLimit": 0,
|
||||
"expiresAt": null,
|
||||
"group": "",
|
||||
@@ -1441,7 +1475,7 @@
|
||||
"epoch": "golden-epoch-1",
|
||||
"uplink": 8000
|
||||
},
|
||||
"wsPath": "<secret>#1"
|
||||
"wsPath": "<secret>#5"
|
||||
}
|
||||
},
|
||||
"status": 200
|
||||
@@ -1485,8 +1519,8 @@
|
||||
},
|
||||
"private_key": "${REALITY_PRIVATE:reality-<nod>#2}",
|
||||
"short_id": [
|
||||
"<secret>#4",
|
||||
"<secret>#8"
|
||||
"<secret>#2",
|
||||
"<secret>#4"
|
||||
]
|
||||
},
|
||||
"server_name": "www.amd.com"
|
||||
@@ -1515,7 +1549,7 @@
|
||||
"outbounds": [
|
||||
{
|
||||
"method": "2022-blake3-aes-256-gcm",
|
||||
"password": "<secret>#11",
|
||||
"password": "<secret>#10",
|
||||
"server": "exit.example.com",
|
||||
"server_port": "<port>#1",
|
||||
"tag": "ng-<nod>#2-exit",
|
||||
@@ -1573,8 +1607,8 @@
|
||||
},
|
||||
"private_key": "${REALITY_PRIVATE:reality-<nod>#2}",
|
||||
"short_id": [
|
||||
"<secret>#4",
|
||||
"<secret>#8"
|
||||
"<secret>#2",
|
||||
"<secret>#4"
|
||||
]
|
||||
},
|
||||
"server_name": "www.amd.com"
|
||||
@@ -1603,7 +1637,7 @@
|
||||
"outbounds": [
|
||||
{
|
||||
"method": "2022-blake3-aes-256-gcm",
|
||||
"password": "<secret>#11",
|
||||
"password": "<secret>#10",
|
||||
"server": "exit.example.com",
|
||||
"server_port": "<port>#1",
|
||||
"tag": "ng-<nod>#2-exit",
|
||||
@@ -1650,7 +1684,7 @@
|
||||
"listen": "<hostip>",
|
||||
"listen_port": "<port>#1",
|
||||
"method": "2022-blake3-aes-256-gcm",
|
||||
"password": "<secret>#11",
|
||||
"password": "<secret>#10",
|
||||
"tag": "ng-<nod>#2-exit-in",
|
||||
"type": "shadowsocks"
|
||||
}
|
||||
@@ -1701,7 +1735,7 @@
|
||||
"listen": "<hostip>",
|
||||
"listen_port": "<port>#1",
|
||||
"method": "2022-blake3-aes-256-gcm",
|
||||
"password": "<secret>#11",
|
||||
"password": "<secret>#10",
|
||||
"tag": "ng-<nod>#2-exit-in",
|
||||
"type": "shadowsocks"
|
||||
}
|
||||
@@ -1740,7 +1774,7 @@
|
||||
"body": {
|
||||
"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>",
|
||||
"customerId": "<cus>#1",
|
||||
"id": "<ncl>#1",
|
||||
@@ -1749,7 +1783,7 @@
|
||||
"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>",
|
||||
"customerId": "<cus>#2",
|
||||
"id": "<ncl>#2",
|
||||
@@ -1767,7 +1801,7 @@
|
||||
"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>",
|
||||
"customerId": "<cus>#3",
|
||||
"id": "<ncl>#4",
|
||||
@@ -1834,7 +1868,7 @@
|
||||
"epoch": "golden-epoch-1",
|
||||
"uplink": 8000
|
||||
},
|
||||
"wsPath": "<secret>#1"
|
||||
"wsPath": "<secret>#5"
|
||||
},
|
||||
{
|
||||
"artifacts": {},
|
||||
@@ -1874,7 +1908,7 @@
|
||||
"topology": "direct",
|
||||
"updatedAt": "<ts>",
|
||||
"usageCounters": null,
|
||||
"wsPath": "<secret>#2"
|
||||
"wsPath": "<secret>#6"
|
||||
},
|
||||
{
|
||||
"artifacts": {
|
||||
@@ -2272,6 +2306,11 @@
|
||||
"customers": [
|
||||
{
|
||||
"createdAt": "<ts>",
|
||||
"credentials": {
|
||||
"clientId": "<uuid>#1",
|
||||
"relayPassword": "<secret>#1",
|
||||
"shortId": "<secret>#2"
|
||||
},
|
||||
"deviceLimit": 0,
|
||||
"expiresAt": null,
|
||||
"group": "",
|
||||
@@ -2309,6 +2348,11 @@
|
||||
},
|
||||
{
|
||||
"createdAt": "<ts>",
|
||||
"credentials": {
|
||||
"clientId": "<uuid>#2",
|
||||
"relayPassword": "<secret>#3",
|
||||
"shortId": "<secret>#4"
|
||||
},
|
||||
"deviceLimit": 2,
|
||||
"expiresAt": null,
|
||||
"group": "",
|
||||
@@ -2327,6 +2371,11 @@
|
||||
},
|
||||
{
|
||||
"createdAt": "<ts>",
|
||||
"credentials": {
|
||||
"clientId": "<uuid>#3",
|
||||
"relayPassword": "<secret>#7",
|
||||
"shortId": "<secret>#8"
|
||||
},
|
||||
"deviceLimit": 0,
|
||||
"expiresAt": null,
|
||||
"group": "",
|
||||
@@ -2406,8 +2455,8 @@
|
||||
},
|
||||
"private_key": "${REALITY_PRIVATE:reality-<nod>#2}",
|
||||
"short_id": [
|
||||
"<secret>#4",
|
||||
"<secret>#8"
|
||||
"<secret>#2",
|
||||
"<secret>#4"
|
||||
]
|
||||
},
|
||||
"server_name": "www.amd.com"
|
||||
@@ -2436,7 +2485,7 @@
|
||||
"outbounds": [
|
||||
{
|
||||
"method": "2022-blake3-aes-256-gcm",
|
||||
"password": "<secret>#11",
|
||||
"password": "<secret>#10",
|
||||
"server": "exit.example.com",
|
||||
"server_port": "<port>#1",
|
||||
"tag": "ng-<nod>#2-exit",
|
||||
@@ -2478,7 +2527,7 @@
|
||||
"listen": "<hostip>",
|
||||
"listen_port": "<port>#1",
|
||||
"method": "2022-blake3-aes-256-gcm",
|
||||
"password": "<secret>#11",
|
||||
"password": "<secret>#10",
|
||||
"tag": "ng-<nod>#2-exit-in",
|
||||
"type": "shadowsocks"
|
||||
}
|
||||
@@ -2529,13 +2578,13 @@
|
||||
"listen": "<hostip>",
|
||||
"listen_port": "<port>#3",
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "<secret>#12",
|
||||
"password": "<secret>#11",
|
||||
"tag": "ng-<nod>#1-in",
|
||||
"type": "shadowsocks",
|
||||
"users": [
|
||||
{
|
||||
"name": "ng:<cus>#1",
|
||||
"password": "<secret>#5"
|
||||
"password": "<secret>#12"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2603,7 +2652,7 @@
|
||||
},
|
||||
"private_key": "${REALITY_PRIVATE:reality-<nod>#3}",
|
||||
"short_id": [
|
||||
"<secret>#10"
|
||||
"<secret>#8"
|
||||
]
|
||||
},
|
||||
"server_name": "www.amd.com"
|
||||
@@ -2612,7 +2661,7 @@
|
||||
"users": [
|
||||
{
|
||||
"name": "ng:<cus>#3",
|
||||
"uuid": "<uuid>#4"
|
||||
"uuid": "<uuid>#3"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2680,8 +2729,8 @@
|
||||
},
|
||||
"private_key": "${REALITY_PRIVATE:reality-<nod>#2}",
|
||||
"short_id": [
|
||||
"<secret>#4",
|
||||
"<secret>#8"
|
||||
"<secret>#2",
|
||||
"<secret>#4"
|
||||
]
|
||||
},
|
||||
"server_name": "www.amd.com"
|
||||
@@ -2710,7 +2759,7 @@
|
||||
"outbounds": [
|
||||
{
|
||||
"method": "2022-blake3-aes-256-gcm",
|
||||
"password": "<secret>#11",
|
||||
"password": "<secret>#10",
|
||||
"server": "exit.example.com",
|
||||
"server_port": "<port>#1",
|
||||
"tag": "ng-<nod>#2-exit",
|
||||
@@ -2752,7 +2801,7 @@
|
||||
"listen": "<hostip>",
|
||||
"listen_port": "<port>#1",
|
||||
"method": "2022-blake3-aes-256-gcm",
|
||||
"password": "<secret>#11",
|
||||
"password": "<secret>#10",
|
||||
"tag": "ng-<nod>#2-exit-in",
|
||||
"type": "shadowsocks"
|
||||
}
|
||||
@@ -2800,11 +2849,6 @@
|
||||
"nodeClients": [
|
||||
{
|
||||
"createdAt": "<ts>",
|
||||
"credentials": {
|
||||
"clientId": "<uuid>#1",
|
||||
"relayPassword": "<secret>#3",
|
||||
"shortId": "<secret>#4"
|
||||
},
|
||||
"customerId": "<cus>#1",
|
||||
"id": "<ncl>#1",
|
||||
"nodeId": "<nod>#2",
|
||||
@@ -2813,11 +2857,6 @@
|
||||
},
|
||||
{
|
||||
"createdAt": "<ts>",
|
||||
"credentials": {
|
||||
"clientId": "<uuid>#2",
|
||||
"relayPassword": "<secret>#7",
|
||||
"shortId": "<secret>#8"
|
||||
},
|
||||
"customerId": "<cus>#2",
|
||||
"id": "<ncl>#2",
|
||||
"nodeId": "<nod>#2",
|
||||
@@ -2826,11 +2865,6 @@
|
||||
},
|
||||
{
|
||||
"createdAt": "<ts>",
|
||||
"credentials": {
|
||||
"clientId": "<uuid>#3",
|
||||
"relayPassword": "<secret>#5",
|
||||
"shortId": "<secret>#6"
|
||||
},
|
||||
"customerId": "<cus>#1",
|
||||
"id": "<ncl>#3",
|
||||
"nodeId": "<nod>#1",
|
||||
@@ -2839,11 +2873,6 @@
|
||||
},
|
||||
{
|
||||
"createdAt": "<ts>",
|
||||
"credentials": {
|
||||
"clientId": "<uuid>#4",
|
||||
"relayPassword": "<secret>#13",
|
||||
"shortId": "<secret>#10"
|
||||
},
|
||||
"customerId": "<cus>#3",
|
||||
"id": "<ncl>#4",
|
||||
"nodeId": "<nod>#3",
|
||||
@@ -2861,8 +2890,8 @@
|
||||
"entryReady": true,
|
||||
"error": null,
|
||||
"exitCredentials": {
|
||||
"clientId": "<uuid>#5",
|
||||
"password": "<secret>#11",
|
||||
"clientId": "<uuid>#4",
|
||||
"password": "<secret>#10",
|
||||
"socksUser": "ng_5b8dd6b991"
|
||||
},
|
||||
"exitPort": "<port>#1",
|
||||
@@ -2909,7 +2938,7 @@
|
||||
"epoch": "golden-epoch-1",
|
||||
"uplink": 8000
|
||||
},
|
||||
"wsPath": "<secret>#1"
|
||||
"wsPath": "<secret>#5"
|
||||
},
|
||||
{
|
||||
"artifacts": {},
|
||||
@@ -2938,12 +2967,12 @@
|
||||
"retiredUsageEpochs": [],
|
||||
"serverId": "<srv>#1",
|
||||
"sortIndex": 1,
|
||||
"ssServerKey": "<secret>#12",
|
||||
"ssServerKey": "<secret>#11",
|
||||
"status": "active",
|
||||
"topology": "direct",
|
||||
"updatedAt": "<ts>",
|
||||
"usageCounters": null,
|
||||
"wsPath": "<secret>#2"
|
||||
"wsPath": "<secret>#6"
|
||||
},
|
||||
{
|
||||
"artifacts": {
|
||||
|
||||
+72
-63
@@ -603,9 +603,40 @@ func CustomerSummary(data *Data, nowMs int64) map[string]any {
|
||||
}
|
||||
}
|
||||
|
||||
// credentialItems mirrors credentialItems in server.js.
|
||||
func credentialItems(d *Data, customer *Customer) []map[string]any {
|
||||
items := []map[string]any{}
|
||||
// realityPublicKeyOf returns the node's panel-hosted Reality public key as
|
||||
// reported back by the agent ("" when unknown). The keypair is the server's
|
||||
// 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 {
|
||||
if nc.CustomerID != customer.ID {
|
||||
continue
|
||||
@@ -618,30 +649,26 @@ func credentialItems(d *Data, customer *Customer) []map[string]any {
|
||||
if node.Protocol == "socks5-auth" {
|
||||
username = NodeEmail(customer.ID)
|
||||
}
|
||||
creds := nc.Credentials
|
||||
var clientID, password, shortID any
|
||||
if creds.ClientID != "" {
|
||||
clientID = creds.ClientID
|
||||
var publicKey any
|
||||
if key := realityPublicKeyOf(node); key != "" {
|
||||
publicKey = key
|
||||
}
|
||||
if creds.RelayPassword != "" {
|
||||
password = creds.RelayPassword
|
||||
}
|
||||
if creds.ShortID != "" {
|
||||
shortID = creds.ShortID
|
||||
}
|
||||
items = append(items, map[string]any{
|
||||
"nodeClientId": nc.ID,
|
||||
"nodeId": node.ID,
|
||||
"nodeName": node.Name,
|
||||
"protocol": node.Protocol,
|
||||
"status": nc.Status,
|
||||
"clientId": clientID,
|
||||
"password": password,
|
||||
"shortId": shortID,
|
||||
"username": username,
|
||||
bindings = append(bindings, map[string]any{
|
||||
"nodeClientId": nc.ID,
|
||||
"nodeId": node.ID,
|
||||
"nodeName": node.Name,
|
||||
"protocol": node.Protocol,
|
||||
"status": nc.Status,
|
||||
"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" /
|
||||
@@ -1926,6 +1953,7 @@ func (a *App) handleAdminApi(w http.ResponseWriter, r *http.Request, pathname st
|
||||
Notes: CleanText(body["notes"], 1000),
|
||||
CreatedAt: NowISO(),
|
||||
UpdatedAt: NowISO(),
|
||||
Credentials: func() *Credentials { c := NewCustomerCredentials(); return &c }(),
|
||||
}
|
||||
// Arm the monthly-reset schedule: it starts with the next
|
||||
// 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 {
|
||||
return true, httpErr(404, "客户不存在")
|
||||
}
|
||||
SendJSON(w, 200, map[string]any{
|
||||
"customerId": customer.ID,
|
||||
"subscriptionToken": customer.SubscriptionToken,
|
||||
"items": credentialItems(data, customer),
|
||||
}, nil)
|
||||
SendJSON(w, 200, customerCredentialsView(data, customer), nil)
|
||||
return true, nil
|
||||
}
|
||||
if params, ok := RoutePattern("/api/customers/:id/credentials/:ncid/regenerate", pathname); ok && r.Method == http.MethodPost {
|
||||
var responseItem map[string]any
|
||||
if params, ok := RoutePattern("/api/customers/:id/credentials/regenerate", pathname); ok && r.Method == http.MethodPost {
|
||||
var view map[string]any
|
||||
_, err := a.store.Transaction(func(d *Data) (any, error) {
|
||||
customer := findCustomer(d, params["id"])
|
||||
if customer == nil {
|
||||
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 {
|
||||
if nc.ID == params["ncid"] && nc.CustomerID == customer.ID {
|
||||
binding = nc
|
||||
break
|
||||
if nc.CustomerID != customer.ID || nc.Status != "active" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if binding == nil {
|
||||
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" {
|
||||
node := findNode(d, nc.NodeID)
|
||||
if node == nil || node.Status == "draft" || node.Status == "removing" || rebuilt[node.ID] {
|
||||
continue
|
||||
}
|
||||
rebuilt[node.ID] = true
|
||||
if err := RebuildNode(d, node, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
Audit(d, actor, "regenerate_credentials", customer.ID, map[string]any{"nodeId": node.ID, "node": node.Name})
|
||||
for _, entry := range credentialItems(d, customer) {
|
||||
if entry["nodeClientId"] == binding.ID {
|
||||
responseItem = entry
|
||||
break
|
||||
}
|
||||
}
|
||||
Audit(d, actor, "regenerate_credentials", customer.ID, map[string]any{"nodesRebuilt": len(rebuilt)})
|
||||
view = customerCredentialsView(d, customer)
|
||||
return nil, nil
|
||||
})
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
SendJSON(w, 200, map[string]any{"item": responseItem}, nil)
|
||||
SendJSON(w, 200, view, nil)
|
||||
return true, nil
|
||||
}
|
||||
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
|
||||
oldExitServerID := strVal(item.ExitServerID)
|
||||
oldForward := forwardNode(item)
|
||||
oldProtocol := item.Protocol
|
||||
next, err := NormalizeNode(body, item, d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
*item = *next
|
||||
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 {
|
||||
QueueJob(d, oldServerID, item.ID, "delete_resource", &JobPayload{ResourceID: item.ResourceID, Kind: "node"})
|
||||
item.EntryReady = false
|
||||
|
||||
+40
-15
@@ -239,6 +239,16 @@ func payloadWithResource(resource map[string]any, kind string, keyMaterial map[s
|
||||
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) {
|
||||
server := orchFindServer(d, node.ServerID)
|
||||
if server == nil {
|
||||
@@ -262,11 +272,26 @@ func resourceForNode(d *Data, node *Node, clients []*NodeClient) (map[string]any
|
||||
if clients == nil {
|
||||
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{
|
||||
ResourceID: node.ResourceID,
|
||||
TagPrefix: tagForNode(node),
|
||||
Node: node,
|
||||
NodeClients: clients,
|
||||
NodeClients: resolved,
|
||||
Server: server,
|
||||
BindAddress: bindAddress,
|
||||
ExitServer: exitServer,
|
||||
@@ -977,14 +1002,14 @@ func assignCustomerData(d *Data, nodeID, customerID, actor string) (*NodeClient,
|
||||
nc.UpdatedAt = NowISO()
|
||||
} else {
|
||||
now := NowISO()
|
||||
ensureCustomerCredentials(d, customerID)
|
||||
nc = &NodeClient{
|
||||
ID: NewID("ncl"),
|
||||
NodeID: nodeID,
|
||||
CustomerID: customerID,
|
||||
Credentials: NewNodeClientCredentials(node.Protocol),
|
||||
Status: "active",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
ID: NewID("ncl"),
|
||||
NodeID: nodeID,
|
||||
CustomerID: customerID,
|
||||
Status: "active",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
d.NodeClients = append(d.NodeClients, nc)
|
||||
}
|
||||
@@ -1077,14 +1102,14 @@ func AlignCustomerNodes(d *Data, customer *Customer, desiredIds []string, actor
|
||||
status = "active"
|
||||
}
|
||||
now := NowISO()
|
||||
ensureCustomerCredentials(d, customer.ID)
|
||||
d.NodeClients = append(d.NodeClients, &NodeClient{
|
||||
ID: NewID("ncl"),
|
||||
NodeID: nodeID,
|
||||
CustomerID: customer.ID,
|
||||
Credentials: NewNodeClientCredentials(node.Protocol),
|
||||
Status: status,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
ID: NewID("ncl"),
|
||||
NodeID: nodeID,
|
||||
CustomerID: customer.ID,
|
||||
Status: status,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
})
|
||||
Audit(d, actor, "assign_customer", node.ID, map[string]any{"customerId": customer.ID, "customer": customer.Name})
|
||||
if status == "active" {
|
||||
|
||||
+45
-15
@@ -196,18 +196,47 @@ func (q xbQuery) String() string {
|
||||
|
||||
func NodeEmail(customerID string) string { return "ng:" + customerID }
|
||||
|
||||
func NewNodeClientCredentials(protocol string) Credentials {
|
||||
keyBytes := 24
|
||||
if cipher, ok := ShadowsocksMethods[protocol]; ok {
|
||||
keyBytes = cipher.KeyBytes
|
||||
}
|
||||
// NewCustomerCredentials mints the single credential set a customer uses on
|
||||
// every node (round-2 item 2, 3x-ui model). The relay password is always a
|
||||
// base64 32-byte root key: SS2022 user keys are derived from it per method
|
||||
// (see EffectiveRelayPassword), so one set serves aes128 and aes256 nodes
|
||||
// alike; every other protocol uses it verbatim.
|
||||
func NewCustomerCredentials() Credentials {
|
||||
return Credentials{
|
||||
ClientID: NewUUID(),
|
||||
RelayPassword: b64(keyBytes),
|
||||
RelayPassword: b64(32),
|
||||
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) {
|
||||
if node.WSPath == nil || *node.WSPath == "" {
|
||||
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))
|
||||
shortIDs := make([]any, 0, len(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" {
|
||||
user["flow"] = "xtls-rprx-vision"
|
||||
}
|
||||
users = append(users, user)
|
||||
shortIDs = append(shortIDs, nc.Credentials.ShortID)
|
||||
shortIDs = append(shortIDs, creds.ShortID)
|
||||
}
|
||||
destPort := node.Reality.DestPort
|
||||
if destPort == 0 {
|
||||
@@ -383,7 +413,7 @@ func buildSingBoxInbound(tag string, node *Node, nodeClients []*NodeClient, serv
|
||||
}
|
||||
users := make([]any, 0, len(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)
|
||||
if wsPath == "" {
|
||||
@@ -406,7 +436,7 @@ func buildSingBoxInbound(tag string, node *Node, nodeClients []*NodeClient, serv
|
||||
if cipher, ok := ShadowsocksMethods[protocol]; ok {
|
||||
users := make([]any, 0, len(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{
|
||||
"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" {
|
||||
users := make([]any, 0, len(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
|
||||
}
|
||||
@@ -428,7 +458,7 @@ func buildSingBoxInbound(tag string, node *Node, nodeClients []*NodeClient, serv
|
||||
}
|
||||
users := make([]any, 0, len(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{
|
||||
"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))
|
||||
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{
|
||||
"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
|
||||
}
|
||||
if cipher, ok := ShadowsocksMethods[protocol]; ok {
|
||||
password := p.RelayPassword
|
||||
password := EffectiveRelayPassword(protocol, p.RelayPassword)
|
||||
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))
|
||||
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"},
|
||||
}
|
||||
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_2", Credentials: Credentials{ClientID: "11111111-2222-3333-4444-000000000002", RelayPassword: "unused2", ShortID: "abcd0002ef01"}},
|
||||
{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"}},
|
||||
}
|
||||
entryServer := &Server{ID: "srv_entry", PublicAddress: "203.0.113.10"}
|
||||
exitServer := &Server{ID: "srv_exit", PublicAddress: "exit.example.com"}
|
||||
@@ -102,7 +102,7 @@ func TestGoldenResources(t *testing.T) {
|
||||
SSServerKey: StrPtr("CwwNDg8QERITFBUWFxgZGg=="),
|
||||
}
|
||||
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"}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ package main
|
||||
// the same serialization here.
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -133,10 +134,70 @@ func (s *Store) Init() error {
|
||||
if err := validateData(&data); err != nil {
|
||||
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
|
||||
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).
|
||||
func (s *Store) Snapshot() *Data {
|
||||
s.mu.Lock()
|
||||
|
||||
@@ -108,13 +108,17 @@ func NodeClientURI(d *Data, nc *NodeClient) string {
|
||||
if networkMode == "" {
|
||||
networkMode = "ipv4"
|
||||
}
|
||||
creds := Credentials{}
|
||||
if customer := findCustomer(d, nc.CustomerID); customer != nil && customer.Credentials != nil {
|
||||
creds = *customer.Credentials
|
||||
}
|
||||
uri, ok := BuildClientURI(BuildClientURIParams{
|
||||
Protocol: node.Protocol,
|
||||
RelayServer: server,
|
||||
RelayPort: node.Port,
|
||||
ClientID: nc.Credentials.ClientID,
|
||||
RelayPassword: nc.Credentials.RelayPassword,
|
||||
ShortID: nc.Credentials.ShortID,
|
||||
ClientID: creds.ClientID,
|
||||
RelayPassword: creds.RelayPassword,
|
||||
ShortID: creds.ShortID,
|
||||
WSPath: xbDeref(node.WSPath),
|
||||
SocksUser: NodeEmail(nc.CustomerID),
|
||||
Reality: node.Reality,
|
||||
|
||||
@@ -36,26 +36,25 @@ func goldenSubData() *Data {
|
||||
Reality: &RealityConfig{KeyID: "reality-nod_rv", ServerName: "www.apple.com", DestPort: 443},
|
||||
Artifacts: art("FIXEDPBKRvKey0123456789abcdefghijklmnopqrstu")},
|
||||
}
|
||||
nc := func(nodeID, clientID, relayPassword, shortID string) *NodeClient {
|
||||
return &NodeClient{
|
||||
NodeID: nodeID, CustomerID: "cus_1", Status: "active",
|
||||
Credentials: Credentials{ClientID: clientID, RelayPassword: relayPassword, ShortID: shortID},
|
||||
}
|
||||
nc := func(nodeID string) *NodeClient {
|
||||
return &NodeClient{NodeID: nodeID, CustomerID: "cus_1", Status: "active"}
|
||||
}
|
||||
clients := []*NodeClient{
|
||||
nc("nod_rf", "11111111-2222-3333-4444-000000000001", "unused", "abcd0001ef01"),
|
||||
nc("nod_ss", "11111111-2222-3333-4444-000000000003", "FRYXGBkaGxwdHh8gISIjJA==", "abcd0003ef01"),
|
||||
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"),
|
||||
nc("nod_rf"), nc("nod_ss"), nc("nod_hy"), nc("nod_any"),
|
||||
nc("nod_ws"), nc("nod_sk"), nc("nod_rv"),
|
||||
}
|
||||
return &Data{
|
||||
Servers: []*Server{entryServer, v6Server},
|
||||
Nodes: nodes,
|
||||
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": [
|
||||
{
|
||||
"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",
|
||||
@@ -10,56 +10,56 @@
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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": {
|
||||
"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": {
|
||||
"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"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
+19
-11
@@ -560,9 +560,13 @@ type Customer struct {
|
||||
ServerUsage map[string]*UsageSlot `json:"serverUsage,omitempty"`
|
||||
LastUsageAt *string `json:"lastUsageAt,omitempty"`
|
||||
LastOnlineAt *string `json:"lastOnlineAt,omitempty"`
|
||||
ExternalSources []ExtSource `json:"externalSources,omitempty"`
|
||||
ManualLinks []ManualLink `json:"manualLinks,omitempty"`
|
||||
Extra extraFields `json:"-"`
|
||||
// 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"`
|
||||
ManualLinks []ManualLink `json:"manualLinks,omitempty"`
|
||||
Extra extraFields `json:"-"`
|
||||
}
|
||||
|
||||
// ExtSource is one external subscription source on a customer
|
||||
@@ -781,14 +785,18 @@ func (c Credentials) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
type NodeClient struct {
|
||||
ID string `json:"id"`
|
||||
NodeID string `json:"nodeId"`
|
||||
CustomerID string `json:"customerId"`
|
||||
Credentials Credentials `json:"credentials"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
UpdatedAt string `json:"updatedAt"`
|
||||
Extra extraFields `json:"-"`
|
||||
ID string `json:"id"`
|
||||
NodeID string `json:"nodeId"`
|
||||
CustomerID string `json:"customerId"`
|
||||
// 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"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
UpdatedAt string `json:"updatedAt"`
|
||||
Extra extraFields `json:"-"`
|
||||
}
|
||||
|
||||
func (n *NodeClient) UnmarshalJSON(raw []byte) error {
|
||||
|
||||
@@ -53,8 +53,8 @@ func TestAggregateEntriesOrderAndDedup(t *testing.T) {
|
||||
},
|
||||
Servers: []*Server{{ID: "srv_1", Name: "S", PublicAddress: "s.example.com"}},
|
||||
NodeClients: []*NodeClient{
|
||||
{ID: "ncl_b", NodeID: "nod_b", CustomerID: "cus_1", Status: "active", Credentials: Credentials{RelayPassword: "pw-b"}},
|
||||
{ID: "ncl_a", NodeID: "nod_a", CustomerID: "cus_1", Status: "active", Credentials: Credentials{RelayPassword: "pw-a"}},
|
||||
{ID: "ncl_b", NodeID: "nod_b", CustomerID: "cus_1", Status: "active"},
|
||||
{ID: "ncl_a", NodeID: "nod_a", CustomerID: "cus_1", Status: "active"},
|
||||
},
|
||||
SubSourceCache: map[string]*SubSourceCacheEntry{
|
||||
"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()
|
||||
d.SubSourceCache["src_1"].OkAt = &okAt
|
||||
customer := &Customer{
|
||||
ID: "cus_1",
|
||||
ID: "cus_1", Credentials: &Credentials{ClientID: "11111111-2222-3333-4444-000000000001", RelayPassword: "pw-a", ShortID: "abcd0001ef01"},
|
||||
ManualLinks: []ManualLink{
|
||||
{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}},
|
||||
}
|
||||
d.Customers = []*Customer{customer}
|
||||
entries := AggregateEntries(d, customer)
|
||||
names := []string{}
|
||||
for _, entry := range entries {
|
||||
|
||||
Reference in New Issue
Block a user