| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618 | package serviceimport (	"archive/zip"	"bytes"	"encoding/json"	"fmt"	"io"	"io/fs"	"mime/multipart"	"net/http"	"os"	"os/exec"	"runtime"	"strconv"	"strings"	"time"	"x-ui/config"	"x-ui/database"	"x-ui/logger"	"x-ui/util/common"	"x-ui/util/sys"	"x-ui/xray"	"github.com/shirou/gopsutil/v4/cpu"	"github.com/shirou/gopsutil/v4/disk"	"github.com/shirou/gopsutil/v4/host"	"github.com/shirou/gopsutil/v4/load"	"github.com/shirou/gopsutil/v4/mem"	"github.com/shirou/gopsutil/v4/net")type ProcessState stringconst (	Running ProcessState = "running"	Stop    ProcessState = "stop"	Error   ProcessState = "error")type Status struct {	T           time.Time `json:"-"`	Cpu         float64   `json:"cpu"`	CpuCores    int       `json:"cpuCores"`	LogicalPro  int       `json:"logicalPro"`	CpuSpeedMhz float64   `json:"cpuSpeedMhz"`	Mem         struct {		Current uint64 `json:"current"`		Total   uint64 `json:"total"`	} `json:"mem"`	Swap struct {		Current uint64 `json:"current"`		Total   uint64 `json:"total"`	} `json:"swap"`	Disk struct {		Current uint64 `json:"current"`		Total   uint64 `json:"total"`	} `json:"disk"`	Xray struct {		State    ProcessState `json:"state"`		ErrorMsg string       `json:"errorMsg"`		Version  string       `json:"version"`	} `json:"xray"`	Uptime   uint64    `json:"uptime"`	Loads    []float64 `json:"loads"`	TcpCount int       `json:"tcpCount"`	UdpCount int       `json:"udpCount"`	NetIO    struct {		Up   uint64 `json:"up"`		Down uint64 `json:"down"`	} `json:"netIO"`	NetTraffic struct {		Sent uint64 `json:"sent"`		Recv uint64 `json:"recv"`	} `json:"netTraffic"`	PublicIP struct {		IPv4 string `json:"ipv4"`		IPv6 string `json:"ipv6"`	} `json:"publicIP"`	AppStats struct {		Threads uint32 `json:"threads"`		Mem     uint64 `json:"mem"`		Uptime  uint64 `json:"uptime"`	} `json:"appStats"`}type Release struct {	TagName string `json:"tag_name"`}type ServerService struct {	xrayService    XrayService	inboundService InboundService	cachedIPv4     string	cachedIPv6     string}func getPublicIP(url string) string {	resp, err := http.Get(url)	if err != nil {		return "N/A"	}	defer resp.Body.Close()	ip, err := io.ReadAll(resp.Body)	if err != nil {		return "N/A"	}	ipString := string(ip)	if ipString == "" {		return "N/A"	}	return ipString}func (s *ServerService) GetStatus(lastStatus *Status) *Status {	now := time.Now()	status := &Status{		T: now,	}	// CPU stats	percents, err := cpu.Percent(0, false)	if err != nil {		logger.Warning("get cpu percent failed:", err)	} else {		status.Cpu = percents[0]	}	status.CpuCores, err = cpu.Counts(false)	if err != nil {		logger.Warning("get cpu cores count failed:", err)	}	status.LogicalPro = runtime.NumCPU()	cpuInfos, err := cpu.Info()	if err != nil {		logger.Warning("get cpu info failed:", err)	} else if len(cpuInfos) > 0 {		status.CpuSpeedMhz = cpuInfos[0].Mhz	} else {		logger.Warning("could not find cpu info")	}	// Uptime	upTime, err := host.Uptime()	if err != nil {		logger.Warning("get uptime failed:", err)	} else {		status.Uptime = upTime	}	// Memory stats	memInfo, err := mem.VirtualMemory()	if err != nil {		logger.Warning("get virtual memory failed:", err)	} else {		status.Mem.Current = memInfo.Used		status.Mem.Total = memInfo.Total	}	swapInfo, err := mem.SwapMemory()	if err != nil {		logger.Warning("get swap memory failed:", err)	} else {		status.Swap.Current = swapInfo.Used		status.Swap.Total = swapInfo.Total	}	// Disk stats	diskInfo, err := disk.Usage("/")	if err != nil {		logger.Warning("get disk usage failed:", err)	} else {		status.Disk.Current = diskInfo.Used		status.Disk.Total = diskInfo.Total	}	// Load averages	avgState, err := load.Avg()	if err != nil {		logger.Warning("get load avg failed:", err)	} else {		status.Loads = []float64{avgState.Load1, avgState.Load5, avgState.Load15}	}	// Network stats	ioStats, err := net.IOCounters(false)	if err != nil {		logger.Warning("get io counters failed:", err)	} else if len(ioStats) > 0 {		ioStat := ioStats[0]		status.NetTraffic.Sent = ioStat.BytesSent		status.NetTraffic.Recv = ioStat.BytesRecv		if lastStatus != nil {			duration := now.Sub(lastStatus.T)			seconds := float64(duration) / float64(time.Second)			up := uint64(float64(status.NetTraffic.Sent-lastStatus.NetTraffic.Sent) / seconds)			down := uint64(float64(status.NetTraffic.Recv-lastStatus.NetTraffic.Recv) / seconds)			status.NetIO.Up = up			status.NetIO.Down = down		}	} else {		logger.Warning("can not find io counters")	}	// TCP/UDP connections	status.TcpCount, err = sys.GetTCPCount()	if err != nil {		logger.Warning("get tcp connections failed:", err)	}	status.UdpCount, err = sys.GetUDPCount()	if err != nil {		logger.Warning("get udp connections failed:", err)	}	// IP fetching with caching	if s.cachedIPv4 == "" || s.cachedIPv6 == "" {		s.cachedIPv4 = getPublicIP("https://api.ipify.org")		s.cachedIPv6 = getPublicIP("https://api6.ipify.org")	}	status.PublicIP.IPv4 = s.cachedIPv4	status.PublicIP.IPv6 = s.cachedIPv6	// Xray status	if s.xrayService.IsXrayRunning() {		status.Xray.State = Running		status.Xray.ErrorMsg = ""	} else {		err := s.xrayService.GetXrayErr()		if err != nil {			status.Xray.State = Error		} else {			status.Xray.State = Stop		}		status.Xray.ErrorMsg = s.xrayService.GetXrayResult()	}	status.Xray.Version = s.xrayService.GetXrayVersion()	// Application stats	var rtm runtime.MemStats	runtime.ReadMemStats(&rtm)	status.AppStats.Mem = rtm.Sys	status.AppStats.Threads = uint32(runtime.NumGoroutine())	if p != nil && p.IsRunning() {		status.AppStats.Uptime = p.GetUptime()	} else {		status.AppStats.Uptime = 0	}	return status}func (s *ServerService) GetXrayVersions() ([]string, error) {	const (		XrayURL    = "https://api.github.com/repos/XTLS/Xray-core/releases"		bufferSize = 8192	)	resp, err := http.Get(XrayURL)	if err != nil {		return nil, err	}	defer resp.Body.Close()	buffer := bytes.NewBuffer(make([]byte, bufferSize))	buffer.Reset()	if _, err := buffer.ReadFrom(resp.Body); err != nil {		return nil, err	}	var releases []Release	if err := json.Unmarshal(buffer.Bytes(), &releases); err != nil {		return nil, err	}	var versions []string	for _, release := range releases {		tagVersion := strings.TrimPrefix(release.TagName, "v")		tagParts := strings.Split(tagVersion, ".")		if len(tagParts) != 3 {			continue		}		major, err1 := strconv.Atoi(tagParts[0])		minor, err2 := strconv.Atoi(tagParts[1])		patch, err3 := strconv.Atoi(tagParts[2])		if err1 != nil || err2 != nil || err3 != nil {			continue		}		if major > 25 || (major == 25 && minor > 3) || (major == 25 && minor == 3 && patch >= 3) {			versions = append(versions, release.TagName)		}	}	return versions, nil}func (s *ServerService) StopXrayService() (string error) {	err := s.xrayService.StopXray()	if err != nil {		logger.Error("stop xray failed:", err)		return err	}	return nil}func (s *ServerService) RestartXrayService() (string error) {	s.xrayService.StopXray()	defer func() {		err := s.xrayService.RestartXray(true)		if err != nil {			logger.Error("start xray failed:", err)		}	}()	return nil}func (s *ServerService) downloadXRay(version string) (string, error) {	osName := runtime.GOOS	arch := runtime.GOARCH	switch osName {	case "darwin":		osName = "macos"	}	switch arch {	case "amd64":		arch = "64"	case "arm64":		arch = "arm64-v8a"	case "armv7":		arch = "arm32-v7a"	case "armv6":		arch = "arm32-v6"	case "armv5":		arch = "arm32-v5"	case "386":		arch = "32"	case "s390x":		arch = "s390x"	}	fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)	url := fmt.Sprintf("https://github.com/XTLS/Xray-core/releases/download/%s/%s", version, fileName)	resp, err := http.Get(url)	if err != nil {		return "", err	}	defer resp.Body.Close()	os.Remove(fileName)	file, err := os.Create(fileName)	if err != nil {		return "", err	}	defer file.Close()	_, err = io.Copy(file, resp.Body)	if err != nil {		return "", err	}	return fileName, nil}func (s *ServerService) UpdateXray(version string) error {	zipFileName, err := s.downloadXRay(version)	if err != nil {		return err	}	zipFile, err := os.Open(zipFileName)	if err != nil {		return err	}	defer func() {		zipFile.Close()		os.Remove(zipFileName)	}()	stat, err := zipFile.Stat()	if err != nil {		return err	}	reader, err := zip.NewReader(zipFile, stat.Size())	if err != nil {		return err	}	s.xrayService.StopXray()	defer func() {		err := s.xrayService.RestartXray(true)		if err != nil {			logger.Error("start xray failed:", err)		}	}()	copyZipFile := func(zipName string, fileName string) error {		zipFile, err := reader.Open(zipName)		if err != nil {			return err		}		os.Remove(fileName)		file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, fs.ModePerm)		if err != nil {			return err		}		defer file.Close()		_, err = io.Copy(file, zipFile)		return err	}	err = copyZipFile("xray", xray.GetBinaryPath())	if err != nil {		return err	}	return nil}func (s *ServerService) GetLogs(count string, level string, syslog string) []string {	c, _ := strconv.Atoi(count)	var lines []string	if syslog == "true" {		cmdArgs := []string{"journalctl", "-u", "x-ui", "--no-pager", "-n", count, "-p", level}		// Run the command		cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)		var out bytes.Buffer		cmd.Stdout = &out		err := cmd.Run()		if err != nil {			return []string{"Failed to run journalctl command!"}		}		lines = strings.Split(out.String(), "\n")	} else {		lines = logger.GetLogs(c, level)	}	return lines}func (s *ServerService) GetConfigJson() (any, error) {	config, err := s.xrayService.GetXrayConfig()	if err != nil {		return nil, err	}	contents, err := json.MarshalIndent(config, "", "  ")	if err != nil {		return nil, err	}	var jsonData any	err = json.Unmarshal(contents, &jsonData)	if err != nil {		return nil, err	}	return jsonData, nil}func (s *ServerService) GetDb() ([]byte, error) {	// Update by manually trigger a checkpoint operation	err := database.Checkpoint()	if err != nil {		return nil, err	}	// Open the file for reading	file, err := os.Open(config.GetDBPath())	if err != nil {		return nil, err	}	defer file.Close()	// Read the file contents	fileContents, err := io.ReadAll(file)	if err != nil {		return nil, err	}	return fileContents, nil}func (s *ServerService) ImportDB(file multipart.File) error {	// Check if the file is a SQLite database	isValidDb, err := database.IsSQLiteDB(file)	if err != nil {		return common.NewErrorf("Error checking db file format: %v", err)	}	if !isValidDb {		return common.NewError("Invalid db file format")	}	// Reset the file reader to the beginning	_, err = file.Seek(0, 0)	if err != nil {		return common.NewErrorf("Error resetting file reader: %v", err)	}	// Save the file as temporary file	tempPath := fmt.Sprintf("%s.temp", config.GetDBPath())	// Remove the existing fallback file (if any) before creating one	_, err = os.Stat(tempPath)	if err == nil {		errRemove := os.Remove(tempPath)		if errRemove != nil {			return common.NewErrorf("Error removing existing temporary db file: %v", errRemove)		}	}	// Create the temporary file	tempFile, err := os.Create(tempPath)	if err != nil {		return common.NewErrorf("Error creating temporary db file: %v", err)	}	defer tempFile.Close()	// Remove temp file before returning	defer os.Remove(tempPath)	// Save uploaded file to temporary file	_, err = io.Copy(tempFile, file)	if err != nil {		return common.NewErrorf("Error saving db: %v", err)	}	// Check if we can init db or not	err = database.InitDB(tempPath)	if err != nil {		return common.NewErrorf("Error checking db: %v", err)	}	// Stop Xray	s.StopXrayService()	// Backup the current database for fallback	fallbackPath := fmt.Sprintf("%s.backup", config.GetDBPath())	// Remove the existing fallback file (if any)	_, err = os.Stat(fallbackPath)	if err == nil {		errRemove := os.Remove(fallbackPath)		if errRemove != nil {			return common.NewErrorf("Error removing existing fallback db file: %v", errRemove)		}	}	// Move the current database to the fallback location	err = os.Rename(config.GetDBPath(), fallbackPath)	if err != nil {		return common.NewErrorf("Error backing up temporary db file: %v", err)	}	// Remove the temporary file before returning	defer os.Remove(fallbackPath)	// Move temp to DB path	err = os.Rename(tempPath, config.GetDBPath())	if err != nil {		errRename := os.Rename(fallbackPath, config.GetDBPath())		if errRename != nil {			return common.NewErrorf("Error moving db file and restoring fallback: %v", errRename)		}		return common.NewErrorf("Error moving db file: %v", err)	}	// Migrate DB	err = database.InitDB(config.GetDBPath())	if err != nil {		errRename := os.Rename(fallbackPath, config.GetDBPath())		if errRename != nil {			return common.NewErrorf("Error migrating db and restoring fallback: %v", errRename)		}		return common.NewErrorf("Error migrating db: %v", err)	}	s.inboundService.MigrateDB()	// Start Xray	err = s.RestartXrayService()	if err != nil {		return common.NewErrorf("Imported DB but Failed to start Xray: %v", err)	}	return nil}func (s *ServerService) GetNewX25519Cert() (any, error) {	// Run the command	cmd := exec.Command(xray.GetBinaryPath(), "x25519")	var out bytes.Buffer	cmd.Stdout = &out	err := cmd.Run()	if err != nil {		return nil, err	}	lines := strings.Split(out.String(), "\n")	privateKeyLine := strings.Split(lines[0], ":")	publicKeyLine := strings.Split(lines[1], ":")	privateKey := strings.TrimSpace(privateKeyLine[1])	publicKey := strings.TrimSpace(publicKeyLine[1])	keyPair := map[string]any{		"privateKey": privateKey,		"publicKey":  publicKey,	}	return keyPair, nil}
 |