package service import ( "archive/zip" "bytes" "encoding/json" "fmt" "io" "io/fs" "net/http" "os" "runtime" "time" "x-ui/logger" "x-ui/util/sys" "x-ui/xray" "github.com/shirou/gopsutil/v3/cpu" "github.com/shirou/gopsutil/v3/disk" "github.com/shirou/gopsutil/v3/host" "github.com/shirou/gopsutil/v3/load" "github.com/shirou/gopsutil/v3/mem" "github.com/shirou/gopsutil/v3/net" ) type ProcessState string const ( Running ProcessState = "running" Stop ProcessState = "stop" Error ProcessState = "error" ) type Status struct { T time.Time `json:"-"` Cpu float64 `json:"cpu"` 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"` } type Release struct { TagName string `json:"tag_name"` } type ServerService struct { xrayService XrayService } func (s *ServerService) GetStatus(lastStatus *Status) *Status { now := time.Now() status := &Status{ T: now, } percents, err := cpu.Percent(0, false) if err != nil { logger.Warning("get cpu percent failed:", err) } else { status.Cpu = percents[0] } upTime, err := host.Uptime() if err != nil { logger.Warning("get uptime failed:", err) } else { status.Uptime = upTime } 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 } distInfo, err := disk.Usage("/") if err != nil { logger.Warning("get dist usage failed:", err) } else { status.Disk.Current = distInfo.Used status.Disk.Total = distInfo.Total } avgState, err := load.Avg() if err != nil { logger.Warning("get load avg failed:", err) } else { status.Loads = []float64{avgState.Load1, avgState.Load5, avgState.Load15} } 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") } 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) } 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() return status } func (s *ServerService) GetXrayVersions() ([]string, error) { url := "https://api.github.com/repos/mhsanaei/Xray-core/releases" resp, err := http.Get(url) if err != nil { return nil, err } defer resp.Body.Close() buffer := bytes.NewBuffer(make([]byte, 8192)) buffer.Reset() _, err = buffer.ReadFrom(resp.Body) if err != nil { return nil, err } releases := make([]Release, 0) err = json.Unmarshal(buffer.Bytes(), &releases) if err != nil { return nil, err } versions := make([]string, 0, len(releases)) for _, release := range releases { versions = append(versions, release.TagName) } return versions, 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" } fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch) url := fmt.Sprintf("https://github.com/mhsanaei/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 } err = copyZipFile("geosite.dat", xray.GetGeositePath()) if err != nil { return err } err = copyZipFile("geoip.dat", xray.GetGeoipPath()) if err != nil { return err } return nil }