架构优化

This commit is contained in:
CN-JS-HuiBai
2026-04-14 23:24:40 +08:00
parent 7a9a36d04f
commit 638dad7a0a
2 changed files with 147 additions and 55 deletions

View File

@@ -160,27 +160,7 @@ cat > "$CONFIG_FILE" <<EOF
"report_interval": "1m"
}
],
"inbounds": [
{
"type": "vless",
"tag": "vless-in",
"listen": "::",
"listen_port": 443,
"tls": {
"enabled": true,
"server_name": "www.google.com",
"reality": {
"enabled": true,
"handshake": {
"server": "www.google.com",
"server_port": 443
},
"private_key": "YOUR_PRIVATE_KEY",
"short_id": ["YOUR_SHORT_ID"]
}
}
}
],
"inbounds": [],
"outbounds": [
{
"type": "direct",
@@ -192,10 +172,6 @@ cat > "$CONFIG_FILE" <<EOF
{
"protocol": "dns",
"action": "hijack-dns"
},
{
"inbound": "vless-in",
"action": "sniff"
}
]
}

View File

@@ -38,22 +38,44 @@ type Service struct {
syncTicker *time.Ticker
reportTicker *time.Ticker
access sync.Mutex
inboundManager adapter.InboundManager
}
type XNodeConfig struct {
Port int `json:"port"`
Protocol string `json:"protocol"`
Settings json.RawMessage `json:"settings"`
StreamSettings json.RawMessage `json:"streamSettings"`
}
type XRealitySettings struct {
Dest string `json:"dest"`
ServerNames []string `json:"serverNames"`
PrivateKey string `json:"privateKey"`
ShortId string `json:"shortId"`
}
type XStreamSettings struct {
Network string `json:"network"`
Security string `json:"security"`
RealitySettings XRealitySettings `json:"realitySettings"`
}
func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.XBoardServiceOptions) (adapter.Service, error) {
ctx, cancel := context.WithCancel(ctx)
s := &Service{
Adapter: boxService.NewAdapter(C.TypeXBoard, tag),
ctx: ctx,
cancel: cancel,
logger: logger,
options: options,
httpClient: &http.Client{Timeout: 10 * time.Second},
traffics: make(map[string]*ssmapi.TrafficManager),
users: make(map[string]*ssmapi.UserManager),
servers: make(map[string]adapter.ManagedSSMServer),
syncTicker: time.NewTicker(time.Duration(options.SyncInterval)),
reportTicker: time.NewTicker(time.Duration(options.ReportInterval)),
Adapter: boxService.NewAdapter(C.TypeXBoard, tag),
ctx: ctx,
cancel: cancel,
logger: logger,
options: options,
httpClient: &http.Client{Timeout: 10 * time.Second},
traffics: make(map[string]*ssmapi.TrafficManager),
users: make(map[string]*ssmapi.UserManager),
servers: make(map[string]adapter.ManagedSSMServer),
syncTicker: time.NewTicker(time.Duration(options.SyncInterval)),
reportTicker: time.NewTicker(time.Duration(options.ReportInterval)),
inboundManager: service.FromContext[adapter.InboundManager](ctx),
}
if s.options.SyncInterval == 0 {
@@ -65,25 +87,6 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio
s.reportTicker = time.NewTicker(1 * time.Minute)
}
inboundManager := service.FromContext[adapter.InboundManager](ctx)
allInbounds := inboundManager.Inbounds()
for _, inbound := range allInbounds {
managedServer, isManaged := inbound.(adapter.ManagedSSMServer)
if isManaged {
traffic := ssmapi.NewTrafficManager()
managedServer.SetTracker(traffic)
user := ssmapi.NewUserManager(managedServer, traffic)
s.traffics[inbound.Tag()] = traffic
s.users[inbound.Tag()] = user
s.servers[inbound.Tag()] = managedServer
s.inboundTags = append(s.inboundTags, inbound.Tag())
}
}
if len(s.inboundTags) == 0 {
logger.Warn("Xboard service: no managed inbounds found")
}
return s, nil
}
@@ -91,10 +94,123 @@ func (s *Service) Start(stage adapter.StartStage) error {
if stage != adapter.StartStateStart {
return nil
}
// Fetch node config and setup inbound
err := s.setupNode()
if err != nil {
s.logger.Error("Xboard setup error: ", err)
// Don't return error to allow sing-box to continue, service will retry in loop
}
go s.loop()
return nil
}
func (s *Service) setupNode() error {
s.logger.Info("Xboard fetching node config...")
config, err := s.fetchConfig()
if err != nil {
return err
}
inboundTag := "xboard-inbound"
var inboundOptions any
switch config.Protocol {
case "vless":
vlessOptions := option.VLESSInboundOptions{
ListenOptions: option.ListenOptions{
ListenPort: uint16(config.Port),
},
}
// Handle Reality
var streamSettings XStreamSettings
json.Unmarshal(config.StreamSettings, &streamSettings)
if streamSettings.Security == "reality" {
vlessOptions.TLS = &option.InboundTLSOptions{
Enabled: true,
ServerName: streamSettings.RealitySettings.ServerNames[0],
Reality: &option.InboundRealityOptions{
Enabled: true,
Handshake: option.InboundRealityHandshakeOptions{
ServerOptions: option.ServerOptions{
Server: streamSettings.RealitySettings.Dest,
ServerPort: 443, // Default for most reality setups
},
},
PrivateKey: streamSettings.RealitySettings.PrivateKey,
ShortID: badoption.Listable[string]{streamSettings.RealitySettings.ShortId},
},
}
}
inboundOptions = vlessOptions
case "vmess":
vmessOptions := option.VMessInboundOptions{
ListenOptions: option.ListenOptions{
ListenPort: uint16(config.Port),
},
}
inboundOptions = vmessOptions
default:
return fmt.Errorf("unsupported protocol: %s", config.Protocol)
}
// Remove old if exists
s.inboundManager.Remove(inboundTag)
// Create new inbound
err = s.inboundManager.Create(s.ctx, nil, s.logger, inboundTag, config.Protocol, inboundOptions)
if err != nil {
return err
}
// Register the new inbound in our managed list
inbound, _ := s.inboundManager.Get(inboundTag)
managedServer, isManaged := inbound.(adapter.ManagedSSMServer)
if isManaged {
traffic := ssmapi.NewTrafficManager()
managedServer.SetTracker(traffic)
user := ssmapi.NewUserManager(managedServer, traffic)
s.access.Lock()
s.traffics[inboundTag] = traffic
s.users[inboundTag] = user
s.servers[inboundTag] = managedServer
s.inboundTags = []string{inboundTag}
s.access.Unlock()
s.logger.Info("Xboard dynamic inbound [", inboundTag, "] created on port ", config.Port)
}
return nil
}
func (s *Service) fetchConfig() (*XNodeConfig, error) {
url := fmt.Sprintf("%s/api/v1/server/UniProxy/config?node_id=%d", s.options.PanelURL, s.options.NodeID)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", s.options.Key)
resp, err := s.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, E.New("failed to fetch config, status: ", resp.Status)
}
var result struct {
Data XNodeConfig `json:"data"`
}
err = json.NewDecoder(resp.Body).Decode(&result)
if err != nil {
return nil, err
}
return &result.Data, nil
}
func (s *Service) loop() {
// Initial sync
s.syncUsers()