server.go 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338
  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. 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 := http.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, fs.ModePerm)
  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. // Stop Xray (ignore error but log)
  860. if errStop := s.StopXrayService(); errStop != nil {
  861. logger.Warningf("Failed to stop Xray before DB import: %v", errStop)
  862. }
  863. // Close existing DB to release file locks (especially on Windows)
  864. if errClose := database.CloseDB(); errClose != nil {
  865. logger.Warningf("Failed to close existing DB before replacement: %v", errClose)
  866. }
  867. // Backup the current database for fallback
  868. fallbackPath := fmt.Sprintf("%s.backup", config.GetDBPath())
  869. // Remove the existing fallback file (if any)
  870. if _, err := os.Stat(fallbackPath); err == nil {
  871. if errRemove := os.Remove(fallbackPath); errRemove != nil {
  872. return common.NewErrorf("Error removing existing fallback db file: %v", errRemove)
  873. }
  874. }
  875. // Move the current database to the fallback location
  876. if err = os.Rename(config.GetDBPath(), fallbackPath); err != nil {
  877. return common.NewErrorf("Error backing up current db file: %v", err)
  878. }
  879. // Defer fallback cleanup ONLY if everything goes well
  880. defer func() {
  881. if _, err := os.Stat(fallbackPath); err == nil {
  882. if rerr := os.Remove(fallbackPath); rerr != nil {
  883. logger.Warningf("Warning: failed to remove fallback file: %v", rerr)
  884. }
  885. }
  886. }()
  887. // Move temp to DB path
  888. if err = os.Rename(tempPath, config.GetDBPath()); err != nil {
  889. // Restore from fallback
  890. if errRename := os.Rename(fallbackPath, config.GetDBPath()); errRename != nil {
  891. return common.NewErrorf("Error moving db file and restoring fallback: %v", errRename)
  892. }
  893. return common.NewErrorf("Error moving db file: %v", err)
  894. }
  895. // Open & migrate new DB
  896. if err = database.InitDB(config.GetDBPath()); err != nil {
  897. if errRename := os.Rename(fallbackPath, config.GetDBPath()); errRename != nil {
  898. return common.NewErrorf("Error migrating db and restoring fallback: %v", errRename)
  899. }
  900. return common.NewErrorf("Error migrating db: %v", err)
  901. }
  902. s.inboundService.MigrateDB()
  903. // Start Xray
  904. if err = s.RestartXrayService(); err != nil {
  905. return common.NewErrorf("Imported DB but failed to start Xray: %v", err)
  906. }
  907. return nil
  908. }
  909. // IsValidGeofileName validates that the filename is safe for geofile operations.
  910. // It checks for path traversal attempts and ensures the filename contains only safe characters.
  911. func (s *ServerService) IsValidGeofileName(filename string) bool {
  912. if filename == "" {
  913. return false
  914. }
  915. // Check for path traversal attempts
  916. if strings.Contains(filename, "..") {
  917. return false
  918. }
  919. // Check for path separators (both forward and backward slash)
  920. if strings.ContainsAny(filename, `/\`) {
  921. return false
  922. }
  923. // Check for absolute path indicators
  924. if filepath.IsAbs(filename) {
  925. return false
  926. }
  927. // Additional security: only allow alphanumeric, dots, underscores, and hyphens
  928. // This is stricter than the general filename regex
  929. validGeofilePattern := `^[a-zA-Z0-9._-]+\.dat$`
  930. matched, _ := regexp.MatchString(validGeofilePattern, filename)
  931. return matched
  932. }
  933. func (s *ServerService) UpdateGeofile(fileName string) error {
  934. type geofileEntry struct {
  935. URL string
  936. FileName string
  937. }
  938. geofileAllowlist := map[string]geofileEntry{
  939. "geoip.dat": {"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat", "geoip.dat"},
  940. "geosite.dat": {"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat", "geosite.dat"},
  941. "geoip_IR.dat": {"https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat", "geoip_IR.dat"},
  942. "geosite_IR.dat": {"https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat", "geosite_IR.dat"},
  943. "geoip_RU.dat": {"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat", "geoip_RU.dat"},
  944. "geosite_RU.dat": {"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat", "geosite_RU.dat"},
  945. }
  946. // Strict allowlist check to avoid writing uncontrolled files
  947. if fileName != "" {
  948. if _, ok := geofileAllowlist[fileName]; !ok {
  949. return common.NewErrorf("Invalid geofile name: %q not in allowlist", fileName)
  950. }
  951. }
  952. downloadFile := func(url, destPath string) error {
  953. var req *http.Request
  954. req, err := http.NewRequest("GET", url, nil)
  955. if err != nil {
  956. return common.NewErrorf("Failed to create HTTP request for %s: %v", url, err)
  957. }
  958. var localFileModTime time.Time
  959. if fileInfo, err := os.Stat(destPath); err == nil {
  960. localFileModTime = fileInfo.ModTime()
  961. if !localFileModTime.IsZero() {
  962. req.Header.Set("If-Modified-Since", localFileModTime.UTC().Format(http.TimeFormat))
  963. }
  964. }
  965. client := &http.Client{}
  966. resp, err := client.Do(req)
  967. if err != nil {
  968. return common.NewErrorf("Failed to download Geofile from %s: %v", url, err)
  969. }
  970. defer resp.Body.Close()
  971. // Parse Last-Modified header from server
  972. var serverModTime time.Time
  973. serverModTimeStr := resp.Header.Get("Last-Modified")
  974. if serverModTimeStr != "" {
  975. parsedTime, err := time.Parse(http.TimeFormat, serverModTimeStr)
  976. if err != nil {
  977. logger.Warningf("Failed to parse Last-Modified header for %s: %v", url, err)
  978. } else {
  979. serverModTime = parsedTime
  980. }
  981. }
  982. // Function to update local file's modification time
  983. updateFileModTime := func() {
  984. if !serverModTime.IsZero() {
  985. if err := os.Chtimes(destPath, serverModTime, serverModTime); err != nil {
  986. logger.Warningf("Failed to update modification time for %s: %v", destPath, err)
  987. }
  988. }
  989. }
  990. // Handle 304 Not Modified
  991. if resp.StatusCode == http.StatusNotModified {
  992. updateFileModTime()
  993. return nil
  994. }
  995. if resp.StatusCode != http.StatusOK {
  996. return common.NewErrorf("Failed to download Geofile from %s: received status code %d", url, resp.StatusCode)
  997. }
  998. file, err := os.Create(destPath)
  999. if err != nil {
  1000. return common.NewErrorf("Failed to create Geofile %s: %v", destPath, err)
  1001. }
  1002. defer file.Close()
  1003. _, err = io.Copy(file, resp.Body)
  1004. if err != nil {
  1005. return common.NewErrorf("Failed to save Geofile %s: %v", destPath, err)
  1006. }
  1007. updateFileModTime()
  1008. return nil
  1009. }
  1010. var errorMessages []string
  1011. if fileName == "" {
  1012. // Download all geofiles
  1013. for _, entry := range geofileAllowlist {
  1014. destPath := filepath.Join(config.GetBinFolderPath(), entry.FileName)
  1015. if err := downloadFile(entry.URL, destPath); err != nil {
  1016. errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", entry.FileName, err))
  1017. }
  1018. }
  1019. } else {
  1020. entry := geofileAllowlist[fileName]
  1021. destPath := filepath.Join(config.GetBinFolderPath(), entry.FileName)
  1022. if err := downloadFile(entry.URL, destPath); err != nil {
  1023. errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", entry.FileName, err))
  1024. }
  1025. }
  1026. err := s.RestartXrayService()
  1027. if err != nil {
  1028. errorMessages = append(errorMessages, fmt.Sprintf("Updated Geofile '%s' but Failed to start Xray: %v", fileName, err))
  1029. }
  1030. if len(errorMessages) > 0 {
  1031. return common.NewErrorf("%s", strings.Join(errorMessages, "\r\n"))
  1032. }
  1033. return nil
  1034. }
  1035. func (s *ServerService) GetNewX25519Cert() (any, error) {
  1036. // Run the command
  1037. cmd := exec.Command(xray.GetBinaryPath(), "x25519")
  1038. var out bytes.Buffer
  1039. cmd.Stdout = &out
  1040. err := cmd.Run()
  1041. if err != nil {
  1042. return nil, err
  1043. }
  1044. lines := strings.Split(out.String(), "\n")
  1045. privateKeyLine := strings.Split(lines[0], ":")
  1046. publicKeyLine := strings.Split(lines[1], ":")
  1047. privateKey := strings.TrimSpace(privateKeyLine[1])
  1048. publicKey := strings.TrimSpace(publicKeyLine[1])
  1049. keyPair := map[string]any{
  1050. "privateKey": privateKey,
  1051. "publicKey": publicKey,
  1052. }
  1053. return keyPair, nil
  1054. }
  1055. func (s *ServerService) GetNewmldsa65() (any, error) {
  1056. // Run the command
  1057. cmd := exec.Command(xray.GetBinaryPath(), "mldsa65")
  1058. var out bytes.Buffer
  1059. cmd.Stdout = &out
  1060. err := cmd.Run()
  1061. if err != nil {
  1062. return nil, err
  1063. }
  1064. lines := strings.Split(out.String(), "\n")
  1065. SeedLine := strings.Split(lines[0], ":")
  1066. VerifyLine := strings.Split(lines[1], ":")
  1067. seed := strings.TrimSpace(SeedLine[1])
  1068. verify := strings.TrimSpace(VerifyLine[1])
  1069. keyPair := map[string]any{
  1070. "seed": seed,
  1071. "verify": verify,
  1072. }
  1073. return keyPair, nil
  1074. }
  1075. func (s *ServerService) GetNewEchCert(sni string) (any, error) {
  1076. // Run the command
  1077. cmd := exec.Command(xray.GetBinaryPath(), "tls", "ech", "--serverName", sni)
  1078. var out bytes.Buffer
  1079. cmd.Stdout = &out
  1080. err := cmd.Run()
  1081. if err != nil {
  1082. return nil, err
  1083. }
  1084. lines := strings.Split(out.String(), "\n")
  1085. if len(lines) < 4 {
  1086. return nil, common.NewError("invalid ech cert")
  1087. }
  1088. configList := lines[1]
  1089. serverKeys := lines[3]
  1090. return map[string]any{
  1091. "echServerKeys": serverKeys,
  1092. "echConfigList": configList,
  1093. }, nil
  1094. }
  1095. func (s *ServerService) GetNewVlessEnc() (any, error) {
  1096. cmd := exec.Command(xray.GetBinaryPath(), "vlessenc")
  1097. var out bytes.Buffer
  1098. cmd.Stdout = &out
  1099. if err := cmd.Run(); err != nil {
  1100. return nil, err
  1101. }
  1102. lines := strings.Split(out.String(), "\n")
  1103. var auths []map[string]string
  1104. var current map[string]string
  1105. for _, line := range lines {
  1106. line = strings.TrimSpace(line)
  1107. if strings.HasPrefix(line, "Authentication:") {
  1108. if current != nil {
  1109. auths = append(auths, current)
  1110. }
  1111. current = map[string]string{
  1112. "label": strings.TrimSpace(strings.TrimPrefix(line, "Authentication:")),
  1113. }
  1114. } else if strings.HasPrefix(line, `"decryption"`) || strings.HasPrefix(line, `"encryption"`) {
  1115. parts := strings.SplitN(line, ":", 2)
  1116. if len(parts) == 2 && current != nil {
  1117. key := strings.Trim(parts[0], `" `)
  1118. val := strings.Trim(parts[1], `" `)
  1119. current[key] = val
  1120. }
  1121. }
  1122. }
  1123. if current != nil {
  1124. auths = append(auths, current)
  1125. }
  1126. return map[string]any{
  1127. "auths": auths,
  1128. }, nil
  1129. }
  1130. func (s *ServerService) GetNewUUID() (map[string]string, error) {
  1131. newUUID, err := uuid.NewRandom()
  1132. if err != nil {
  1133. return nil, fmt.Errorf("failed to generate UUID: %w", err)
  1134. }
  1135. return map[string]string{
  1136. "uuid": newUUID.String(),
  1137. }, nil
  1138. }
  1139. func (s *ServerService) GetNewmlkem768() (any, error) {
  1140. // Run the command
  1141. cmd := exec.Command(xray.GetBinaryPath(), "mlkem768")
  1142. var out bytes.Buffer
  1143. cmd.Stdout = &out
  1144. err := cmd.Run()
  1145. if err != nil {
  1146. return nil, err
  1147. }
  1148. lines := strings.Split(out.String(), "\n")
  1149. SeedLine := strings.Split(lines[0], ":")
  1150. ClientLine := strings.Split(lines[1], ":")
  1151. seed := strings.TrimSpace(SeedLine[1])
  1152. client := strings.TrimSpace(ClientLine[1])
  1153. keyPair := map[string]any{
  1154. "seed": seed,
  1155. "client": client,
  1156. }
  1157. return keyPair, nil
  1158. }