| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 | 
							- package service
 
- import (
 
- 	"encoding/json"
 
- 	"errors"
 
- 	"runtime"
 
- 	"sync"
 
- 	"github.com/mhsanaei/3x-ui/v2/logger"
 
- 	"github.com/mhsanaei/3x-ui/v2/xray"
 
- 	"go.uber.org/atomic"
 
- )
 
- var (
 
- 	p                 *xray.Process
 
- 	lock              sync.Mutex
 
- 	isNeedXrayRestart atomic.Bool // Indicates that restart was requested for Xray
 
- 	isManuallyStopped atomic.Bool // Indicates that Xray was stopped manually from the panel
 
- 	result            string
 
- )
 
- // XrayService provides business logic for Xray process management.
 
- // It handles starting, stopping, restarting Xray, and managing its configuration.
 
- type XrayService struct {
 
- 	inboundService InboundService
 
- 	settingService SettingService
 
- 	xrayAPI        xray.XrayAPI
 
- }
 
- // IsXrayRunning checks if the Xray process is currently running.
 
- func (s *XrayService) IsXrayRunning() bool {
 
- 	return p != nil && p.IsRunning()
 
- }
 
- // GetXrayErr returns the error from the Xray process, if any.
 
- func (s *XrayService) GetXrayErr() error {
 
- 	if p == nil {
 
- 		return nil
 
- 	}
 
- 	err := p.GetErr()
 
- 	if runtime.GOOS == "windows" && err.Error() == "exit status 1" {
 
- 		// exit status 1 on Windows means that Xray process was killed
 
- 		// as we kill process to stop in on Windows, this is not an error
 
- 		return nil
 
- 	}
 
- 	return err
 
- }
 
- // GetXrayResult returns the result string from the Xray process.
 
- func (s *XrayService) GetXrayResult() string {
 
- 	if result != "" {
 
- 		return result
 
- 	}
 
- 	if s.IsXrayRunning() {
 
- 		return ""
 
- 	}
 
- 	if p == nil {
 
- 		return ""
 
- 	}
 
- 	result = p.GetResult()
 
- 	if runtime.GOOS == "windows" && result == "exit status 1" {
 
- 		// exit status 1 on Windows means that Xray process was killed
 
- 		// as we kill process to stop in on Windows, this is not an error
 
- 		return ""
 
- 	}
 
- 	return result
 
- }
 
- // GetXrayVersion returns the version of the running Xray process.
 
- func (s *XrayService) GetXrayVersion() string {
 
- 	if p == nil {
 
- 		return "Unknown"
 
- 	}
 
- 	return p.GetVersion()
 
- }
 
- // RemoveIndex removes an element at the specified index from a slice.
 
- // Returns a new slice with the element removed.
 
- func RemoveIndex(s []any, index int) []any {
 
- 	return append(s[:index], s[index+1:]...)
 
- }
 
- // GetXrayConfig retrieves and builds the Xray configuration from settings and inbounds.
 
- func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
 
- 	templateConfig, err := s.settingService.GetXrayConfigTemplate()
 
- 	if err != nil {
 
- 		return nil, err
 
- 	}
 
- 	xrayConfig := &xray.Config{}
 
- 	err = json.Unmarshal([]byte(templateConfig), xrayConfig)
 
- 	if err != nil {
 
- 		return nil, err
 
- 	}
 
- 	s.inboundService.AddTraffic(nil, nil)
 
- 	inbounds, err := s.inboundService.GetAllInbounds()
 
- 	if err != nil {
 
- 		return nil, err
 
- 	}
 
- 	for _, inbound := range inbounds {
 
- 		if !inbound.Enable {
 
- 			continue
 
- 		}
 
- 		// get settings clients
 
- 		settings := map[string]any{}
 
- 		json.Unmarshal([]byte(inbound.Settings), &settings)
 
- 		clients, ok := settings["clients"].([]any)
 
- 		if ok {
 
- 			// check users active or not
 
- 			clientStats := inbound.ClientStats
 
- 			for _, clientTraffic := range clientStats {
 
- 				indexDecrease := 0
 
- 				for index, client := range clients {
 
- 					c := client.(map[string]any)
 
- 					if c["email"] == clientTraffic.Email {
 
- 						if !clientTraffic.Enable {
 
- 							clients = RemoveIndex(clients, index-indexDecrease)
 
- 							indexDecrease++
 
- 							logger.Infof("Remove Inbound User %s due to expiration or traffic limit", c["email"])
 
- 						}
 
- 					}
 
- 				}
 
- 			}
 
- 			// clear client config for additional parameters
 
- 			var final_clients []any
 
- 			for _, client := range clients {
 
- 				c := client.(map[string]any)
 
- 				if c["enable"] != nil {
 
- 					if enable, ok := c["enable"].(bool); ok && !enable {
 
- 						continue
 
- 					}
 
- 				}
 
- 				for key := range c {
 
- 					if key != "email" && key != "id" && key != "password" && key != "flow" && key != "method" {
 
- 						delete(c, key)
 
- 					}
 
- 					if c["flow"] == "xtls-rprx-vision-udp443" {
 
- 						c["flow"] = "xtls-rprx-vision"
 
- 					}
 
- 				}
 
- 				final_clients = append(final_clients, any(c))
 
- 			}
 
- 			settings["clients"] = final_clients
 
- 			modifiedSettings, err := json.MarshalIndent(settings, "", "  ")
 
- 			if err != nil {
 
- 				return nil, err
 
- 			}
 
- 			inbound.Settings = string(modifiedSettings)
 
- 		}
 
- 		if len(inbound.StreamSettings) > 0 {
 
- 			// Unmarshal stream JSON
 
- 			var stream map[string]any
 
- 			json.Unmarshal([]byte(inbound.StreamSettings), &stream)
 
- 			// Remove the "settings" field under "tlsSettings" and "realitySettings"
 
- 			tlsSettings, ok1 := stream["tlsSettings"].(map[string]any)
 
- 			realitySettings, ok2 := stream["realitySettings"].(map[string]any)
 
- 			if ok1 || ok2 {
 
- 				if ok1 {
 
- 					delete(tlsSettings, "settings")
 
- 				} else if ok2 {
 
- 					delete(realitySettings, "settings")
 
- 				}
 
- 			}
 
- 			delete(stream, "externalProxy")
 
- 			newStream, err := json.MarshalIndent(stream, "", "  ")
 
- 			if err != nil {
 
- 				return nil, err
 
- 			}
 
- 			inbound.StreamSettings = string(newStream)
 
- 		}
 
- 		inboundConfig := inbound.GenXrayInboundConfig()
 
- 		xrayConfig.InboundConfigs = append(xrayConfig.InboundConfigs, *inboundConfig)
 
- 	}
 
- 	return xrayConfig, nil
 
- }
 
- // GetXrayTraffic fetches the current traffic statistics from the running Xray process.
 
- func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, []*xray.ClientTraffic, error) {
 
- 	if !s.IsXrayRunning() {
 
- 		err := errors.New("xray is not running")
 
- 		logger.Debug("Attempted to fetch Xray traffic, but Xray is not running:", err)
 
- 		return nil, nil, err
 
- 	}
 
- 	apiPort := p.GetAPIPort()
 
- 	s.xrayAPI.Init(apiPort)
 
- 	defer s.xrayAPI.Close()
 
- 	traffic, clientTraffic, err := s.xrayAPI.GetTraffic(true)
 
- 	if err != nil {
 
- 		logger.Debug("Failed to fetch Xray traffic:", err)
 
- 		return nil, nil, err
 
- 	}
 
- 	return traffic, clientTraffic, nil
 
- }
 
- // RestartXray restarts the Xray process, optionally forcing a restart even if config unchanged.
 
- func (s *XrayService) RestartXray(isForce bool) error {
 
- 	lock.Lock()
 
- 	defer lock.Unlock()
 
- 	logger.Debug("restart Xray, force:", isForce)
 
- 	isManuallyStopped.Store(false)
 
- 	xrayConfig, err := s.GetXrayConfig()
 
- 	if err != nil {
 
- 		return err
 
- 	}
 
- 	if s.IsXrayRunning() {
 
- 		if !isForce && p.GetConfig().Equals(xrayConfig) && !isNeedXrayRestart.Load() {
 
- 			logger.Debug("It does not need to restart Xray")
 
- 			return nil
 
- 		}
 
- 		p.Stop()
 
- 	}
 
- 	p = xray.NewProcess(xrayConfig)
 
- 	result = ""
 
- 	err = p.Start()
 
- 	if err != nil {
 
- 		return err
 
- 	}
 
- 	return nil
 
- }
 
- // StopXray stops the running Xray process.
 
- func (s *XrayService) StopXray() error {
 
- 	lock.Lock()
 
- 	defer lock.Unlock()
 
- 	isManuallyStopped.Store(true)
 
- 	logger.Debug("Attempting to stop Xray...")
 
- 	if s.IsXrayRunning() {
 
- 		return p.Stop()
 
- 	}
 
- 	return errors.New("xray is not running")
 
- }
 
- // SetToNeedRestart marks that Xray needs to be restarted.
 
- func (s *XrayService) SetToNeedRestart() {
 
- 	isNeedXrayRestart.Store(true)
 
- }
 
- // IsNeedRestartAndSetFalse checks if restart is needed and resets the flag to false.
 
- func (s *XrayService) IsNeedRestartAndSetFalse() bool {
 
- 	return isNeedXrayRestart.CompareAndSwap(true, false)
 
- }
 
- // DidXrayCrash checks if Xray crashed by verifying it's not running and wasn't manually stopped.
 
- func (s *XrayService) DidXrayCrash() bool {
 
- 	return !s.IsXrayRunning() && !isManuallyStopped.Load()
 
- }
 
 
  |