Files
trihuy-russian/web/service/xray.go
T

279 lines
7.2 KiB
Go
Raw Normal View History

2023-02-09 22:48:06 +03:30
package service
import (
"encoding/json"
"errors"
"runtime"
2023-02-09 22:48:06 +03:30
"sync"
2024-03-11 01:01:24 +03:30
2025-09-19 10:05:43 +02:00
"github.com/mhsanaei/3x-ui/v2/logger"
"github.com/mhsanaei/3x-ui/v2/xray"
2023-02-18 16:07:32 +03:30
2023-02-09 22:48:06 +03:30
"go.uber.org/atomic"
)
2024-03-11 01:01:24 +03:30
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
2024-03-11 01:01:24 +03:30
result string
)
2023-02-09 22:48:06 +03:30
2025-09-20 09:35:50 +02:00
// XrayService provides business logic for Xray process management.
// It handles starting, stopping, restarting Xray, and managing its configuration.
2023-02-09 22:48:06 +03:30
type XrayService struct {
inboundService InboundService
settingService SettingService
2023-06-05 00:32:19 +03:30
xrayAPI xray.XrayAPI
2023-02-09 22:48:06 +03:30
}
2025-09-20 09:35:50 +02:00
// IsXrayRunning checks if the Xray process is currently running.
2023-02-09 22:48:06 +03:30
func (s *XrayService) IsXrayRunning() bool {
return p != nil && p.IsRunning()
}
2025-09-20 09:35:50 +02:00
// GetXrayErr returns the error from the Xray process, if any.
2023-02-09 22:48:06 +03:30
func (s *XrayService) GetXrayErr() error {
if p == nil {
return nil
}
err := p.GetErr()
2026-01-13 17:40:52 +01:00
if err == nil {
return nil
}
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
2023-02-09 22:48:06 +03:30
}
2025-09-20 09:35:50 +02:00
// GetXrayResult returns the result string from the Xray process.
2023-02-09 22:48:06 +03:30
func (s *XrayService) GetXrayResult() string {
if result != "" {
return result
}
if s.IsXrayRunning() {
return ""
}
if p == nil {
return ""
}
2023-02-09 22:48:06 +03:30
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 ""
}
2023-02-09 22:48:06 +03:30
return result
}
2025-09-20 09:35:50 +02:00
// GetXrayVersion returns the version of the running Xray process.
2023-02-09 22:48:06 +03:30
func (s *XrayService) GetXrayVersion() string {
if p == nil {
return "Unknown"
}
return p.GetVersion()
}
2023-02-18 16:07:32 +03:30
2025-09-20 09:35:50 +02:00
// RemoveIndex removes an element at the specified index from a slice.
// Returns a new slice with the element removed.
2025-03-12 20:13:51 +01:00
func RemoveIndex(s []any, index int) []any {
2023-02-09 22:48:06 +03:30
return append(s[:index], s[index+1:]...)
}
2025-09-20 09:35:50 +02:00
// GetXrayConfig retrieves and builds the Xray configuration from settings and inbounds.
2023-02-09 22:48:06 +03:30
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)
2023-02-09 22:48:06 +03:30
inbounds, err := s.inboundService.GetAllInbounds()
if err != nil {
return nil, err
}
for _, inbound := range inbounds {
if !inbound.Enable {
continue
}
// get settings clients
2025-03-12 20:13:51 +01:00
settings := map[string]any{}
2023-02-09 22:48:06 +03:30
json.Unmarshal([]byte(inbound.Settings), &settings)
2025-03-12 20:13:51 +01:00
clients, ok := settings["clients"].([]any)
2023-02-09 22:48:06 +03:30
if ok {
// Fast O(N) lookup map for client traffic enablement
2023-02-09 22:48:06 +03:30
clientStats := inbound.ClientStats
enableMap := make(map[string]bool, len(clientStats))
2023-02-09 22:48:06 +03:30
for _, clientTraffic := range clientStats {
enableMap[clientTraffic.Email] = clientTraffic.Enable
2023-02-09 22:48:06 +03:30
}
2023-04-09 23:13:18 +03:30
// filter and clean clients
2025-03-12 20:13:51 +01:00
var final_clients []any
2023-04-09 23:13:18 +03:30
for _, client := range clients {
c, ok := client.(map[string]any)
if !ok {
continue
2023-04-09 23:13:18 +03:30
}
email, _ := c["email"].(string)
// check users active or not via stats
if enable, exists := enableMap[email]; exists && !enable {
logger.Infof("Remove Inbound User %s due to expiration or traffic limit", email)
continue
}
// check manual disabled flag
if manualEnable, ok := c["enable"].(bool); ok && !manualEnable {
continue
}
// clear client config for additional parameters
2023-04-09 23:13:18 +03:30
for key := range c {
2026-05-05 21:00:03 +02:00
if key != "email" && key != "id" && key != "password" && key != "flow" && key != "method" && key != "auth" && key != "reverse" {
2023-04-09 23:13:18 +03:30
delete(c, key)
}
2026-04-20 16:05:27 +02:00
if flow, ok := c["flow"].(string); ok && flow == "xtls-rprx-vision-udp443" {
c["flow"] = "xtls-rprx-vision"
}
2023-04-09 23:13:18 +03:30
}
2025-03-12 20:13:51 +01:00
final_clients = append(final_clients, any(c))
2023-04-09 23:13:18 +03:30
}
settings["clients"] = final_clients
2023-04-24 14:13:25 +03:30
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
2023-02-09 22:48:06 +03:30
if err != nil {
return nil, err
}
2023-02-18 16:07:32 +03:30
2023-02-09 22:48:06 +03:30
inbound.Settings = string(modifiedSettings)
}
2023-12-08 18:45:21 +01:00
if len(inbound.StreamSettings) > 0 {
// Unmarshal stream JSON
2025-03-12 20:13:51 +01:00
var stream map[string]any
2023-12-08 18:45:21 +01:00
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
// Remove the "settings" field under "tlsSettings" and "realitySettings"
2025-03-12 20:13:51 +01:00
tlsSettings, ok1 := stream["tlsSettings"].(map[string]any)
realitySettings, ok2 := stream["realitySettings"].(map[string]any)
2023-12-08 18:45:21 +01:00
if ok1 || ok2 {
if ok1 {
delete(tlsSettings, "settings")
} else if ok2 {
delete(realitySettings, "settings")
}
}
2023-12-08 18:45:21 +01:00
delete(stream, "externalProxy")
newStream, err := json.MarshalIndent(stream, "", " ")
if err != nil {
return nil, err
}
inbound.StreamSettings = string(newStream)
}
2023-02-09 22:48:06 +03:30
inboundConfig := inbound.GenXrayInboundConfig()
xrayConfig.InboundConfigs = append(xrayConfig.InboundConfigs, *inboundConfig)
}
return xrayConfig, nil
}
2025-09-20 09:35:50 +02:00
// GetXrayTraffic fetches the current traffic statistics from the running Xray process.
2023-02-09 22:48:06 +03:30
func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, []*xray.ClientTraffic, error) {
if !s.IsXrayRunning() {
2024-07-08 23:08:00 +02:00
err := errors.New("xray is not running")
logger.Debug("Attempted to fetch Xray traffic, but Xray is not running:", err)
return nil, nil, err
2023-02-09 22:48:06 +03:30
}
2024-07-08 23:08:00 +02:00
apiPort := p.GetAPIPort()
if err := s.xrayAPI.Init(apiPort); err != nil {
logger.Debug("Failed to initialize Xray API:", err)
return nil, nil, err
}
2023-06-05 00:32:19 +03:30
defer s.xrayAPI.Close()
2024-07-08 23:08:00 +02:00
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
2023-02-09 22:48:06 +03:30
}
2025-09-20 09:35:50 +02:00
// RestartXray restarts the Xray process, optionally forcing a restart even if config unchanged.
2023-02-09 22:48:06 +03:30
func (s *XrayService) RestartXray(isForce bool) error {
lock.Lock()
defer lock.Unlock()
logger.Debug("restart Xray, force:", isForce)
isManuallyStopped.Store(false)
2023-02-09 22:48:06 +03:30
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")
2023-02-09 22:48:06 +03:30
return nil
}
p.Stop()
}
p = xray.NewProcess(xrayConfig)
result = ""
2023-06-05 00:32:19 +03:30
err = p.Start()
if err != nil {
return err
}
2023-06-05 00:32:19 +03:30
return nil
2023-02-09 22:48:06 +03:30
}
2025-09-20 09:35:50 +02:00
// StopXray stops the running Xray process.
2023-02-09 22:48:06 +03:30
func (s *XrayService) StopXray() error {
lock.Lock()
defer lock.Unlock()
isManuallyStopped.Store(true)
2024-07-08 23:08:00 +02:00
logger.Debug("Attempting to stop Xray...")
2023-02-09 22:48:06 +03:30
if s.IsXrayRunning() {
return p.Stop()
}
return errors.New("xray is not running")
}
2025-09-20 09:35:50 +02:00
// SetToNeedRestart marks that Xray needs to be restarted.
2023-02-09 22:48:06 +03:30
func (s *XrayService) SetToNeedRestart() {
isNeedXrayRestart.Store(true)
}
2025-09-20 09:35:50 +02:00
// IsNeedRestartAndSetFalse checks if restart is needed and resets the flag to false.
2023-02-09 22:48:06 +03:30
func (s *XrayService) IsNeedRestartAndSetFalse() bool {
2023-03-17 19:37:49 +03:30
return isNeedXrayRestart.CompareAndSwap(true, false)
2023-02-09 22:48:06 +03:30
}
2025-09-20 09:35:50 +02:00
// 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()
}