feat(mtproto): per-client ad-tags, management-API auth, and record secret sync

Catch the panel up to the mtg-multi README (v1.14.0):

- Each client can now carry its own 32-hex advertising tag overriding the
  inbound-level one. The tag lives on the client (settings JSON is the
  source of truth, clients.ad_tag is the UI projection), is rendered into
  the fork's [secret-ad-tags] section for active secrets only (mtg rejects
  a config whose override names an unknown secret), is pushed per entry
  through PUT /secrets, and is part of the reload fingerprint so a tag
  edit hot-applies without dropping connections.
- The loopback management API can replace the whole secret set, so every
  mtg process now gets a random per-process api-token; the manager sends
  it as a bearer token on PUT /secrets and GET /stats and reuses it across
  config rewrites, because mtg reads the token only at startup.
- Malformed tags are rejected at every save path and additionally dropped
  in InstanceFromInbound: one bad tag would otherwise fail the whole
  generated config and take every client of the inbound down with it.
- SyncInbound never copied a re-keyed mtproto secret into the canonical
  clients table, so the clients page and subscription links kept serving
  the old secret, which mtg then rejects. It is now guarded-copied like
  the other credentials.
This commit is contained in:
MHSanaei
2026-07-07 12:00:43 +02:00
parent 659f0f404c
commit 43500a5470
33 changed files with 361 additions and 54 deletions
+2
View File
@@ -214,6 +214,7 @@ export const EXAMPLES: Record<string, unknown> = {
"token": "new-token-string"
},
"Client": {
"adTag": "0123456789abcdef0123456789abcdef",
"allowedIPs": [
""
],
@@ -248,6 +249,7 @@ export const EXAMPLES: Record<string, unknown> = {
"inboundId": 0
},
"ClientRecord": {
"adTag": "",
"allowedIPs": "",
"auth": "",
"comment": "",
+8
View File
@@ -1066,6 +1066,10 @@ export const SCHEMAS: Record<string, unknown> = {
"Client": {
"description": "Client represents a client configuration for Xray inbounds with traffic limits and settings.",
"properties": {
"adTag": {
"example": "0123456789abcdef0123456789abcdef",
"type": "string"
},
"allowedIPs": {
"items": {
"type": "string"
@@ -1205,6 +1209,9 @@ export const SCHEMAS: Record<string, unknown> = {
},
"ClientRecord": {
"properties": {
"adTag": {
"type": "string"
},
"allowedIPs": {
"type": "string"
},
@@ -1280,6 +1287,7 @@ export const SCHEMAS: Record<string, unknown> = {
}
},
"required": [
"adTag",
"allowedIPs",
"auth",
"comment",
+2
View File
@@ -224,6 +224,7 @@ export interface ApiTokenView {
}
export interface Client {
adTag?: string;
allowedIPs?: string[];
auth?: string;
comment: string;
@@ -258,6 +259,7 @@ export interface ClientInbound {
}
export interface ClientRecord {
adTag: string;
allowedIPs: string;
auth: string;
comment: string;
+2
View File
@@ -240,6 +240,7 @@ export const ApiTokenViewSchema = z.object({
export type ApiTokenView = z.infer<typeof ApiTokenViewSchema>;
export const ClientSchema = z.object({
adTag: z.string().optional(),
allowedIPs: z.array(z.string()).optional(),
auth: z.string().optional(),
comment: z.string(),
@@ -276,6 +277,7 @@ export const ClientInboundSchema = z.object({
export type ClientInbound = z.infer<typeof ClientInboundSchema>;
export const ClientRecordSchema = z.object({
adTag: z.string(),
allowedIPs: z.string(),
auth: z.string(),
comment: z.string(),