feat(inbound): excludeFromSub hides links without disabling (#6463)

* feat(inbound): excludeFromSub hides links without disabling

Add a per-inbound flag that omits subscription output while keeping the
inbound enabled for Xray, auth, and traffic accounting. Fixes #6435.

* fix(inbound): excludeFromSub review follow-ups

gofumpt model.go, sync docs OpenAPI, keep excludeFromSub master-authored
on node mirror, and exercise the legacy add-column migration path in tests.

* fix(sub): keep excluded inbounds' clients in the usage header

The excludeFromSub filter sat in getInboundsBySubId's SQL, so an excluded
inbound's clients never reached seenEmails in the raw, Clash or JSON
renderer. A client that lives only on a hidden inbound (one client per
inbound sharing a subId) dropped out of the Subscription-Userinfo usage,
quota and expiry and out of the info-node state, while the inbound kept
serving it and counting its traffic.

The query returns every enabled inbound again; each renderer skips an
excluded inbound's links but still counts its clients, the same rule the
Clash renderer already applies to external links it cannot express.

---------

Co-authored-by: mrchatam <mrchatam@users.noreply.github.com>
Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com>
This commit is contained in:
mrchatam
2026-09-27 13:56:37 +03:30
committed by GitHub
parent 12d51d7195
commit 6f40a75909
36 changed files with 347 additions and 0 deletions
+1
View File
@@ -796,6 +796,7 @@ export const EXAMPLES: Record<string, unknown> = {
"disableFlow": false,
"down": 0,
"enable": true,
"excludeFromSub": false,
"expiryTime": 0,
"fallbackParent": null,
"id": 1,
+6
View File
@@ -3028,6 +3028,11 @@ export const SCHEMAS: Record<string, unknown> = {
"example": true,
"type": "boolean"
},
"excludeFromSub": {
"description": "Whether to omit this inbound from subscription output while keeping it operational",
"example": false,
"type": "boolean"
},
"expiryTime": {
"description": "Expiration timestamp",
"format": "int64",
@@ -3151,6 +3156,7 @@ export const SCHEMAS: Record<string, unknown> = {
"disableFlow",
"down",
"enable",
"excludeFromSub",
"expiryTime",
"id",
"lastTrafficResetTime",
+1
View File
@@ -705,6 +705,7 @@ export interface Inbound {
disableFlow: boolean;
down: number;
enable: boolean;
excludeFromSub: boolean;
expiryTime: number;
fallbackParent?: FallbackParentInfo | null;
id: number;
+1
View File
@@ -748,6 +748,7 @@ export const InboundSchema = z.object({
disableFlow: z.boolean(),
down: z.number().int(),
enable: z.boolean(),
excludeFromSub: z.boolean(),
expiryTime: z.number().int(),
fallbackParent: z.lazy(() => FallbackParentInfoSchema).nullable().optional(),
id: z.number().int(),