| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292 | 
							- package xray
 
- import (
 
- 	"bytes"
 
- 	"encoding/json"
 
- 	"errors"
 
- 	"fmt"
 
- 	"io/fs"
 
- 	"os"
 
- 	"os/exec"
 
- 	"runtime"
 
- 	"strings"
 
- 	"syscall"
 
- 	"time"
 
- 	"github.com/mhsanaei/3x-ui/v2/config"
 
- 	"github.com/mhsanaei/3x-ui/v2/logger"
 
- 	"github.com/mhsanaei/3x-ui/v2/util/common"
 
- )
 
- // GetBinaryName returns the Xray binary filename for the current OS and architecture.
 
- func GetBinaryName() string {
 
- 	return fmt.Sprintf("xray-%s-%s", runtime.GOOS, runtime.GOARCH)
 
- }
 
- // GetBinaryPath returns the full path to the Xray binary executable.
 
- func GetBinaryPath() string {
 
- 	return config.GetBinFolderPath() + "/" + GetBinaryName()
 
- }
 
- // GetConfigPath returns the path to the Xray configuration file in the binary folder.
 
- func GetConfigPath() string {
 
- 	return config.GetBinFolderPath() + "/config.json"
 
- }
 
- // GetGeositePath returns the path to the geosite data file used by Xray.
 
- func GetGeositePath() string {
 
- 	return config.GetBinFolderPath() + "/geosite.dat"
 
- }
 
- // GetGeoipPath returns the path to the geoip data file used by Xray.
 
- func GetGeoipPath() string {
 
- 	return config.GetBinFolderPath() + "/geoip.dat"
 
- }
 
- // GetIPLimitLogPath returns the path to the IP limit log file.
 
- func GetIPLimitLogPath() string {
 
- 	return config.GetLogFolder() + "/3xipl.log"
 
- }
 
- // GetIPLimitBannedLogPath returns the path to the banned IP log file.
 
- func GetIPLimitBannedLogPath() string {
 
- 	return config.GetLogFolder() + "/3xipl-banned.log"
 
- }
 
- // GetIPLimitBannedPrevLogPath returns the path to the previous banned IP log file.
 
- func GetIPLimitBannedPrevLogPath() string {
 
- 	return config.GetLogFolder() + "/3xipl-banned.prev.log"
 
- }
 
- // GetAccessPersistentLogPath returns the path to the persistent access log file.
 
- func GetAccessPersistentLogPath() string {
 
- 	return config.GetLogFolder() + "/3xipl-ap.log"
 
- }
 
- // GetAccessPersistentPrevLogPath returns the path to the previous persistent access log file.
 
- func GetAccessPersistentPrevLogPath() string {
 
- 	return config.GetLogFolder() + "/3xipl-ap.prev.log"
 
- }
 
- // GetAccessLogPath reads the Xray config and returns the access log file path.
 
- func GetAccessLogPath() (string, error) {
 
- 	config, err := os.ReadFile(GetConfigPath())
 
- 	if err != nil {
 
- 		logger.Warningf("Failed to read configuration file: %s", err)
 
- 		return "", err
 
- 	}
 
- 	jsonConfig := map[string]any{}
 
- 	err = json.Unmarshal([]byte(config), &jsonConfig)
 
- 	if err != nil {
 
- 		logger.Warningf("Failed to parse JSON configuration: %s", err)
 
- 		return "", err
 
- 	}
 
- 	if jsonConfig["log"] != nil {
 
- 		jsonLog := jsonConfig["log"].(map[string]any)
 
- 		if jsonLog["access"] != nil {
 
- 			accessLogPath := jsonLog["access"].(string)
 
- 			return accessLogPath, nil
 
- 		}
 
- 	}
 
- 	return "", err
 
- }
 
- // stopProcess calls Stop on the given Process instance.
 
- func stopProcess(p *Process) {
 
- 	p.Stop()
 
- }
 
- // Process wraps an Xray process instance and provides management methods.
 
- type Process struct {
 
- 	*process
 
- }
 
- // NewProcess creates a new Xray process and sets up cleanup on garbage collection.
 
- func NewProcess(xrayConfig *Config) *Process {
 
- 	p := &Process{newProcess(xrayConfig)}
 
- 	runtime.SetFinalizer(p, stopProcess)
 
- 	return p
 
- }
 
- type process struct {
 
- 	cmd *exec.Cmd
 
- 	version string
 
- 	apiPort int
 
- 	onlineClients []string
 
- 	config    *Config
 
- 	logWriter *LogWriter
 
- 	exitErr   error
 
- 	startTime time.Time
 
- }
 
- // newProcess creates a new internal process struct for Xray.
 
- func newProcess(config *Config) *process {
 
- 	return &process{
 
- 		version:   "Unknown",
 
- 		config:    config,
 
- 		logWriter: NewLogWriter(),
 
- 		startTime: time.Now(),
 
- 	}
 
- }
 
- // IsRunning returns true if the Xray process is currently running.
 
- func (p *process) IsRunning() bool {
 
- 	if p.cmd == nil || p.cmd.Process == nil {
 
- 		return false
 
- 	}
 
- 	if p.cmd.ProcessState == nil {
 
- 		return true
 
- 	}
 
- 	return false
 
- }
 
- // GetErr returns the last error encountered by the Xray process.
 
- func (p *process) GetErr() error {
 
- 	return p.exitErr
 
- }
 
- // GetResult returns the last log line or error from the Xray process.
 
- func (p *process) GetResult() string {
 
- 	if len(p.logWriter.lastLine) == 0 && p.exitErr != nil {
 
- 		return p.exitErr.Error()
 
- 	}
 
- 	return p.logWriter.lastLine
 
- }
 
- // GetVersion returns the version string of the Xray process.
 
- func (p *process) GetVersion() string {
 
- 	return p.version
 
- }
 
- // GetAPIPort returns the API port used by the Xray process.
 
- func (p *Process) GetAPIPort() int {
 
- 	return p.apiPort
 
- }
 
- // GetConfig returns the configuration used by the Xray process.
 
- func (p *Process) GetConfig() *Config {
 
- 	return p.config
 
- }
 
- // GetOnlineClients returns the list of online clients for the Xray process.
 
- func (p *Process) GetOnlineClients() []string {
 
- 	return p.onlineClients
 
- }
 
- // SetOnlineClients sets the list of online clients for the Xray process.
 
- func (p *Process) SetOnlineClients(users []string) {
 
- 	p.onlineClients = users
 
- }
 
- // GetUptime returns the uptime of the Xray process in seconds.
 
- func (p *Process) GetUptime() uint64 {
 
- 	return uint64(time.Since(p.startTime).Seconds())
 
- }
 
- // refreshAPIPort updates the API port from the inbound configs.
 
- func (p *process) refreshAPIPort() {
 
- 	for _, inbound := range p.config.InboundConfigs {
 
- 		if inbound.Tag == "api" {
 
- 			p.apiPort = inbound.Port
 
- 			break
 
- 		}
 
- 	}
 
- }
 
- // refreshVersion updates the version string by running the Xray binary with -version.
 
- func (p *process) refreshVersion() {
 
- 	cmd := exec.Command(GetBinaryPath(), "-version")
 
- 	data, err := cmd.Output()
 
- 	if err != nil {
 
- 		p.version = "Unknown"
 
- 	} else {
 
- 		datas := bytes.Split(data, []byte(" "))
 
- 		if len(datas) <= 1 {
 
- 			p.version = "Unknown"
 
- 		} else {
 
- 			p.version = string(datas[1])
 
- 		}
 
- 	}
 
- }
 
- // Start launches the Xray process with the current configuration.
 
- func (p *process) Start() (err error) {
 
- 	if p.IsRunning() {
 
- 		return errors.New("xray is already running")
 
- 	}
 
- 	defer func() {
 
- 		if err != nil {
 
- 			logger.Error("Failure in running xray-core process: ", err)
 
- 			p.exitErr = err
 
- 		}
 
- 	}()
 
- 	data, err := json.MarshalIndent(p.config, "", "  ")
 
- 	if err != nil {
 
- 		return common.NewErrorf("Failed to generate XRAY configuration files: %v", err)
 
- 	}
 
- 	err = os.MkdirAll(config.GetLogFolder(), 0o770)
 
- 	if err != nil {
 
- 		logger.Warningf("Failed to create log folder: %s", err)
 
- 	}
 
- 	configPath := GetConfigPath()
 
- 	err = os.WriteFile(configPath, data, fs.ModePerm)
 
- 	if err != nil {
 
- 		return common.NewErrorf("Failed to write configuration file: %v", err)
 
- 	}
 
- 	cmd := exec.Command(GetBinaryPath(), "-c", configPath)
 
- 	p.cmd = cmd
 
- 	cmd.Stdout = p.logWriter
 
- 	cmd.Stderr = p.logWriter
 
- 	go func() {
 
- 		err := cmd.Run()
 
- 		if err != nil {
 
- 			// On Windows, killing the process results in "exit status 1" which isn't an error for us
 
- 			if runtime.GOOS == "windows" {
 
- 				errStr := strings.ToLower(err.Error())
 
- 				if strings.Contains(errStr, "exit status 1") {
 
- 					// Suppress noisy log on graceful stop
 
- 					p.exitErr = err
 
- 					return
 
- 				}
 
- 			}
 
- 			logger.Error("Failure in running xray-core:", err)
 
- 			p.exitErr = err
 
- 		}
 
- 	}()
 
- 	p.refreshVersion()
 
- 	p.refreshAPIPort()
 
- 	return nil
 
- }
 
- // Stop terminates the running Xray process.
 
- func (p *process) Stop() error {
 
- 	if !p.IsRunning() {
 
- 		return errors.New("xray is not running")
 
- 	}
 
- 	if runtime.GOOS == "windows" {
 
- 		return p.cmd.Process.Kill()
 
- 	} else {
 
- 		return p.cmd.Process.Signal(syscall.SIGTERM)
 
- 	}
 
- }
 
- // writeCrashReport writes a crash report to the binary folder with a timestamped filename.
 
- func writeCrashReport(m []byte) error {
 
- 	crashReportPath := config.GetBinFolderPath() + "/core_crash_" + time.Now().Format("20060102_150405") + ".log"
 
- 	return os.WriteFile(crashReportPath, m, os.ModePerm)
 
- }
 
 
  |