From ef2e4088efc5df98d13b5e9af2ea21c10738583b Mon Sep 17 00:00:00 2001 From: shenlan Date: Mon, 27 Oct 2025 02:23:12 +0800 Subject: [PATCH] Ensure VLESS users include encryption field (#588) --- account/internal/xrayconfig/generator.go | 16 ++++++++--- account/internal/xrayconfig/generator_test.go | 14 ++++++---- docs/xray-single-port-multi-user.md | 27 +++++++++++-------- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/account/internal/xrayconfig/generator.go b/account/internal/xrayconfig/generator.go index 25ecac5..1758dd5 100644 --- a/account/internal/xrayconfig/generator.go +++ b/account/internal/xrayconfig/generator.go @@ -14,14 +14,19 @@ const ( // DefaultFlow is applied to VLESS clients when no explicit flow is // provided. It matches the tlsSettings baked into the template. DefaultFlow = "xtls-rprx-vision" + + // DefaultEncryption is required by Xray for all VLESS users in the + // outbound array. + DefaultEncryption = "none" ) // Client represents an entry under outbounds[].settings.vnext[].users[] in the Xray // config. type Client struct { - ID string - Email string - Flow string + ID string + Email string + Flow string + Encryption string } // Generator updates the Xray configuration file based on a template and a set of @@ -111,6 +116,11 @@ func replaceClients(root map[string]interface{}, clients []Client) error { flow = DefaultFlow } entry["flow"] = flow + encryption := strings.TrimSpace(client.Encryption) + if encryption == "" { + encryption = DefaultEncryption + } + entry["encryption"] = encryption clientObjects = append(clientObjects, entry) } diff --git a/account/internal/xrayconfig/generator_test.go b/account/internal/xrayconfig/generator_test.go index f7872c9..5fbc825 100644 --- a/account/internal/xrayconfig/generator_test.go +++ b/account/internal/xrayconfig/generator_test.go @@ -13,9 +13,10 @@ type testConfig struct { Settings struct { VNext []struct { Users []struct { - ID string `json:"id"` - Email string `json:"email,omitempty"` - Flow string `json:"flow,omitempty"` + ID string `json:"id"` + Email string `json:"email,omitempty"` + Flow string `json:"flow,omitempty"` + Encryption string `json:"encryption"` } `json:"users"` Address string `json:"address"` } `json:"vnext"` @@ -57,7 +58,7 @@ func TestGeneratorGenerate(t *testing.T) { } clients := []Client{ - {ID: "uuid-a", Email: "a@demo", Flow: "xtls-rprx-vision"}, + {ID: "uuid-a", Email: "a@demo", Flow: "xtls-rprx-vision", Encryption: "custom"}, {ID: "uuid-b"}, } @@ -94,7 +95,10 @@ func TestGeneratorGenerate(t *testing.T) { if gotUsers[0].Flow != "xtls-rprx-vision" { t.Fatalf("unexpected first user flow: %+v", gotUsers[0]) } - if gotUsers[1].ID != "uuid-b" || gotUsers[1].Email != "" || gotUsers[1].Flow != DefaultFlow { + if gotUsers[0].Encryption != "custom" { + t.Fatalf("unexpected first user encryption: %+v", gotUsers[0]) + } + if gotUsers[1].ID != "uuid-b" || gotUsers[1].Email != "" || gotUsers[1].Flow != DefaultFlow || gotUsers[1].Encryption != DefaultEncryption { t.Fatalf("unexpected second user: %+v", gotUsers[1]) } } diff --git a/docs/xray-single-port-multi-user.md b/docs/xray-single-port-multi-user.md index 0ac3daf..d4fe494 100644 --- a/docs/xray-single-port-multi-user.md +++ b/docs/xray-single-port-multi-user.md @@ -19,12 +19,13 @@ The XControl platform manages access to an Xray proxy node that exposes a single ## Data Model -| Field | Source | Notes | -|--------------|---------------|-----------------------------------------------------------| -| `id` | Account table | Stored as UUID v4 for compatibility with Xray clients. | -| `email` | Account table | Optional identifier; used for auditing and debugging. | -| `flow` | Derived | Optional; defaults to `xtls-rprx-vision` for Vision mode. | -| `enabled` | Account table | Only enabled users contribute to the generated array. | +| Field | Source | Notes | +|--------------|---------------|------------------------------------------------------------------------| +| `id` | Account table | Stored as UUID v4 for compatibility with Xray clients. | +| `email` | Account table | Optional identifier; used for auditing and debugging. | +| `flow` | Derived | Optional; defaults to `xtls-rprx-vision` for Vision mode. | +| `encryption` | Derived | Required by Xray; defaults to `none` when not explicitly configured. | +| `enabled` | Account table | Only enabled users contribute to the generated array. | The backend queries all enabled accounts and materializes the JSON payload expected by Xray. @@ -54,8 +55,8 @@ The backend queries all enabled accounts and materializes the JSON payload expec 4. **Generate Configuration**: - Load the base template for `/usr/local/etc/xray/config.json`. - Replace the `outbounds[].settings.vnext[].users[]` node with the freshly computed array. New registrations simply append to the - slice composed in memory before the generator writes it back. Each client entry includes the UUID, optional email, and any - flow directive required by the transport profile. + slice composed in memory before the generator writes it back. Each client entry includes the UUID, optional email, required `encryption` + flag (defaulting to `none`), and any flow directive required by the transport profile. - Persist the resulting JSON atomically (write to temp file then move into place). 5. **Validate JSON**: - Run `jq . /usr/local/etc/xray/config.json` or an equivalent Go `json.Unmarshal` check to confirm syntax correctness. @@ -72,9 +73,10 @@ The backend queries all enabled accounts and materializes the JSON payload expec ```go // Client represents an entry in outbounds[].settings.vnext[].users[]. type Client struct { - ID string `json:"id"` - Email string `json:"email"` - Flow string `json:"flow"` + ID string `json:"id"` + Email string `json:"email"` + Flow string `json:"flow"` + Encryption string `json:"encryption"` } func SyncXrayClients(ctx context.Context, db *sql.DB, fs afero.Fs, runner command.Runner) error { @@ -93,6 +95,9 @@ func SyncXrayClients(ctx context.Context, db *sql.DB, fs afero.Fs, runner comman if cfg.Outbounds[0].Settings.VNext[0].Users[i].Flow == "" { cfg.Outbounds[0].Settings.VNext[0].Users[i].Flow = "xtls-rprx-vision" } + if cfg.Outbounds[0].Settings.VNext[0].Users[i].Encryption == "" { + cfg.Outbounds[0].Settings.VNext[0].Users[i].Encryption = "none" + } } buf, err := json.MarshalIndent(cfg, "", " ")