server.go 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423
  1. package service
  2. import (
  3. "archive/zip"
  4. "bufio"
  5. "bytes"
  6. "encoding/json"
  7. "fmt"
  8. "io"
  9. "mime/multipart"
  10. "net/http"
  11. "os"
  12. "os/exec"
  13. "path/filepath"
  14. "regexp"
  15. "runtime"
  16. "slices"
  17. "strconv"
  18. "strings"
  19. "sync"
  20. "time"
  21. "github.com/mhsanaei/3x-ui/v3/config"
  22. "github.com/mhsanaei/3x-ui/v3/database"
  23. "github.com/mhsanaei/3x-ui/v3/logger"
  24. "github.com/mhsanaei/3x-ui/v3/util/common"
  25. "github.com/mhsanaei/3x-ui/v3/util/sys"
  26. "github.com/mhsanaei/3x-ui/v3/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. var xrayVersionsClient = &http.Client{Timeout: 10 * time.Second}
  443. const (
  444. maxXrayArchiveBytes = 200 << 20
  445. maxXrayBinaryBytes = 200 << 20
  446. )
  447. func (s *ServerService) GetXrayVersions() ([]string, error) {
  448. const (
  449. XrayURL = "https://api.github.com/repos/XTLS/Xray-core/releases"
  450. bufferSize = 8192
  451. )
  452. resp, err := xrayVersionsClient.Get(XrayURL)
  453. if err != nil {
  454. return nil, err
  455. }
  456. defer resp.Body.Close()
  457. // Check HTTP status code - GitHub API returns object instead of array on error
  458. if resp.StatusCode != http.StatusOK {
  459. bodyBytes, _ := io.ReadAll(resp.Body)
  460. var errorResponse struct {
  461. Message string `json:"message"`
  462. }
  463. if json.Unmarshal(bodyBytes, &errorResponse) == nil && errorResponse.Message != "" {
  464. return nil, fmt.Errorf("GitHub API error: %s", errorResponse.Message)
  465. }
  466. return nil, fmt.Errorf("GitHub API returned status %d: %s", resp.StatusCode, resp.Status)
  467. }
  468. buffer := bytes.NewBuffer(make([]byte, bufferSize))
  469. buffer.Reset()
  470. if _, err := buffer.ReadFrom(resp.Body); err != nil {
  471. return nil, err
  472. }
  473. var releases []Release
  474. if err := json.Unmarshal(buffer.Bytes(), &releases); err != nil {
  475. return nil, err
  476. }
  477. var versions []string
  478. for _, release := range releases {
  479. tagVersion := strings.TrimPrefix(release.TagName, "v")
  480. if tagVersion == "26.5.3" {
  481. continue
  482. }
  483. tagParts := strings.Split(tagVersion, ".")
  484. if len(tagParts) != 3 {
  485. continue
  486. }
  487. major, err1 := strconv.Atoi(tagParts[0])
  488. minor, err2 := strconv.Atoi(tagParts[1])
  489. patch, err3 := strconv.Atoi(tagParts[2])
  490. if err1 != nil || err2 != nil || err3 != nil {
  491. continue
  492. }
  493. if major > 26 || (major == 26 && minor > 4) || (major == 26 && minor == 4 && patch >= 25) {
  494. versions = append(versions, release.TagName)
  495. }
  496. }
  497. return versions, nil
  498. }
  499. func (s *ServerService) StopXrayService() error {
  500. err := s.xrayService.StopXray()
  501. if err != nil {
  502. logger.Error("stop xray failed:", err)
  503. return err
  504. }
  505. return nil
  506. }
  507. func (s *ServerService) RestartXrayService() error {
  508. err := s.xrayService.RestartXray(true)
  509. if err != nil {
  510. logger.Error("start xray failed:", err)
  511. return err
  512. }
  513. return nil
  514. }
  515. func (s *ServerService) downloadXRay(version string) (string, error) {
  516. osName := runtime.GOOS
  517. arch := runtime.GOARCH
  518. switch osName {
  519. case "darwin":
  520. osName = "macos"
  521. case "windows":
  522. osName = "windows"
  523. }
  524. switch arch {
  525. case "amd64":
  526. arch = "64"
  527. case "arm64":
  528. arch = "arm64-v8a"
  529. case "armv7":
  530. arch = "arm32-v7a"
  531. case "armv6":
  532. arch = "arm32-v6"
  533. case "armv5":
  534. arch = "arm32-v5"
  535. case "386":
  536. arch = "32"
  537. case "s390x":
  538. arch = "s390x"
  539. }
  540. fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)
  541. url := fmt.Sprintf("https://github.com/XTLS/Xray-core/releases/download/%s/%s", version, fileName)
  542. client := &http.Client{Timeout: 60 * time.Second}
  543. resp, err := client.Get(url)
  544. if err != nil {
  545. return "", err
  546. }
  547. defer resp.Body.Close()
  548. if resp.StatusCode != http.StatusOK {
  549. return "", fmt.Errorf("download xray: unexpected HTTP %d", resp.StatusCode)
  550. }
  551. if resp.ContentLength > maxXrayArchiveBytes {
  552. return "", fmt.Errorf("download xray: archive exceeds %d bytes", maxXrayArchiveBytes)
  553. }
  554. file, err := os.CreateTemp("", "xray-*.zip")
  555. if err != nil {
  556. return "", err
  557. }
  558. path := file.Name()
  559. ok := false
  560. defer func() {
  561. _ = file.Close()
  562. if !ok {
  563. _ = os.Remove(path)
  564. }
  565. }()
  566. n, err := io.Copy(file, io.LimitReader(resp.Body, maxXrayArchiveBytes+1))
  567. if err != nil {
  568. return "", err
  569. }
  570. if n > maxXrayArchiveBytes {
  571. return "", fmt.Errorf("download xray: archive exceeds %d bytes", maxXrayArchiveBytes)
  572. }
  573. ok = true
  574. return path, nil
  575. }
  576. func (s *ServerService) UpdateXray(version string) error {
  577. versions, err := s.GetXrayVersions()
  578. if err != nil {
  579. return err
  580. }
  581. if !slices.Contains(versions, version) {
  582. return fmt.Errorf("xray version %q is not in the fetched release list", version)
  583. }
  584. // 1. Stop xray before doing anything
  585. if err := s.StopXrayService(); err != nil {
  586. logger.Warning("failed to stop xray before update:", err)
  587. }
  588. // 2. Download the zip
  589. zipFileName, err := s.downloadXRay(version)
  590. if err != nil {
  591. return err
  592. }
  593. defer os.Remove(zipFileName)
  594. zipFile, err := os.Open(zipFileName)
  595. if err != nil {
  596. return err
  597. }
  598. defer zipFile.Close()
  599. stat, err := zipFile.Stat()
  600. if err != nil {
  601. return err
  602. }
  603. reader, err := zip.NewReader(zipFile, stat.Size())
  604. if err != nil {
  605. return err
  606. }
  607. // 3. Helper to extract files
  608. copyZipFile := func(zipName string, fileName string) error {
  609. zipFile, err := reader.Open(zipName)
  610. if err != nil {
  611. return err
  612. }
  613. defer zipFile.Close()
  614. if err := os.MkdirAll(filepath.Dir(fileName), 0755); err != nil {
  615. return err
  616. }
  617. tmpFile, err := os.CreateTemp(filepath.Dir(fileName), ".xray-*")
  618. if err != nil {
  619. return err
  620. }
  621. tmpPath := tmpFile.Name()
  622. ok := false
  623. defer func() {
  624. _ = tmpFile.Close()
  625. if !ok {
  626. _ = os.Remove(tmpPath)
  627. }
  628. }()
  629. n, err := io.Copy(tmpFile, io.LimitReader(zipFile, maxXrayBinaryBytes+1))
  630. if err != nil {
  631. return err
  632. }
  633. if n > maxXrayBinaryBytes {
  634. return fmt.Errorf("xray binary exceeds %d bytes", maxXrayBinaryBytes)
  635. }
  636. if err := tmpFile.Chmod(0755); err != nil {
  637. return err
  638. }
  639. if err := tmpFile.Close(); err != nil {
  640. return err
  641. }
  642. if runtime.GOOS == "windows" {
  643. _ = os.Remove(fileName)
  644. }
  645. if err := os.Rename(tmpPath, fileName); err != nil {
  646. return err
  647. }
  648. ok = true
  649. return nil
  650. }
  651. // 4. Extract correct binary
  652. if runtime.GOOS == "windows" {
  653. targetBinary := filepath.Join("bin", "xray-windows-amd64.exe")
  654. err = copyZipFile("xray.exe", targetBinary)
  655. } else {
  656. err = copyZipFile("xray", xray.GetBinaryPath())
  657. }
  658. if err != nil {
  659. return err
  660. }
  661. // 5. Restart xray
  662. if err := s.xrayService.RestartXray(true); err != nil {
  663. logger.Error("start xray failed:", err)
  664. return err
  665. }
  666. return nil
  667. }
  668. func (s *ServerService) GetLogs(count string, level string, syslog string) []string {
  669. c, _ := strconv.Atoi(count)
  670. var lines []string
  671. if syslog == "true" {
  672. // Check if running on Windows - journalctl is not available
  673. if runtime.GOOS == "windows" {
  674. return []string{"Syslog is not supported on Windows. Please use application logs instead by unchecking the 'Syslog' option."}
  675. }
  676. // Validate and sanitize count parameter
  677. countInt, err := strconv.Atoi(count)
  678. if err != nil || countInt < 1 || countInt > 10000 {
  679. return []string{"Invalid count parameter - must be a number between 1 and 10000"}
  680. }
  681. // Validate level parameter - only allow valid syslog levels
  682. validLevels := map[string]bool{
  683. "0": true, "emerg": true,
  684. "1": true, "alert": true,
  685. "2": true, "crit": true,
  686. "3": true, "err": true,
  687. "4": true, "warning": true,
  688. "5": true, "notice": true,
  689. "6": true, "info": true,
  690. "7": true, "debug": true,
  691. }
  692. if !validLevels[level] {
  693. return []string{"Invalid level parameter - must be a valid syslog level"}
  694. }
  695. // Use hardcoded command with validated parameters
  696. cmd := exec.Command("journalctl", "-u", "x-ui", "--no-pager", "-n", strconv.Itoa(countInt), "-p", level)
  697. var out bytes.Buffer
  698. cmd.Stdout = &out
  699. err = cmd.Run()
  700. if err != nil {
  701. return []string{"Failed to run journalctl command! Make sure systemd is available and x-ui service is registered."}
  702. }
  703. lines = strings.Split(out.String(), "\n")
  704. } else {
  705. lines = logger.GetLogs(c, level)
  706. }
  707. return lines
  708. }
  709. func (s *ServerService) GetXrayLogs(
  710. count string,
  711. filter string,
  712. showDirect string,
  713. showBlocked string,
  714. showProxy string,
  715. freedoms []string,
  716. blackholes []string) []LogEntry {
  717. const (
  718. Direct = iota
  719. Blocked
  720. Proxied
  721. )
  722. countInt, _ := strconv.Atoi(count)
  723. var entries []LogEntry
  724. pathToAccessLog, err := xray.GetAccessLogPath()
  725. if err != nil {
  726. return nil
  727. }
  728. file, err := os.Open(pathToAccessLog)
  729. if err != nil {
  730. return nil
  731. }
  732. defer file.Close()
  733. scanner := bufio.NewScanner(file)
  734. for scanner.Scan() {
  735. line := strings.TrimSpace(scanner.Text())
  736. if line == "" || strings.Contains(line, "api -> api") {
  737. //skipping empty lines and api calls
  738. continue
  739. }
  740. if filter != "" && !strings.Contains(line, filter) {
  741. //applying filter if it's not empty
  742. continue
  743. }
  744. var entry LogEntry
  745. parts := strings.Fields(line)
  746. for i, part := range parts {
  747. if i == 0 {
  748. dateTime, err := time.ParseInLocation("2006/01/02 15:04:05.999999", parts[0]+" "+parts[1], time.Local)
  749. if err != nil {
  750. continue
  751. }
  752. entry.DateTime = dateTime.UTC()
  753. }
  754. if part == "from" {
  755. entry.FromAddress = strings.TrimLeft(parts[i+1], "/")
  756. } else if part == "accepted" {
  757. entry.ToAddress = strings.TrimLeft(parts[i+1], "/")
  758. } else if strings.HasPrefix(part, "[") {
  759. entry.Inbound = part[1:]
  760. } else if strings.HasSuffix(part, "]") {
  761. entry.Outbound = part[:len(part)-1]
  762. } else if part == "email:" {
  763. entry.Email = parts[i+1]
  764. }
  765. }
  766. if logEntryContains(line, freedoms) {
  767. if showDirect == "false" {
  768. continue
  769. }
  770. entry.Event = Direct
  771. } else if logEntryContains(line, blackholes) {
  772. if showBlocked == "false" {
  773. continue
  774. }
  775. entry.Event = Blocked
  776. } else {
  777. if showProxy == "false" {
  778. continue
  779. }
  780. entry.Event = Proxied
  781. }
  782. entries = append(entries, entry)
  783. }
  784. if err := scanner.Err(); err != nil {
  785. return nil
  786. }
  787. if len(entries) > countInt {
  788. entries = entries[len(entries)-countInt:]
  789. }
  790. return entries
  791. }
  792. // isVirtualInterface returns true for loopback and virtual/tunnel interfaces
  793. // that should be excluded from network traffic statistics.
  794. func isVirtualInterface(name string) bool {
  795. // Exact matches
  796. if name == "lo" || name == "lo0" {
  797. return true
  798. }
  799. // Prefix matches for virtual/tunnel interfaces
  800. virtualPrefixes := []string{
  801. "loopback",
  802. "docker",
  803. "br-",
  804. "veth",
  805. "virbr",
  806. "tun",
  807. "tap",
  808. "wg",
  809. "tailscale",
  810. "zt",
  811. }
  812. for _, prefix := range virtualPrefixes {
  813. if strings.HasPrefix(name, prefix) {
  814. return true
  815. }
  816. }
  817. return false
  818. }
  819. func logEntryContains(line string, suffixes []string) bool {
  820. for _, sfx := range suffixes {
  821. if strings.Contains(line, sfx+"]") {
  822. return true
  823. }
  824. }
  825. return false
  826. }
  827. func (s *ServerService) GetConfigJson() (any, error) {
  828. config, err := s.xrayService.GetXrayConfig()
  829. if err != nil {
  830. return nil, err
  831. }
  832. contents, err := json.MarshalIndent(config, "", " ")
  833. if err != nil {
  834. return nil, err
  835. }
  836. var jsonData any
  837. err = json.Unmarshal(contents, &jsonData)
  838. if err != nil {
  839. return nil, err
  840. }
  841. return jsonData, nil
  842. }
  843. func (s *ServerService) GetDb() ([]byte, error) {
  844. // Update by manually trigger a checkpoint operation
  845. err := database.Checkpoint()
  846. if err != nil {
  847. return nil, err
  848. }
  849. // Open the file for reading
  850. file, err := os.Open(config.GetDBPath())
  851. if err != nil {
  852. return nil, err
  853. }
  854. defer file.Close()
  855. // Read the file contents
  856. fileContents, err := io.ReadAll(file)
  857. if err != nil {
  858. return nil, err
  859. }
  860. return fileContents, nil
  861. }
  862. func (s *ServerService) ImportDB(file multipart.File) error {
  863. // Check if the file is a SQLite database
  864. isValidDb, err := database.IsSQLiteDB(file)
  865. if err != nil {
  866. return common.NewErrorf("Error checking db file format: %v", err)
  867. }
  868. if !isValidDb {
  869. return common.NewError("Invalid db file format")
  870. }
  871. // Reset the file reader to the beginning
  872. _, err = file.Seek(0, 0)
  873. if err != nil {
  874. return common.NewErrorf("Error resetting file reader: %v", err)
  875. }
  876. // Save the file as a temporary file
  877. tempPath := fmt.Sprintf("%s.temp", config.GetDBPath())
  878. // Remove the existing temporary file (if any)
  879. if _, err := os.Stat(tempPath); err == nil {
  880. if errRemove := os.Remove(tempPath); errRemove != nil {
  881. return common.NewErrorf("Error removing existing temporary db file: %v", errRemove)
  882. }
  883. }
  884. // Create the temporary file
  885. tempFile, err := os.Create(tempPath)
  886. if err != nil {
  887. return common.NewErrorf("Error creating temporary db file: %v", err)
  888. }
  889. // Robust deferred cleanup for the temporary file
  890. defer func() {
  891. if tempFile != nil {
  892. if cerr := tempFile.Close(); cerr != nil {
  893. logger.Warningf("Warning: failed to close temp file: %v", cerr)
  894. }
  895. }
  896. if _, err := os.Stat(tempPath); err == nil {
  897. if rerr := os.Remove(tempPath); rerr != nil {
  898. logger.Warningf("Warning: failed to remove temp file: %v", rerr)
  899. }
  900. }
  901. }()
  902. // Save uploaded file to temporary file
  903. if _, err = io.Copy(tempFile, file); err != nil {
  904. return common.NewErrorf("Error saving db: %v", err)
  905. }
  906. // Close temp file before opening via sqlite
  907. if err = tempFile.Close(); err != nil {
  908. return common.NewErrorf("Error closing temporary db file: %v", err)
  909. }
  910. tempFile = nil
  911. // Validate integrity (no migrations / side effects)
  912. if err = database.ValidateSQLiteDB(tempPath); err != nil {
  913. return common.NewErrorf("Invalid or corrupt db file: %v", err)
  914. }
  915. xrayStopped := true
  916. defer func() {
  917. if xrayStopped {
  918. if errR := s.RestartXrayService(); errR != nil {
  919. logger.Warningf("Failed to restart Xray after DB import error: %v", errR)
  920. }
  921. }
  922. }()
  923. if errStop := s.StopXrayService(); errStop != nil {
  924. logger.Warningf("Failed to stop Xray before DB import: %v", errStop)
  925. }
  926. if errClose := database.CloseDB(); errClose != nil {
  927. logger.Warningf("Failed to close existing DB before replacement: %v", errClose)
  928. }
  929. // Backup the current database for fallback
  930. fallbackPath := fmt.Sprintf("%s.backup", config.GetDBPath())
  931. // Remove the existing fallback file (if any)
  932. if _, err := os.Stat(fallbackPath); err == nil {
  933. if errRemove := os.Remove(fallbackPath); errRemove != nil {
  934. return common.NewErrorf("Error removing existing fallback db file: %v", errRemove)
  935. }
  936. }
  937. // Move the current database to the fallback location
  938. if err = os.Rename(config.GetDBPath(), fallbackPath); err != nil {
  939. return common.NewErrorf("Error backing up current db file: %v", err)
  940. }
  941. // Defer fallback cleanup ONLY if everything goes well
  942. defer func() {
  943. if _, err := os.Stat(fallbackPath); err == nil {
  944. if rerr := os.Remove(fallbackPath); rerr != nil {
  945. logger.Warningf("Warning: failed to remove fallback file: %v", rerr)
  946. }
  947. }
  948. }()
  949. // Move temp to DB path
  950. if err = os.Rename(tempPath, config.GetDBPath()); err != nil {
  951. // Restore from fallback
  952. if errRename := os.Rename(fallbackPath, config.GetDBPath()); errRename != nil {
  953. return common.NewErrorf("Error moving db file and restoring fallback: %v", errRename)
  954. }
  955. return common.NewErrorf("Error moving db file: %v", err)
  956. }
  957. // Open & migrate new DB
  958. if err = database.InitDB(config.GetDBPath()); err != nil {
  959. if errRename := os.Rename(fallbackPath, config.GetDBPath()); errRename != nil {
  960. return common.NewErrorf("Error migrating db and restoring fallback: %v", errRename)
  961. }
  962. return common.NewErrorf("Error migrating db: %v", err)
  963. }
  964. s.inboundService.MigrateDB()
  965. xrayStopped = false
  966. if err = s.RestartXrayService(); err != nil {
  967. return common.NewErrorf("Imported DB but failed to start Xray: %v", err)
  968. }
  969. return nil
  970. }
  971. // IsValidGeofileName validates that the filename is safe for geofile operations.
  972. // It checks for path traversal attempts and ensures the filename contains only safe characters.
  973. func (s *ServerService) IsValidGeofileName(filename string) bool {
  974. if filename == "" {
  975. return false
  976. }
  977. // Check for path traversal attempts
  978. if strings.Contains(filename, "..") {
  979. return false
  980. }
  981. // Check for path separators (both forward and backward slash)
  982. if strings.ContainsAny(filename, `/\`) {
  983. return false
  984. }
  985. // Check for absolute path indicators
  986. if filepath.IsAbs(filename) {
  987. return false
  988. }
  989. // Additional security: only allow alphanumeric, dots, underscores, and hyphens
  990. // This is stricter than the general filename regex
  991. validGeofilePattern := `^[a-zA-Z0-9._-]+\.dat$`
  992. matched, _ := regexp.MatchString(validGeofilePattern, filename)
  993. return matched
  994. }
  995. func (s *ServerService) UpdateGeofile(fileName string) error {
  996. type geofileEntry struct {
  997. URL string
  998. FileName string
  999. }
  1000. geofileAllowlist := map[string]geofileEntry{
  1001. "geoip.dat": {"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat", "geoip.dat"},
  1002. "geosite.dat": {"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat", "geosite.dat"},
  1003. "geoip_IR.dat": {"https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat", "geoip_IR.dat"},
  1004. "geosite_IR.dat": {"https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat", "geosite_IR.dat"},
  1005. "geoip_RU.dat": {"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat", "geoip_RU.dat"},
  1006. "geosite_RU.dat": {"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat", "geosite_RU.dat"},
  1007. }
  1008. // Strict allowlist check to avoid writing uncontrolled files
  1009. if fileName != "" {
  1010. if _, ok := geofileAllowlist[fileName]; !ok {
  1011. return common.NewErrorf("Invalid geofile name: %q not in allowlist", fileName)
  1012. }
  1013. }
  1014. downloadFile := func(url, destPath string) error {
  1015. var req *http.Request
  1016. req, err := http.NewRequest("GET", url, nil)
  1017. if err != nil {
  1018. return common.NewErrorf("Failed to create HTTP request for %s: %v", url, err)
  1019. }
  1020. var localFileModTime time.Time
  1021. if fileInfo, err := os.Stat(destPath); err == nil {
  1022. localFileModTime = fileInfo.ModTime()
  1023. if !localFileModTime.IsZero() {
  1024. req.Header.Set("If-Modified-Since", localFileModTime.UTC().Format(http.TimeFormat))
  1025. }
  1026. }
  1027. client := &http.Client{}
  1028. resp, err := client.Do(req)
  1029. if err != nil {
  1030. return common.NewErrorf("Failed to download Geofile from %s: %v", url, err)
  1031. }
  1032. defer resp.Body.Close()
  1033. // Parse Last-Modified header from server
  1034. var serverModTime time.Time
  1035. serverModTimeStr := resp.Header.Get("Last-Modified")
  1036. if serverModTimeStr != "" {
  1037. parsedTime, err := time.Parse(http.TimeFormat, serverModTimeStr)
  1038. if err != nil {
  1039. logger.Warningf("Failed to parse Last-Modified header for %s: %v", url, err)
  1040. } else {
  1041. serverModTime = parsedTime
  1042. }
  1043. }
  1044. // Function to update local file's modification time
  1045. updateFileModTime := func() {
  1046. if !serverModTime.IsZero() {
  1047. if err := os.Chtimes(destPath, serverModTime, serverModTime); err != nil {
  1048. logger.Warningf("Failed to update modification time for %s: %v", destPath, err)
  1049. }
  1050. }
  1051. }
  1052. // Handle 304 Not Modified
  1053. if resp.StatusCode == http.StatusNotModified {
  1054. updateFileModTime()
  1055. return nil
  1056. }
  1057. if resp.StatusCode != http.StatusOK {
  1058. return common.NewErrorf("Failed to download Geofile from %s: received status code %d", url, resp.StatusCode)
  1059. }
  1060. file, err := os.Create(destPath)
  1061. if err != nil {
  1062. return common.NewErrorf("Failed to create Geofile %s: %v", destPath, err)
  1063. }
  1064. defer file.Close()
  1065. _, err = io.Copy(file, resp.Body)
  1066. if err != nil {
  1067. return common.NewErrorf("Failed to save Geofile %s: %v", destPath, err)
  1068. }
  1069. updateFileModTime()
  1070. return nil
  1071. }
  1072. var errorMessages []string
  1073. if fileName == "" {
  1074. // Download all geofiles
  1075. for _, entry := range geofileAllowlist {
  1076. destPath := filepath.Join(config.GetBinFolderPath(), entry.FileName)
  1077. if err := downloadFile(entry.URL, destPath); err != nil {
  1078. errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", entry.FileName, err))
  1079. }
  1080. }
  1081. } else {
  1082. entry := geofileAllowlist[fileName]
  1083. destPath := filepath.Join(config.GetBinFolderPath(), entry.FileName)
  1084. if err := downloadFile(entry.URL, destPath); err != nil {
  1085. errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", entry.FileName, err))
  1086. }
  1087. }
  1088. err := s.RestartXrayService()
  1089. if err != nil {
  1090. errorMessages = append(errorMessages, fmt.Sprintf("Updated Geofile '%s' but Failed to start Xray: %v", fileName, err))
  1091. }
  1092. if len(errorMessages) > 0 {
  1093. return common.NewErrorf("%s", strings.Join(errorMessages, "\r\n"))
  1094. }
  1095. return nil
  1096. }
  1097. func (s *ServerService) GetNewX25519Cert() (any, error) {
  1098. // Run the command
  1099. cmd := exec.Command(xray.GetBinaryPath(), "x25519")
  1100. var out bytes.Buffer
  1101. cmd.Stdout = &out
  1102. err := cmd.Run()
  1103. if err != nil {
  1104. return nil, err
  1105. }
  1106. lines := strings.Split(out.String(), "\n")
  1107. privateKeyLine := strings.Split(lines[0], ":")
  1108. publicKeyLine := strings.Split(lines[1], ":")
  1109. privateKey := strings.TrimSpace(privateKeyLine[1])
  1110. publicKey := strings.TrimSpace(publicKeyLine[1])
  1111. keyPair := map[string]any{
  1112. "privateKey": privateKey,
  1113. "publicKey": publicKey,
  1114. }
  1115. return keyPair, nil
  1116. }
  1117. func (s *ServerService) GetNewmldsa65() (any, error) {
  1118. // Run the command
  1119. cmd := exec.Command(xray.GetBinaryPath(), "mldsa65")
  1120. var out bytes.Buffer
  1121. cmd.Stdout = &out
  1122. err := cmd.Run()
  1123. if err != nil {
  1124. return nil, err
  1125. }
  1126. lines := strings.Split(out.String(), "\n")
  1127. SeedLine := strings.Split(lines[0], ":")
  1128. VerifyLine := strings.Split(lines[1], ":")
  1129. seed := strings.TrimSpace(SeedLine[1])
  1130. verify := strings.TrimSpace(VerifyLine[1])
  1131. keyPair := map[string]any{
  1132. "seed": seed,
  1133. "verify": verify,
  1134. }
  1135. return keyPair, nil
  1136. }
  1137. func (s *ServerService) GetNewEchCert(sni string) (any, error) {
  1138. // Run the command
  1139. cmd := exec.Command(xray.GetBinaryPath(), "tls", "ech", "--serverName", sni)
  1140. var out bytes.Buffer
  1141. cmd.Stdout = &out
  1142. err := cmd.Run()
  1143. if err != nil {
  1144. return nil, err
  1145. }
  1146. lines := strings.Split(out.String(), "\n")
  1147. if len(lines) < 4 {
  1148. return nil, common.NewError("invalid ech cert")
  1149. }
  1150. configList := lines[1]
  1151. serverKeys := lines[3]
  1152. return map[string]any{
  1153. "echServerKeys": serverKeys,
  1154. "echConfigList": configList,
  1155. }, nil
  1156. }
  1157. func (s *ServerService) GetNewVlessEnc() (any, error) {
  1158. cmd := exec.Command(xray.GetBinaryPath(), "vlessenc")
  1159. var out bytes.Buffer
  1160. cmd.Stdout = &out
  1161. if err := cmd.Run(); err != nil {
  1162. return nil, err
  1163. }
  1164. return map[string]any{
  1165. "auths": parseVlessEncAuths(out.String()),
  1166. }, nil
  1167. }
  1168. func parseVlessEncAuths(output string) []map[string]string {
  1169. lines := strings.Split(output, "\n")
  1170. var auths []map[string]string
  1171. var current map[string]string
  1172. for _, line := range lines {
  1173. line = strings.TrimSpace(line)
  1174. if strings.HasPrefix(line, "Authentication:") {
  1175. if current != nil {
  1176. auths = append(auths, current)
  1177. }
  1178. label := strings.TrimSpace(strings.TrimPrefix(line, "Authentication:"))
  1179. current = map[string]string{
  1180. "id": vlessEncAuthID(label),
  1181. "label": label,
  1182. }
  1183. } else if strings.HasPrefix(line, `"decryption"`) || strings.HasPrefix(line, `"encryption"`) {
  1184. parts := strings.SplitN(line, ":", 2)
  1185. if len(parts) == 2 && current != nil {
  1186. key := strings.Trim(parts[0], `" `)
  1187. val := strings.TrimSpace(parts[1])
  1188. val = strings.TrimSuffix(val, ",")
  1189. val = strings.Trim(val, `" `)
  1190. current[key] = val
  1191. }
  1192. }
  1193. }
  1194. if current != nil {
  1195. auths = append(auths, current)
  1196. }
  1197. return auths
  1198. }
  1199. func vlessEncAuthID(label string) string {
  1200. normalized := strings.NewReplacer("-", "", "_", "", " ", "").Replace(strings.ToLower(label))
  1201. switch {
  1202. case strings.Contains(normalized, "mlkem768"):
  1203. return "mlkem768"
  1204. case strings.Contains(normalized, "x25519"):
  1205. return "x25519"
  1206. default:
  1207. return normalized
  1208. }
  1209. }
  1210. func (s *ServerService) GetNewUUID() (map[string]string, error) {
  1211. newUUID, err := uuid.NewRandom()
  1212. if err != nil {
  1213. return nil, fmt.Errorf("failed to generate UUID: %w", err)
  1214. }
  1215. return map[string]string{
  1216. "uuid": newUUID.String(),
  1217. }, nil
  1218. }
  1219. func (s *ServerService) GetNewmlkem768() (any, error) {
  1220. // Run the command
  1221. cmd := exec.Command(xray.GetBinaryPath(), "mlkem768")
  1222. var out bytes.Buffer
  1223. cmd.Stdout = &out
  1224. err := cmd.Run()
  1225. if err != nil {
  1226. return nil, err
  1227. }
  1228. lines := strings.Split(out.String(), "\n")
  1229. SeedLine := strings.Split(lines[0], ":")
  1230. ClientLine := strings.Split(lines[1], ":")
  1231. seed := strings.TrimSpace(SeedLine[1])
  1232. client := strings.TrimSpace(ClientLine[1])
  1233. keyPair := map[string]any{
  1234. "seed": seed,
  1235. "client": client,
  1236. }
  1237. return keyPair, nil
  1238. }