server.go 31 KB

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