架构优化
This commit is contained in:
26
install.sh
26
install.sh
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user