server.go 35 KB

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