@@ -2,7 +2,9 @@ package main
22
33import (
44 "context"
5+ "encoding/json"
56 "fmt"
7+ "os"
68
79 proxyman_command "github.com/xtls/xray-core/app/proxyman/command"
810 stats_command "github.com/xtls/xray-core/app/stats/command"
@@ -13,12 +15,50 @@ import (
1315 "google.golang.org/grpc/credentials/insecure"
1416)
1517
16- // XrayClient wraps the Xray gRPC API
18+ // xrayConfigPath is the canonical location written by nodeSetup.installXray.
19+ const xrayConfigPath = "/usr/local/etc/xray/config.json"
20+
21+ // probeFlowFromXrayConfig reads the on-disk Xray config, finds the inbound
22+ // with the given tag and returns the flow value from its first client.
23+ // This is a best-effort backward-compat helper for legacy panels that did
24+ // not write the explicit per-tag Flow into cc-agent config.json.
25+ func probeFlowFromXrayConfig (tag string ) (string , bool ) {
26+ data , err := os .ReadFile (xrayConfigPath )
27+ if err != nil {
28+ return "" , false
29+ }
30+
31+ var parsed struct {
32+ Inbounds []struct {
33+ Tag string `json:"tag"`
34+ Settings struct {
35+ Clients []struct {
36+ Flow string `json:"flow"`
37+ } `json:"clients"`
38+ } `json:"settings"`
39+ } `json:"inbounds"`
40+ }
41+ if err := json .Unmarshal (data , & parsed ); err != nil {
42+ return "" , false
43+ }
44+
45+ for _ , ib := range parsed .Inbounds {
46+ if ib .Tag == tag && len (ib .Settings .Clients ) > 0 {
47+ return ib .Settings .Clients [0 ].Flow , true
48+ }
49+ }
50+ return "" , false
51+ }
52+
53+ // XrayClient wraps the Xray gRPC API. It owns the list of VLESS inbounds the
54+ // agent must keep in sync — every AddUser/RemoveUser call iterates over them.
55+ // The per-tag Flow value is used as-is when calling AlterInbound, so flow=""
56+ // is sent for transports where flow is not supported (WS/gRPC/XHTTP).
1757type XrayClient struct {
18- conn * grpc.ClientConn
19- proxyman proxyman_command.HandlerServiceClient
20- stats stats_command.StatsServiceClient
21- inboundTag string
58+ conn * grpc.ClientConn
59+ proxyman proxyman_command.HandlerServiceClient
60+ stats stats_command.StatsServiceClient
61+ inbounds [] InboundEntry
2262}
2363
2464func NewXrayClient (cfg * Config ) (* XrayClient , error ) {
@@ -29,47 +69,64 @@ func NewXrayClient(cfg *Config) (*XrayClient, error) {
2969 return nil , fmt .Errorf ("grpc.NewClient: %w" , err )
3070 }
3171
72+ // LoadConfig guarantees Inbounds is populated (synthesizes a single
73+ // entry from the legacy InboundTag when missing). No further fallback
74+ // is needed here.
3275 return & XrayClient {
33- conn : conn ,
34- proxyman : proxyman_command .NewHandlerServiceClient (conn ),
35- stats : stats_command .NewStatsServiceClient (conn ),
36- inboundTag : cfg .InboundTag ,
76+ conn : conn ,
77+ proxyman : proxyman_command .NewHandlerServiceClient (conn ),
78+ stats : stats_command .NewStatsServiceClient (conn ),
79+ inbounds : cfg .Inbounds ,
3780 }, nil
3881}
3982
40- // AddUser adds a VLESS user to the Xray inbound via gRPC
83+ // AddUser adds a VLESS user to every configured Xray inbound via gRPC.
84+ // Flow is taken from the per-inbound configuration; the value of u.Flow
85+ // is intentionally ignored — the agent is the source of truth here.
4186func (c * XrayClient ) AddUser (ctx context.Context , u * User ) error {
42- _ , err := c .proxyman .AlterInbound (ctx , & proxyman_command.AlterInboundRequest {
43- Tag : c .inboundTag ,
44- Operation : serial .ToTypedMessage (& proxyman_command.AddUserOperation {
45- User : & protocol.User {
46- Level : 0 ,
47- Email : u .Email ,
48- Account : serial .ToTypedMessage (& vless.Account {
49- Id : u .ID ,
50- Flow : u .Flow ,
51- }),
52- },
53- }),
54- })
55- if err != nil {
56- return fmt .Errorf ("AddUser %s: %w" , u .Email , err )
87+ if len (c .inbounds ) == 0 {
88+ return fmt .Errorf ("AddUser %s: no inbounds configured" , u .Email )
5789 }
58- return nil
90+ var firstErr error
91+ for _ , ib := range c .inbounds {
92+ _ , err := c .proxyman .AlterInbound (ctx , & proxyman_command.AlterInboundRequest {
93+ Tag : ib .Tag ,
94+ Operation : serial .ToTypedMessage (& proxyman_command.AddUserOperation {
95+ User : & protocol.User {
96+ Level : 0 ,
97+ Email : u .Email ,
98+ Account : serial .ToTypedMessage (& vless.Account {
99+ Id : u .ID ,
100+ Flow : ib .Flow ,
101+ }),
102+ },
103+ }),
104+ })
105+ if err != nil && firstErr == nil {
106+ firstErr = fmt .Errorf ("AddUser %s on %s: %w" , u .Email , ib .Tag , err )
107+ }
108+ }
109+ return firstErr
59110}
60111
61- // RemoveUser removes a user from the Xray inbound via gRPC
112+ // RemoveUser removes a user from every configured Xray inbound via gRPC.
62113func (c * XrayClient ) RemoveUser (ctx context.Context , email string ) error {
63- _ , err := c .proxyman .AlterInbound (ctx , & proxyman_command.AlterInboundRequest {
64- Tag : c .inboundTag ,
65- Operation : serial .ToTypedMessage (& proxyman_command.RemoveUserOperation {
66- Email : email ,
67- }),
68- })
69- if err != nil {
70- return fmt .Errorf ("RemoveUser %s: %w" , email , err )
114+ if len (c .inbounds ) == 0 {
115+ return fmt .Errorf ("RemoveUser %s: no inbounds configured" , email )
116+ }
117+ var firstErr error
118+ for _ , ib := range c .inbounds {
119+ _ , err := c .proxyman .AlterInbound (ctx , & proxyman_command.AlterInboundRequest {
120+ Tag : ib .Tag ,
121+ Operation : serial .ToTypedMessage (& proxyman_command.RemoveUserOperation {
122+ Email : email ,
123+ }),
124+ })
125+ if err != nil && firstErr == nil {
126+ firstErr = fmt .Errorf ("RemoveUser %s on %s: %w" , email , ib .Tag , err )
127+ }
71128 }
72- return nil
129+ return firstErr
73130}
74131
75132// QueryStats fetches traffic stats from Xray matching the given pattern.
0 commit comments