1
0

server.go 34 KB

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