server.go 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340
  1. package service
  2. import (
  3. "archive/zip"
  4. "bufio"
  5. "bytes"
  6. "encoding/json"
  7. "fmt"
  8. "io"
  9. "io/fs"
  10. "mime/multipart"
  11. "net/http"
  12. "os"
  13. "os/exec"
  14. "path/filepath"
  15. "regexp"
  16. "runtime"
  17. "strconv"
  18. "strings"
  19. "sync"
  20. "time"
  21. "github.com/mhsanaei/3x-ui/v2/config"
  22. "github.com/mhsanaei/3x-ui/v2/database"
  23. "github.com/mhsanaei/3x-ui/v2/logger"
  24. "github.com/mhsanaei/3x-ui/v2/util/common"
  25. "github.com/mhsanaei/3x-ui/v2/util/sys"
  26. "github.com/mhsanaei/3x-ui/v2/xray"
  27. "github.com/google/uuid"
  28. "github.com/shirou/gopsutil/v4/cpu"
  29. "github.com/shirou/gopsutil/v4/disk"
  30. "github.com/shirou/gopsutil/v4/host"
  31. "github.com/shirou/gopsutil/v4/load"
  32. "github.com/shirou/gopsutil/v4/mem"
  33. "github.com/shirou/gopsutil/v4/net"
  34. )
  35. // ProcessState represents the current state of a system process.
  36. type ProcessState string
  37. // Process state constants
  38. const (
  39. Running ProcessState = "running" // Process is running normally
  40. Stop ProcessState = "stop" // Process is stopped
  41. Error ProcessState = "error" // Process is in error state
  42. )
  43. // Status represents comprehensive system and application status information.
  44. // It includes CPU, memory, disk, network statistics, and Xray process status.
  45. type Status struct {
  46. T time.Time `json:"-"`
  47. Cpu float64 `json:"cpu"`
  48. CpuCores int `json:"cpuCores"`
  49. LogicalPro int `json:"logicalPro"`
  50. CpuSpeedMhz float64 `json:"cpuSpeedMhz"`
  51. Mem struct {
  52. Current uint64 `json:"current"`
  53. Total uint64 `json:"total"`
  54. } `json:"mem"`
  55. Swap struct {
  56. Current uint64 `json:"current"`
  57. Total uint64 `json:"total"`
  58. } `json:"swap"`
  59. Disk struct {
  60. Current uint64 `json:"current"`
  61. Total uint64 `json:"total"`
  62. } `json:"disk"`
  63. Xray struct {
  64. State ProcessState `json:"state"`
  65. ErrorMsg string `json:"errorMsg"`
  66. Version string `json:"version"`
  67. } `json:"xray"`
  68. Uptime uint64 `json:"uptime"`
  69. Loads []float64 `json:"loads"`
  70. TcpCount int `json:"tcpCount"`
  71. UdpCount int `json:"udpCount"`
  72. NetIO struct {
  73. Up uint64 `json:"up"`
  74. Down uint64 `json:"down"`
  75. } `json:"netIO"`
  76. NetTraffic struct {
  77. Sent uint64 `json:"sent"`
  78. Recv uint64 `json:"recv"`
  79. } `json:"netTraffic"`
  80. PublicIP struct {
  81. IPv4 string `json:"ipv4"`
  82. IPv6 string `json:"ipv6"`
  83. } `json:"publicIP"`
  84. AppStats struct {
  85. Threads uint32 `json:"threads"`
  86. Mem uint64 `json:"mem"`
  87. Uptime uint64 `json:"uptime"`
  88. } `json:"appStats"`
  89. }
  90. // Release represents information about a software release from GitHub.
  91. type Release struct {
  92. TagName string `json:"tag_name"` // The tag name of the release
  93. }
  94. // ServerService provides business logic for server monitoring and management.
  95. // It handles system status collection, IP detection, and application statistics.
  96. type ServerService struct {
  97. xrayService XrayService
  98. inboundService InboundService
  99. cachedIPv4 string
  100. cachedIPv6 string
  101. noIPv6 bool
  102. mu sync.Mutex
  103. lastCPUTimes cpu.TimesStat
  104. hasLastCPUSample bool
  105. hasNativeCPUSample bool
  106. emaCPU float64
  107. cachedCpuSpeedMhz float64
  108. lastCpuInfoAttempt time.Time
  109. }
  110. // AggregateCpuHistory returns up to maxPoints averaged buckets of size bucketSeconds.
  111. // Kept for back-compat with the original /panel/api/server/cpuHistory/:bucket route;
  112. // the response key is "cpu" (not "v") so legacy consumers parse unchanged.
  113. func (s *ServerService) AggregateCpuHistory(bucketSeconds int, maxPoints int) []map[string]any {
  114. out := systemMetrics.aggregate("cpu", bucketSeconds, maxPoints)
  115. for _, p := range out {
  116. p["cpu"] = p["v"]
  117. delete(p, "v")
  118. }
  119. return out
  120. }
  121. // AggregateSystemMetric returns up to maxPoints averaged buckets for any
  122. // known system metric (see SystemMetricKeys). Output points have keys
  123. // {"t": unixSec, "v": value}; the caller decides how to format the value.
  124. func (s *ServerService) AggregateSystemMetric(metric string, bucketSeconds int, maxPoints int) []map[string]any {
  125. return systemMetrics.aggregate(metric, bucketSeconds, maxPoints)
  126. }
  127. type LogEntry struct {
  128. DateTime time.Time
  129. FromAddress string
  130. ToAddress string
  131. Inbound string
  132. Outbound string
  133. Email string
  134. Event int
  135. }
  136. func getPublicIP(url string) string {
  137. client := &http.Client{
  138. Timeout: 3 * time.Second,
  139. }
  140. resp, err := client.Get(url)
  141. if err != nil {
  142. return "N/A"
  143. }
  144. defer resp.Body.Close()
  145. // Don't retry if access is blocked or region-restricted
  146. if resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusUnavailableForLegalReasons {
  147. return "N/A"
  148. }
  149. if resp.StatusCode != http.StatusOK {
  150. return "N/A"
  151. }
  152. ip, err := io.ReadAll(resp.Body)
  153. if err != nil {
  154. return "N/A"
  155. }
  156. ipString := strings.TrimSpace(string(ip))
  157. if ipString == "" {
  158. return "N/A"
  159. }
  160. return ipString
  161. }
  162. func (s *ServerService) GetStatus(lastStatus *Status) *Status {
  163. now := time.Now()
  164. status := &Status{
  165. T: now,
  166. }
  167. // CPU stats
  168. util, err := s.sampleCPUUtilization()
  169. if err != nil {
  170. logger.Warning("get cpu percent failed:", err)
  171. } else {
  172. status.Cpu = util
  173. }
  174. status.CpuCores, err = cpu.Counts(false)
  175. if err != nil {
  176. logger.Warning("get cpu cores count failed:", err)
  177. }
  178. status.LogicalPro = runtime.NumCPU()
  179. if status.CpuSpeedMhz = s.cachedCpuSpeedMhz; s.cachedCpuSpeedMhz == 0 && time.Since(s.lastCpuInfoAttempt) > 5*time.Minute {
  180. s.lastCpuInfoAttempt = time.Now()
  181. done := make(chan struct{})
  182. go func() {
  183. defer close(done)
  184. cpuInfos, err := cpu.Info()
  185. if err != nil {
  186. logger.Warning("get cpu info failed:", err)
  187. return
  188. }
  189. if len(cpuInfos) > 0 {
  190. s.cachedCpuSpeedMhz = cpuInfos[0].Mhz
  191. status.CpuSpeedMhz = s.cachedCpuSpeedMhz
  192. } else {
  193. logger.Warning("could not find cpu info")
  194. }
  195. }()
  196. select {
  197. case <-done:
  198. case <-time.After(1500 * time.Millisecond):
  199. logger.Warning("cpu info query timed out; will retry later")
  200. }
  201. } else if s.cachedCpuSpeedMhz != 0 {
  202. status.CpuSpeedMhz = s.cachedCpuSpeedMhz
  203. }
  204. // Uptime
  205. upTime, err := host.Uptime()
  206. if err != nil {
  207. logger.Warning("get uptime failed:", err)
  208. } else {
  209. status.Uptime = upTime
  210. }
  211. // Memory stats
  212. memInfo, err := mem.VirtualMemory()
  213. if err != nil {
  214. logger.Warning("get virtual memory failed:", err)
  215. } else {
  216. status.Mem.Current = memInfo.Used
  217. status.Mem.Total = memInfo.Total
  218. }
  219. swapInfo, err := mem.SwapMemory()
  220. if err != nil {
  221. logger.Warning("get swap memory failed:", err)
  222. } else {
  223. status.Swap.Current = swapInfo.Used
  224. status.Swap.Total = swapInfo.Total
  225. }
  226. // Disk stats
  227. diskInfo, err := disk.Usage("/")
  228. if err != nil {
  229. logger.Warning("get disk usage failed:", err)
  230. } else {
  231. status.Disk.Current = diskInfo.Used
  232. status.Disk.Total = diskInfo.Total
  233. }
  234. // Load averages
  235. avgState, err := load.Avg()
  236. if err != nil {
  237. logger.Warning("get load avg failed:", err)
  238. } else {
  239. status.Loads = []float64{avgState.Load1, avgState.Load5, avgState.Load15}
  240. }
  241. // Network stats
  242. ioStats, err := net.IOCounters(true)
  243. if err != nil {
  244. logger.Warning("get io counters failed:", err)
  245. } else {
  246. var totalSent, totalRecv uint64
  247. for _, iface := range ioStats {
  248. name := strings.ToLower(iface.Name)
  249. if isVirtualInterface(name) {
  250. continue
  251. }
  252. totalSent += iface.BytesSent
  253. totalRecv += iface.BytesRecv
  254. }
  255. status.NetTraffic.Sent = totalSent
  256. status.NetTraffic.Recv = totalRecv
  257. if lastStatus != nil {
  258. duration := now.Sub(lastStatus.T)
  259. seconds := float64(duration) / float64(time.Second)
  260. up := uint64(float64(status.NetTraffic.Sent-lastStatus.NetTraffic.Sent) / seconds)
  261. down := uint64(float64(status.NetTraffic.Recv-lastStatus.NetTraffic.Recv) / seconds)
  262. status.NetIO.Up = up
  263. status.NetIO.Down = down
  264. }
  265. }
  266. // TCP/UDP connections
  267. status.TcpCount, err = sys.GetTCPCount()
  268. if err != nil {
  269. logger.Warning("get tcp connections failed:", err)
  270. }
  271. status.UdpCount, err = sys.GetUDPCount()
  272. if err != nil {
  273. logger.Warning("get udp connections failed:", err)
  274. }
  275. // IP fetching with caching
  276. showIp4ServiceLists := []string{
  277. "https://api4.ipify.org",
  278. "https://ipv4.icanhazip.com",
  279. "https://v4.api.ipinfo.io/ip",
  280. "https://ipv4.myexternalip.com/raw",
  281. "https://4.ident.me",
  282. "https://check-host.net/ip",
  283. }
  284. showIp6ServiceLists := []string{
  285. "https://api6.ipify.org",
  286. "https://ipv6.icanhazip.com",
  287. "https://v6.api.ipinfo.io/ip",
  288. "https://ipv6.myexternalip.com/raw",
  289. "https://6.ident.me",
  290. }
  291. if s.cachedIPv4 == "" {
  292. for _, ip4Service := range showIp4ServiceLists {
  293. s.cachedIPv4 = getPublicIP(ip4Service)
  294. if s.cachedIPv4 != "N/A" {
  295. break
  296. }
  297. }
  298. }
  299. if s.cachedIPv6 == "" && !s.noIPv6 {
  300. for _, ip6Service := range showIp6ServiceLists {
  301. s.cachedIPv6 = getPublicIP(ip6Service)
  302. if s.cachedIPv6 != "N/A" {
  303. break
  304. }
  305. }
  306. }
  307. if s.cachedIPv6 == "N/A" {
  308. s.noIPv6 = true
  309. }
  310. status.PublicIP.IPv4 = s.cachedIPv4
  311. status.PublicIP.IPv6 = s.cachedIPv6
  312. // Xray status
  313. if s.xrayService.IsXrayRunning() {
  314. status.Xray.State = Running
  315. status.Xray.ErrorMsg = ""
  316. } else {
  317. err := s.xrayService.GetXrayErr()
  318. if err != nil {
  319. status.Xray.State = Error
  320. } else {
  321. status.Xray.State = Stop
  322. }
  323. status.Xray.ErrorMsg = s.xrayService.GetXrayResult()
  324. }
  325. status.Xray.Version = s.xrayService.GetXrayVersion()
  326. // Application stats
  327. var rtm runtime.MemStats
  328. runtime.ReadMemStats(&rtm)
  329. status.AppStats.Mem = rtm.Sys
  330. status.AppStats.Threads = uint32(runtime.NumGoroutine())
  331. if p != nil && p.IsRunning() {
  332. status.AppStats.Uptime = p.GetUptime()
  333. } else {
  334. status.AppStats.Uptime = 0
  335. }
  336. return status
  337. }
  338. // AppendCpuSample is preserved for callers that only have the CPU number.
  339. // New callers should prefer AppendStatusSample which writes the full set.
  340. func (s *ServerService) AppendCpuSample(t time.Time, v float64) {
  341. systemMetrics.append("cpu", t, v)
  342. }
  343. // AppendStatusSample writes one tick of every metric we keep — CPU, memory
  344. // percent, network throughput (bytes/s), online client count, and the three
  345. // load averages. Called by ServerController.refreshStatus on the same @2s
  346. // cadence as AppendCpuSample, so all series stay aligned.
  347. func (s *ServerService) AppendStatusSample(t time.Time, status *Status) {
  348. if status == nil {
  349. return
  350. }
  351. systemMetrics.append("cpu", t, status.Cpu)
  352. if status.Mem.Total > 0 {
  353. systemMetrics.append("mem", t, float64(status.Mem.Current)*100.0/float64(status.Mem.Total))
  354. }
  355. systemMetrics.append("netUp", t, float64(status.NetIO.Up))
  356. systemMetrics.append("netDown", t, float64(status.NetIO.Down))
  357. online := 0
  358. if p != nil && p.IsRunning() {
  359. online = len(p.GetOnlineClients())
  360. }
  361. systemMetrics.append("online", t, float64(online))
  362. if len(status.Loads) >= 3 {
  363. systemMetrics.append("load1", t, status.Loads[0])
  364. systemMetrics.append("load5", t, status.Loads[1])
  365. systemMetrics.append("load15", t, status.Loads[2])
  366. }
  367. }
  368. func (s *ServerService) sampleCPUUtilization() (float64, error) {
  369. // Try native platform-specific CPU implementation first (Windows, Linux, macOS)
  370. if pct, err := sys.CPUPercentRaw(); err == nil {
  371. s.mu.Lock()
  372. // First call to native method returns 0 (initializes baseline)
  373. if !s.hasNativeCPUSample {
  374. s.hasNativeCPUSample = true
  375. s.mu.Unlock()
  376. return 0, nil
  377. }
  378. // Smooth with EMA
  379. const alpha = 0.3
  380. if s.emaCPU == 0 {
  381. s.emaCPU = pct
  382. } else {
  383. s.emaCPU = alpha*pct + (1-alpha)*s.emaCPU
  384. }
  385. val := s.emaCPU
  386. s.mu.Unlock()
  387. return val, nil
  388. }
  389. // If native call fails, fall back to gopsutil times
  390. // Read aggregate CPU times (all CPUs combined)
  391. times, err := cpu.Times(false)
  392. if err != nil {
  393. return 0, err
  394. }
  395. if len(times) == 0 {
  396. return 0, fmt.Errorf("no cpu times available")
  397. }
  398. cur := times[0]
  399. s.mu.Lock()
  400. defer s.mu.Unlock()
  401. // If this is the first sample, initialize and return current EMA (0 by default)
  402. if !s.hasLastCPUSample {
  403. s.lastCPUTimes = cur
  404. s.hasLastCPUSample = true
  405. return s.emaCPU, nil
  406. }
  407. // Compute busy and total deltas
  408. // Note: Guest and GuestNice times are already included in User and Nice respectively,
  409. // so we exclude them to avoid double-counting (Linux kernel accounting)
  410. idleDelta := cur.Idle - s.lastCPUTimes.Idle
  411. busyDelta := (cur.User - s.lastCPUTimes.User) +
  412. (cur.System - s.lastCPUTimes.System) +
  413. (cur.Nice - s.lastCPUTimes.Nice) +
  414. (cur.Iowait - s.lastCPUTimes.Iowait) +
  415. (cur.Irq - s.lastCPUTimes.Irq) +
  416. (cur.Softirq - s.lastCPUTimes.Softirq) +
  417. (cur.Steal - s.lastCPUTimes.Steal)
  418. totalDelta := busyDelta + idleDelta
  419. // Update last sample for next time
  420. s.lastCPUTimes = cur
  421. // Guard against division by zero or negative deltas (e.g., counter resets)
  422. if totalDelta <= 0 {
  423. return s.emaCPU, nil
  424. }
  425. raw := 100.0 * (busyDelta / totalDelta)
  426. if raw < 0 {
  427. raw = 0
  428. }
  429. if raw > 100 {
  430. raw = 100
  431. }
  432. // Exponential moving average to smooth spikes
  433. const alpha = 0.3 // smoothing factor (0<alpha<=1). Higher = more responsive, lower = smoother
  434. if s.emaCPU == 0 {
  435. // Initialize EMA with the first real reading to avoid long warm-up from zero
  436. s.emaCPU = raw
  437. } else {
  438. s.emaCPU = alpha*raw + (1-alpha)*s.emaCPU
  439. }
  440. return s.emaCPU, nil
  441. }
  442. var xrayVersionsClient = &http.Client{Timeout: 10 * time.Second}
  443. func (s *ServerService) GetXrayVersions() ([]string, error) {
  444. const (
  445. XrayURL = "https://api.github.com/repos/XTLS/Xray-core/releases"
  446. bufferSize = 8192
  447. )
  448. resp, err := xrayVersionsClient.Get(XrayURL)
  449. if err != nil {
  450. return nil, err
  451. }
  452. defer resp.Body.Close()
  453. // Check HTTP status code - GitHub API returns object instead of array on error
  454. if resp.StatusCode != http.StatusOK {
  455. bodyBytes, _ := io.ReadAll(resp.Body)
  456. var errorResponse struct {
  457. Message string `json:"message"`
  458. }
  459. if json.Unmarshal(bodyBytes, &errorResponse) == nil && errorResponse.Message != "" {
  460. return nil, fmt.Errorf("GitHub API error: %s", errorResponse.Message)
  461. }
  462. return nil, fmt.Errorf("GitHub API returned status %d: %s", resp.StatusCode, resp.Status)
  463. }
  464. buffer := bytes.NewBuffer(make([]byte, bufferSize))
  465. buffer.Reset()
  466. if _, err := buffer.ReadFrom(resp.Body); err != nil {
  467. return nil, err
  468. }
  469. var releases []Release
  470. if err := json.Unmarshal(buffer.Bytes(), &releases); err != nil {
  471. return nil, err
  472. }
  473. var versions []string
  474. for _, release := range releases {
  475. tagVersion := strings.TrimPrefix(release.TagName, "v")
  476. if tagVersion == "26.5.3" {
  477. continue
  478. }
  479. tagParts := strings.Split(tagVersion, ".")
  480. if len(tagParts) != 3 {
  481. continue
  482. }
  483. major, err1 := strconv.Atoi(tagParts[0])
  484. minor, err2 := strconv.Atoi(tagParts[1])
  485. patch, err3 := strconv.Atoi(tagParts[2])
  486. if err1 != nil || err2 != nil || err3 != nil {
  487. continue
  488. }
  489. if major > 26 || (major == 26 && minor > 4) || (major == 26 && minor == 4 && patch >= 25) {
  490. versions = append(versions, release.TagName)
  491. }
  492. }
  493. return versions, nil
  494. }
  495. func (s *ServerService) StopXrayService() error {
  496. err := s.xrayService.StopXray()
  497. if err != nil {
  498. logger.Error("stop xray failed:", err)
  499. return err
  500. }
  501. return nil
  502. }
  503. func (s *ServerService) RestartXrayService() error {
  504. err := s.xrayService.RestartXray(true)
  505. if err != nil {
  506. logger.Error("start xray failed:", err)
  507. return err
  508. }
  509. return nil
  510. }
  511. func (s *ServerService) downloadXRay(version string) (string, error) {
  512. osName := runtime.GOOS
  513. arch := runtime.GOARCH
  514. switch osName {
  515. case "darwin":
  516. osName = "macos"
  517. case "windows":
  518. osName = "windows"
  519. }
  520. switch arch {
  521. case "amd64":
  522. arch = "64"
  523. case "arm64":
  524. arch = "arm64-v8a"
  525. case "armv7":
  526. arch = "arm32-v7a"
  527. case "armv6":
  528. arch = "arm32-v6"
  529. case "armv5":
  530. arch = "arm32-v5"
  531. case "386":
  532. arch = "32"
  533. case "s390x":
  534. arch = "s390x"
  535. }
  536. fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)
  537. url := fmt.Sprintf("https://github.com/XTLS/Xray-core/releases/download/%s/%s", version, fileName)
  538. resp, err := http.Get(url)
  539. if err != nil {
  540. return "", err
  541. }
  542. defer resp.Body.Close()
  543. os.Remove(fileName)
  544. file, err := os.Create(fileName)
  545. if err != nil {
  546. return "", err
  547. }
  548. defer file.Close()
  549. _, err = io.Copy(file, resp.Body)
  550. if err != nil {
  551. return "", err
  552. }
  553. return fileName, nil
  554. }
  555. func (s *ServerService) UpdateXray(version string) error {
  556. // 1. Stop xray before doing anything
  557. if err := s.StopXrayService(); err != nil {
  558. logger.Warning("failed to stop xray before update:", err)
  559. }
  560. // 2. Download the zip
  561. zipFileName, err := s.downloadXRay(version)
  562. if err != nil {
  563. return err
  564. }
  565. defer os.Remove(zipFileName)
  566. zipFile, err := os.Open(zipFileName)
  567. if err != nil {
  568. return err
  569. }
  570. defer zipFile.Close()
  571. stat, err := zipFile.Stat()
  572. if err != nil {
  573. return err
  574. }
  575. reader, err := zip.NewReader(zipFile, stat.Size())
  576. if err != nil {
  577. return err
  578. }
  579. // 3. Helper to extract files
  580. copyZipFile := func(zipName string, fileName string) error {
  581. zipFile, err := reader.Open(zipName)
  582. if err != nil {
  583. return err
  584. }
  585. defer zipFile.Close()
  586. os.MkdirAll(filepath.Dir(fileName), 0755)
  587. os.Remove(fileName)
  588. file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, fs.ModePerm)
  589. if err != nil {
  590. return err
  591. }
  592. defer file.Close()
  593. _, err = io.Copy(file, zipFile)
  594. return err
  595. }
  596. // 4. Extract correct binary
  597. if runtime.GOOS == "windows" {
  598. targetBinary := filepath.Join("bin", "xray-windows-amd64.exe")
  599. err = copyZipFile("xray.exe", targetBinary)
  600. } else {
  601. err = copyZipFile("xray", xray.GetBinaryPath())
  602. }
  603. if err != nil {
  604. return err
  605. }
  606. // 5. Restart xray
  607. if err := s.xrayService.RestartXray(true); err != nil {
  608. logger.Error("start xray failed:", err)
  609. return err
  610. }
  611. return nil
  612. }
  613. func (s *ServerService) GetLogs(count string, level string, syslog string) []string {
  614. c, _ := strconv.Atoi(count)
  615. var lines []string
  616. if syslog == "true" {
  617. // Check if running on Windows - journalctl is not available
  618. if runtime.GOOS == "windows" {
  619. return []string{"Syslog is not supported on Windows. Please use application logs instead by unchecking the 'Syslog' option."}
  620. }
  621. // Validate and sanitize count parameter
  622. countInt, err := strconv.Atoi(count)
  623. if err != nil || countInt < 1 || countInt > 10000 {
  624. return []string{"Invalid count parameter - must be a number between 1 and 10000"}
  625. }
  626. // Validate level parameter - only allow valid syslog levels
  627. validLevels := map[string]bool{
  628. "0": true, "emerg": true,
  629. "1": true, "alert": true,
  630. "2": true, "crit": true,
  631. "3": true, "err": true,
  632. "4": true, "warning": true,
  633. "5": true, "notice": true,
  634. "6": true, "info": true,
  635. "7": true, "debug": true,
  636. }
  637. if !validLevels[level] {
  638. return []string{"Invalid level parameter - must be a valid syslog level"}
  639. }
  640. // Use hardcoded command with validated parameters
  641. cmd := exec.Command("journalctl", "-u", "x-ui", "--no-pager", "-n", strconv.Itoa(countInt), "-p", level)
  642. var out bytes.Buffer
  643. cmd.Stdout = &out
  644. err = cmd.Run()
  645. if err != nil {
  646. return []string{"Failed to run journalctl command! Make sure systemd is available and x-ui service is registered."}
  647. }
  648. lines = strings.Split(out.String(), "\n")
  649. } else {
  650. lines = logger.GetLogs(c, level)
  651. }
  652. return lines
  653. }
  654. func (s *ServerService) GetXrayLogs(
  655. count string,
  656. filter string,
  657. showDirect string,
  658. showBlocked string,
  659. showProxy string,
  660. freedoms []string,
  661. blackholes []string) []LogEntry {
  662. const (
  663. Direct = iota
  664. Blocked
  665. Proxied
  666. )
  667. countInt, _ := strconv.Atoi(count)
  668. var entries []LogEntry
  669. pathToAccessLog, err := xray.GetAccessLogPath()
  670. if err != nil {
  671. return nil
  672. }
  673. file, err := os.Open(pathToAccessLog)
  674. if err != nil {
  675. return nil
  676. }
  677. defer file.Close()
  678. scanner := bufio.NewScanner(file)
  679. for scanner.Scan() {
  680. line := strings.TrimSpace(scanner.Text())
  681. if line == "" || strings.Contains(line, "api -> api") {
  682. //skipping empty lines and api calls
  683. continue
  684. }
  685. if filter != "" && !strings.Contains(line, filter) {
  686. //applying filter if it's not empty
  687. continue
  688. }
  689. var entry LogEntry
  690. parts := strings.Fields(line)
  691. for i, part := range parts {
  692. if i == 0 {
  693. dateTime, err := time.ParseInLocation("2006/01/02 15:04:05.999999", parts[0]+" "+parts[1], time.Local)
  694. if err != nil {
  695. continue
  696. }
  697. entry.DateTime = dateTime.UTC()
  698. }
  699. if part == "from" {
  700. entry.FromAddress = strings.TrimLeft(parts[i+1], "/")
  701. } else if part == "accepted" {
  702. entry.ToAddress = strings.TrimLeft(parts[i+1], "/")
  703. } else if strings.HasPrefix(part, "[") {
  704. entry.Inbound = part[1:]
  705. } else if strings.HasSuffix(part, "]") {
  706. entry.Outbound = part[:len(part)-1]
  707. } else if part == "email:" {
  708. entry.Email = parts[i+1]
  709. }
  710. }
  711. if logEntryContains(line, freedoms) {
  712. if showDirect == "false" {
  713. continue
  714. }
  715. entry.Event = Direct
  716. } else if logEntryContains(line, blackholes) {
  717. if showBlocked == "false" {
  718. continue
  719. }
  720. entry.Event = Blocked
  721. } else {
  722. if showProxy == "false" {
  723. continue
  724. }
  725. entry.Event = Proxied
  726. }
  727. entries = append(entries, entry)
  728. }
  729. if err := scanner.Err(); err != nil {
  730. return nil
  731. }
  732. if len(entries) > countInt {
  733. entries = entries[len(entries)-countInt:]
  734. }
  735. return entries
  736. }
  737. // isVirtualInterface returns true for loopback and virtual/tunnel interfaces
  738. // that should be excluded from network traffic statistics.
  739. func isVirtualInterface(name string) bool {
  740. // Exact matches
  741. if name == "lo" || name == "lo0" {
  742. return true
  743. }
  744. // Prefix matches for virtual/tunnel interfaces
  745. virtualPrefixes := []string{
  746. "loopback",
  747. "docker",
  748. "br-",
  749. "veth",
  750. "virbr",
  751. "tun",
  752. "tap",
  753. "wg",
  754. "tailscale",
  755. "zt",
  756. }
  757. for _, prefix := range virtualPrefixes {
  758. if strings.HasPrefix(name, prefix) {
  759. return true
  760. }
  761. }
  762. return false
  763. }
  764. func logEntryContains(line string, suffixes []string) bool {
  765. for _, sfx := range suffixes {
  766. if strings.Contains(line, sfx+"]") {
  767. return true
  768. }
  769. }
  770. return false
  771. }
  772. func (s *ServerService) GetConfigJson() (any, error) {
  773. config, err := s.xrayService.GetXrayConfig()
  774. if err != nil {
  775. return nil, err
  776. }
  777. contents, err := json.MarshalIndent(config, "", " ")
  778. if err != nil {
  779. return nil, err
  780. }
  781. var jsonData any
  782. err = json.Unmarshal(contents, &jsonData)
  783. if err != nil {
  784. return nil, err
  785. }
  786. return jsonData, nil
  787. }
  788. func (s *ServerService) GetDb() ([]byte, error) {
  789. // Update by manually trigger a checkpoint operation
  790. err := database.Checkpoint()
  791. if err != nil {
  792. return nil, err
  793. }
  794. // Open the file for reading
  795. file, err := os.Open(config.GetDBPath())
  796. if err != nil {
  797. return nil, err
  798. }
  799. defer file.Close()
  800. // Read the file contents
  801. fileContents, err := io.ReadAll(file)
  802. if err != nil {
  803. return nil, err
  804. }
  805. return fileContents, nil
  806. }
  807. func (s *ServerService) ImportDB(file multipart.File) error {
  808. // Check if the file is a SQLite database
  809. isValidDb, err := database.IsSQLiteDB(file)
  810. if err != nil {
  811. return common.NewErrorf("Error checking db file format: %v", err)
  812. }
  813. if !isValidDb {
  814. return common.NewError("Invalid db file format")
  815. }
  816. // Reset the file reader to the beginning
  817. _, err = file.Seek(0, 0)
  818. if err != nil {
  819. return common.NewErrorf("Error resetting file reader: %v", err)
  820. }
  821. // Save the file as a temporary file
  822. tempPath := fmt.Sprintf("%s.temp", config.GetDBPath())
  823. // Remove the existing temporary file (if any)
  824. if _, err := os.Stat(tempPath); err == nil {
  825. if errRemove := os.Remove(tempPath); errRemove != nil {
  826. return common.NewErrorf("Error removing existing temporary db file: %v", errRemove)
  827. }
  828. }
  829. // Create the temporary file
  830. tempFile, err := os.Create(tempPath)
  831. if err != nil {
  832. return common.NewErrorf("Error creating temporary db file: %v", err)
  833. }
  834. // Robust deferred cleanup for the temporary file
  835. defer func() {
  836. if tempFile != nil {
  837. if cerr := tempFile.Close(); cerr != nil {
  838. logger.Warningf("Warning: failed to close temp file: %v", cerr)
  839. }
  840. }
  841. if _, err := os.Stat(tempPath); err == nil {
  842. if rerr := os.Remove(tempPath); rerr != nil {
  843. logger.Warningf("Warning: failed to remove temp file: %v", rerr)
  844. }
  845. }
  846. }()
  847. // Save uploaded file to temporary file
  848. if _, err = io.Copy(tempFile, file); err != nil {
  849. return common.NewErrorf("Error saving db: %v", err)
  850. }
  851. // Close temp file before opening via sqlite
  852. if err = tempFile.Close(); err != nil {
  853. return common.NewErrorf("Error closing temporary db file: %v", err)
  854. }
  855. tempFile = nil
  856. // Validate integrity (no migrations / side effects)
  857. if err = database.ValidateSQLiteDB(tempPath); err != nil {
  858. return common.NewErrorf("Invalid or corrupt db file: %v", err)
  859. }
  860. // Stop Xray (ignore error but log)
  861. if errStop := s.StopXrayService(); errStop != nil {
  862. logger.Warningf("Failed to stop Xray before DB import: %v", errStop)
  863. }
  864. // Close existing DB to release file locks (especially on Windows)
  865. if errClose := database.CloseDB(); errClose != nil {
  866. logger.Warningf("Failed to close existing DB before replacement: %v", errClose)
  867. }
  868. // Backup the current database for fallback
  869. fallbackPath := fmt.Sprintf("%s.backup", config.GetDBPath())
  870. // Remove the existing fallback file (if any)
  871. if _, err := os.Stat(fallbackPath); err == nil {
  872. if errRemove := os.Remove(fallbackPath); errRemove != nil {
  873. return common.NewErrorf("Error removing existing fallback db file: %v", errRemove)
  874. }
  875. }
  876. // Move the current database to the fallback location
  877. if err = os.Rename(config.GetDBPath(), fallbackPath); err != nil {
  878. return common.NewErrorf("Error backing up current db file: %v", err)
  879. }
  880. // Defer fallback cleanup ONLY if everything goes well
  881. defer func() {
  882. if _, err := os.Stat(fallbackPath); err == nil {
  883. if rerr := os.Remove(fallbackPath); rerr != nil {
  884. logger.Warningf("Warning: failed to remove fallback file: %v", rerr)
  885. }
  886. }
  887. }()
  888. // Move temp to DB path
  889. if err = os.Rename(tempPath, config.GetDBPath()); err != nil {
  890. // Restore from fallback
  891. if errRename := os.Rename(fallbackPath, config.GetDBPath()); errRename != nil {
  892. return common.NewErrorf("Error moving db file and restoring fallback: %v", errRename)
  893. }
  894. return common.NewErrorf("Error moving db file: %v", err)
  895. }
  896. // Open & migrate new DB
  897. if err = database.InitDB(config.GetDBPath()); err != nil {
  898. if errRename := os.Rename(fallbackPath, config.GetDBPath()); errRename != nil {
  899. return common.NewErrorf("Error migrating db and restoring fallback: %v", errRename)
  900. }
  901. return common.NewErrorf("Error migrating db: %v", err)
  902. }
  903. s.inboundService.MigrateDB()
  904. // Start Xray
  905. if err = s.RestartXrayService(); err != nil {
  906. return common.NewErrorf("Imported DB but failed to start Xray: %v", err)
  907. }
  908. return nil
  909. }
  910. // IsValidGeofileName validates that the filename is safe for geofile operations.
  911. // It checks for path traversal attempts and ensures the filename contains only safe characters.
  912. func (s *ServerService) IsValidGeofileName(filename string) bool {
  913. if filename == "" {
  914. return false
  915. }
  916. // Check for path traversal attempts
  917. if strings.Contains(filename, "..") {
  918. return false
  919. }
  920. // Check for path separators (both forward and backward slash)
  921. if strings.ContainsAny(filename, `/\`) {
  922. return false
  923. }
  924. // Check for absolute path indicators
  925. if filepath.IsAbs(filename) {
  926. return false
  927. }
  928. // Additional security: only allow alphanumeric, dots, underscores, and hyphens
  929. // This is stricter than the general filename regex
  930. validGeofilePattern := `^[a-zA-Z0-9._-]+\.dat$`
  931. matched, _ := regexp.MatchString(validGeofilePattern, filename)
  932. return matched
  933. }
  934. func (s *ServerService) UpdateGeofile(fileName string) error {
  935. type geofileEntry struct {
  936. URL string
  937. FileName string
  938. }
  939. geofileAllowlist := map[string]geofileEntry{
  940. "geoip.dat": {"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat", "geoip.dat"},
  941. "geosite.dat": {"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat", "geosite.dat"},
  942. "geoip_IR.dat": {"https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat", "geoip_IR.dat"},
  943. "geosite_IR.dat": {"https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat", "geosite_IR.dat"},
  944. "geoip_RU.dat": {"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat", "geoip_RU.dat"},
  945. "geosite_RU.dat": {"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat", "geosite_RU.dat"},
  946. }
  947. // Strict allowlist check to avoid writing uncontrolled files
  948. if fileName != "" {
  949. if _, ok := geofileAllowlist[fileName]; !ok {
  950. return common.NewErrorf("Invalid geofile name: %q not in allowlist", fileName)
  951. }
  952. }
  953. downloadFile := func(url, destPath string) error {
  954. var req *http.Request
  955. req, err := http.NewRequest("GET", url, nil)
  956. if err != nil {
  957. return common.NewErrorf("Failed to create HTTP request for %s: %v", url, err)
  958. }
  959. var localFileModTime time.Time
  960. if fileInfo, err := os.Stat(destPath); err == nil {
  961. localFileModTime = fileInfo.ModTime()
  962. if !localFileModTime.IsZero() {
  963. req.Header.Set("If-Modified-Since", localFileModTime.UTC().Format(http.TimeFormat))
  964. }
  965. }
  966. client := &http.Client{}
  967. resp, err := client.Do(req)
  968. if err != nil {
  969. return common.NewErrorf("Failed to download Geofile from %s: %v", url, err)
  970. }
  971. defer resp.Body.Close()
  972. // Parse Last-Modified header from server
  973. var serverModTime time.Time
  974. serverModTimeStr := resp.Header.Get("Last-Modified")
  975. if serverModTimeStr != "" {
  976. parsedTime, err := time.Parse(http.TimeFormat, serverModTimeStr)
  977. if err != nil {
  978. logger.Warningf("Failed to parse Last-Modified header for %s: %v", url, err)
  979. } else {
  980. serverModTime = parsedTime
  981. }
  982. }
  983. // Function to update local file's modification time
  984. updateFileModTime := func() {
  985. if !serverModTime.IsZero() {
  986. if err := os.Chtimes(destPath, serverModTime, serverModTime); err != nil {
  987. logger.Warningf("Failed to update modification time for %s: %v", destPath, err)
  988. }
  989. }
  990. }
  991. // Handle 304 Not Modified
  992. if resp.StatusCode == http.StatusNotModified {
  993. updateFileModTime()
  994. return nil
  995. }
  996. if resp.StatusCode != http.StatusOK {
  997. return common.NewErrorf("Failed to download Geofile from %s: received status code %d", url, resp.StatusCode)
  998. }
  999. file, err := os.Create(destPath)
  1000. if err != nil {
  1001. return common.NewErrorf("Failed to create Geofile %s: %v", destPath, err)
  1002. }
  1003. defer file.Close()
  1004. _, err = io.Copy(file, resp.Body)
  1005. if err != nil {
  1006. return common.NewErrorf("Failed to save Geofile %s: %v", destPath, err)
  1007. }
  1008. updateFileModTime()
  1009. return nil
  1010. }
  1011. var errorMessages []string
  1012. if fileName == "" {
  1013. // Download all geofiles
  1014. for _, entry := range geofileAllowlist {
  1015. destPath := filepath.Join(config.GetBinFolderPath(), entry.FileName)
  1016. if err := downloadFile(entry.URL, destPath); err != nil {
  1017. errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", entry.FileName, err))
  1018. }
  1019. }
  1020. } else {
  1021. entry := geofileAllowlist[fileName]
  1022. destPath := filepath.Join(config.GetBinFolderPath(), entry.FileName)
  1023. if err := downloadFile(entry.URL, destPath); err != nil {
  1024. errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", entry.FileName, err))
  1025. }
  1026. }
  1027. err := s.RestartXrayService()
  1028. if err != nil {
  1029. errorMessages = append(errorMessages, fmt.Sprintf("Updated Geofile '%s' but Failed to start Xray: %v", fileName, err))
  1030. }
  1031. if len(errorMessages) > 0 {
  1032. return common.NewErrorf("%s", strings.Join(errorMessages, "\r\n"))
  1033. }
  1034. return nil
  1035. }
  1036. func (s *ServerService) GetNewX25519Cert() (any, error) {
  1037. // Run the command
  1038. cmd := exec.Command(xray.GetBinaryPath(), "x25519")
  1039. var out bytes.Buffer
  1040. cmd.Stdout = &out
  1041. err := cmd.Run()
  1042. if err != nil {
  1043. return nil, err
  1044. }
  1045. lines := strings.Split(out.String(), "\n")
  1046. privateKeyLine := strings.Split(lines[0], ":")
  1047. publicKeyLine := strings.Split(lines[1], ":")
  1048. privateKey := strings.TrimSpace(privateKeyLine[1])
  1049. publicKey := strings.TrimSpace(publicKeyLine[1])
  1050. keyPair := map[string]any{
  1051. "privateKey": privateKey,
  1052. "publicKey": publicKey,
  1053. }
  1054. return keyPair, nil
  1055. }
  1056. func (s *ServerService) GetNewmldsa65() (any, error) {
  1057. // Run the command
  1058. cmd := exec.Command(xray.GetBinaryPath(), "mldsa65")
  1059. var out bytes.Buffer
  1060. cmd.Stdout = &out
  1061. err := cmd.Run()
  1062. if err != nil {
  1063. return nil, err
  1064. }
  1065. lines := strings.Split(out.String(), "\n")
  1066. SeedLine := strings.Split(lines[0], ":")
  1067. VerifyLine := strings.Split(lines[1], ":")
  1068. seed := strings.TrimSpace(SeedLine[1])
  1069. verify := strings.TrimSpace(VerifyLine[1])
  1070. keyPair := map[string]any{
  1071. "seed": seed,
  1072. "verify": verify,
  1073. }
  1074. return keyPair, nil
  1075. }
  1076. func (s *ServerService) GetNewEchCert(sni string) (any, error) {
  1077. // Run the command
  1078. cmd := exec.Command(xray.GetBinaryPath(), "tls", "ech", "--serverName", sni)
  1079. var out bytes.Buffer
  1080. cmd.Stdout = &out
  1081. err := cmd.Run()
  1082. if err != nil {
  1083. return nil, err
  1084. }
  1085. lines := strings.Split(out.String(), "\n")
  1086. if len(lines) < 4 {
  1087. return nil, common.NewError("invalid ech cert")
  1088. }
  1089. configList := lines[1]
  1090. serverKeys := lines[3]
  1091. return map[string]any{
  1092. "echServerKeys": serverKeys,
  1093. "echConfigList": configList,
  1094. }, nil
  1095. }
  1096. func (s *ServerService) GetNewVlessEnc() (any, error) {
  1097. cmd := exec.Command(xray.GetBinaryPath(), "vlessenc")
  1098. var out bytes.Buffer
  1099. cmd.Stdout = &out
  1100. if err := cmd.Run(); err != nil {
  1101. return nil, err
  1102. }
  1103. lines := strings.Split(out.String(), "\n")
  1104. var auths []map[string]string
  1105. var current map[string]string
  1106. for _, line := range lines {
  1107. line = strings.TrimSpace(line)
  1108. if strings.HasPrefix(line, "Authentication:") {
  1109. if current != nil {
  1110. auths = append(auths, current)
  1111. }
  1112. current = map[string]string{
  1113. "label": strings.TrimSpace(strings.TrimPrefix(line, "Authentication:")),
  1114. }
  1115. } else if strings.HasPrefix(line, `"decryption"`) || strings.HasPrefix(line, `"encryption"`) {
  1116. parts := strings.SplitN(line, ":", 2)
  1117. if len(parts) == 2 && current != nil {
  1118. key := strings.Trim(parts[0], `" `)
  1119. val := strings.Trim(parts[1], `" `)
  1120. current[key] = val
  1121. }
  1122. }
  1123. }
  1124. if current != nil {
  1125. auths = append(auths, current)
  1126. }
  1127. return map[string]any{
  1128. "auths": auths,
  1129. }, nil
  1130. }
  1131. func (s *ServerService) GetNewUUID() (map[string]string, error) {
  1132. newUUID, err := uuid.NewRandom()
  1133. if err != nil {
  1134. return nil, fmt.Errorf("failed to generate UUID: %w", err)
  1135. }
  1136. return map[string]string{
  1137. "uuid": newUUID.String(),
  1138. }, nil
  1139. }
  1140. func (s *ServerService) GetNewmlkem768() (any, error) {
  1141. // Run the command
  1142. cmd := exec.Command(xray.GetBinaryPath(), "mlkem768")
  1143. var out bytes.Buffer
  1144. cmd.Stdout = &out
  1145. err := cmd.Run()
  1146. if err != nil {
  1147. return nil, err
  1148. }
  1149. lines := strings.Split(out.String(), "\n")
  1150. SeedLine := strings.Split(lines[0], ":")
  1151. ClientLine := strings.Split(lines[1], ":")
  1152. seed := strings.TrimSpace(SeedLine[1])
  1153. client := strings.TrimSpace(ClientLine[1])
  1154. keyPair := map[string]any{
  1155. "seed": seed,
  1156. "client": client,
  1157. }
  1158. return keyPair, nil
  1159. }