server.go 31 KB

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