1
0

server.go 54 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945
  1. package service
  2. import (
  3. "archive/zip"
  4. "bufio"
  5. "bytes"
  6. "context"
  7. "crypto/sha256"
  8. "crypto/x509"
  9. "encoding/hex"
  10. "encoding/json"
  11. "encoding/pem"
  12. "fmt"
  13. "io"
  14. "mime/multipart"
  15. "net/http"
  16. "net/url"
  17. "os"
  18. "os/exec"
  19. "path/filepath"
  20. "regexp"
  21. "runtime"
  22. "slices"
  23. "strconv"
  24. "strings"
  25. "sync"
  26. "time"
  27. "github.com/mhsanaei/3x-ui/v3/internal/config"
  28. "github.com/mhsanaei/3x-ui/v3/internal/database"
  29. "github.com/mhsanaei/3x-ui/v3/internal/logger"
  30. "github.com/mhsanaei/3x-ui/v3/internal/util/common"
  31. "github.com/mhsanaei/3x-ui/v3/internal/util/sys"
  32. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  33. "github.com/google/uuid"
  34. "github.com/shirou/gopsutil/v4/cpu"
  35. "github.com/shirou/gopsutil/v4/disk"
  36. "github.com/shirou/gopsutil/v4/host"
  37. "github.com/shirou/gopsutil/v4/load"
  38. "github.com/shirou/gopsutil/v4/mem"
  39. "github.com/shirou/gopsutil/v4/net"
  40. )
  41. // ProcessState represents the current state of a system process.
  42. type ProcessState string
  43. // Process state constants
  44. const (
  45. Running ProcessState = "running" // Process is running normally
  46. Stop ProcessState = "stop" // Process is stopped
  47. Error ProcessState = "error" // Process is in error state
  48. )
  49. // Status represents comprehensive system and application status information.
  50. // It includes CPU, memory, disk, network statistics, and Xray process status.
  51. type Status struct {
  52. T time.Time `json:"-"`
  53. Cpu float64 `json:"cpu"`
  54. CpuCores int `json:"cpuCores"`
  55. LogicalPro int `json:"logicalPro"`
  56. CpuSpeedMhz float64 `json:"cpuSpeedMhz"`
  57. Mem struct {
  58. Current uint64 `json:"current"`
  59. Total uint64 `json:"total"`
  60. } `json:"mem"`
  61. Swap struct {
  62. Current uint64 `json:"current"`
  63. Total uint64 `json:"total"`
  64. } `json:"swap"`
  65. Disk struct {
  66. Current uint64 `json:"current"`
  67. Total uint64 `json:"total"`
  68. } `json:"disk"`
  69. DiskIO struct {
  70. Read uint64 `json:"read"`
  71. Write uint64 `json:"write"`
  72. } `json:"diskIO"`
  73. DiskTraffic struct {
  74. Read uint64 `json:"read"`
  75. Write uint64 `json:"write"`
  76. } `json:"diskTraffic"`
  77. Xray struct {
  78. State ProcessState `json:"state"`
  79. ErrorMsg string `json:"errorMsg"`
  80. Version string `json:"version"`
  81. } `json:"xray"`
  82. PanelVersion string `json:"panelVersion"`
  83. PanelGuid string `json:"panelGuid"`
  84. Uptime uint64 `json:"uptime"`
  85. Loads []float64 `json:"loads"`
  86. TcpCount int `json:"tcpCount"`
  87. UdpCount int `json:"udpCount"`
  88. NetIO struct {
  89. Up uint64 `json:"up"`
  90. Down uint64 `json:"down"`
  91. PktUp uint64 `json:"pktUp"`
  92. PktDown uint64 `json:"pktDown"`
  93. } `json:"netIO"`
  94. NetTraffic struct {
  95. Sent uint64 `json:"sent"`
  96. Recv uint64 `json:"recv"`
  97. PktSent uint64 `json:"pktSent"`
  98. PktRecv uint64 `json:"pktRecv"`
  99. } `json:"netTraffic"`
  100. PublicIP struct {
  101. IPv4 string `json:"ipv4"`
  102. IPv6 string `json:"ipv6"`
  103. } `json:"publicIP"`
  104. AppStats struct {
  105. Threads uint32 `json:"threads"`
  106. Mem uint64 `json:"mem"`
  107. Uptime uint64 `json:"uptime"`
  108. } `json:"appStats"`
  109. }
  110. // Release represents information about a software release from GitHub.
  111. type Release struct {
  112. TagName string `json:"tag_name"` // The tag name of the release
  113. }
  114. // ServerService provides business logic for server monitoring and management.
  115. // It handles system status collection, IP detection, and application statistics.
  116. type ServerService struct {
  117. xrayService XrayService
  118. inboundService InboundService
  119. settingService SettingService
  120. cachedIPv4 string
  121. cachedIPv6 string
  122. noIPv6 bool
  123. mu sync.Mutex
  124. lastCPUTimes cpu.TimesStat
  125. hasLastCPUSample bool
  126. hasNativeCPUSample bool
  127. emaCPU float64
  128. cachedCpuSpeedMhz float64
  129. lastCpuInfoAttempt time.Time
  130. lastStatusMu sync.RWMutex
  131. lastStatus *Status
  132. versionsCacheMu sync.Mutex
  133. versionsCache *cachedXrayVersions
  134. }
  135. type cachedXrayVersions struct {
  136. versions []string
  137. fetchedAt time.Time
  138. }
  139. // xrayVersionsCacheTTL bounds how often /getXrayVersion hits GitHub. The list
  140. // is purely informational (rendered in the "switch Xray version" picker) so a
  141. // quarter-hour staleness window is fine and saves the API budget.
  142. const xrayVersionsCacheTTL = 15 * time.Minute
  143. // allowedHistoryBuckets is the bucket-second whitelist for time-series
  144. // aggregation endpoints (server + node metrics). Restricting it prevents
  145. // callers from triggering arbitrary aggregation work and keeps the
  146. // frontend's bucket selector self-documenting.
  147. var allowedHistoryBuckets = map[int]bool{
  148. 2: true, // Real-time view
  149. 30: true, // 30s intervals
  150. 60: true, // 1m intervals
  151. 120: true, // 2m intervals
  152. 180: true, // 3m intervals
  153. 300: true, // 5m intervals
  154. 720: true, // 12m intervals
  155. 1440: true, // 24m intervals
  156. 2880: true, // 48m intervals
  157. }
  158. // IsAllowedHistoryBucket reports whether a bucket-seconds value is in the
  159. // whitelist used by /server/history, /server/cpuHistory, /server/xrayMetricsHistory,
  160. // /server/xrayObservatoryHistory, and /nodes/history.
  161. func IsAllowedHistoryBucket(bucketSeconds int) bool {
  162. return allowedHistoryBuckets[bucketSeconds]
  163. }
  164. // LastStatus returns the most recent Status snapshot collected by
  165. // RefreshStatus. Safe for concurrent readers.
  166. func (s *ServerService) LastStatus() *Status {
  167. s.lastStatusMu.RLock()
  168. defer s.lastStatusMu.RUnlock()
  169. return s.lastStatus
  170. }
  171. // RefreshStatus collects a new system snapshot, stores it as LastStatus, and
  172. // appends it to the system-metrics time series. Returns the new snapshot (may
  173. // be nil if collection failed). Called by the background ticker; the caller is
  174. // responsible for any side effects (websocket broadcast, xray metrics sample).
  175. func (s *ServerService) RefreshStatus() *Status {
  176. next := s.GetStatus(s.LastStatus())
  177. if next == nil {
  178. return nil
  179. }
  180. s.lastStatusMu.Lock()
  181. s.lastStatus = next
  182. s.lastStatusMu.Unlock()
  183. s.AppendStatusSample(time.Now(), next)
  184. return next
  185. }
  186. // GetXrayVersionsCached wraps GetXrayVersions with a TTL cache. On fetch
  187. // failure we serve the last successful list (if any) so the UI doesn't go
  188. // blank during a GitHub API hiccup; if there's no cache at all the underlying
  189. // error is surfaced.
  190. func (s *ServerService) GetXrayVersionsCached() ([]string, error) {
  191. s.versionsCacheMu.Lock()
  192. cache := s.versionsCache
  193. s.versionsCacheMu.Unlock()
  194. if cache != nil && time.Since(cache.fetchedAt) <= xrayVersionsCacheTTL {
  195. return cache.versions, nil
  196. }
  197. versions, err := s.GetXrayVersions()
  198. if err != nil {
  199. if cache != nil {
  200. logger.Warning("GetXrayVersionsCached: serving stale list:", err)
  201. return cache.versions, nil
  202. }
  203. return nil, err
  204. }
  205. s.versionsCacheMu.Lock()
  206. s.versionsCache = &cachedXrayVersions{versions: versions, fetchedAt: time.Now()}
  207. s.versionsCacheMu.Unlock()
  208. return versions, nil
  209. }
  210. // GetDefaultLogOutboundTags scans the default Xray config for freedom and
  211. // blackhole outbound tags so /getXrayLogs can colour-code log lines without
  212. // the controller re-doing the JSON walk. Falls back to the historical
  213. // "direct"/"blocked" defaults when the config can't be read.
  214. func (s *ServerService) GetDefaultLogOutboundTags() (freedoms, blackholes []string) {
  215. config, err := s.settingService.GetDefaultXrayConfig()
  216. if err == nil && config != nil {
  217. if cfgMap, ok := config.(map[string]any); ok {
  218. if outbounds, ok := cfgMap["outbounds"].([]any); ok {
  219. for _, outbound := range outbounds {
  220. obMap, ok := outbound.(map[string]any)
  221. if !ok {
  222. continue
  223. }
  224. tag, _ := obMap["tag"].(string)
  225. if tag == "" {
  226. continue
  227. }
  228. switch obMap["protocol"] {
  229. case "freedom":
  230. freedoms = append(freedoms, tag)
  231. case "blackhole":
  232. blackholes = append(blackholes, tag)
  233. }
  234. }
  235. }
  236. }
  237. }
  238. if len(freedoms) == 0 {
  239. freedoms = []string{"direct"}
  240. }
  241. if len(blackholes) == 0 {
  242. blackholes = []string{"blocked"}
  243. }
  244. return freedoms, blackholes
  245. }
  246. // AggregateCpuHistory returns up to maxPoints averaged buckets of size bucketSeconds.
  247. // Kept for back-compat with the original /panel/api/server/cpuHistory/:bucket route;
  248. // the response key is "cpu" (not "v") so legacy consumers parse unchanged.
  249. func (s *ServerService) AggregateCpuHistory(bucketSeconds int, maxPoints int) []map[string]any {
  250. out := systemMetrics.aggregate("cpu", bucketSeconds, maxPoints)
  251. for _, p := range out {
  252. p["cpu"] = p["v"]
  253. delete(p, "v")
  254. }
  255. return out
  256. }
  257. // AggregateSystemMetric returns up to maxPoints averaged buckets for any
  258. // known system metric (see SystemMetricKeys). Output points have keys
  259. // {"t": unixSec, "v": value}; the caller decides how to format the value.
  260. func (s *ServerService) AggregateSystemMetric(metric string, bucketSeconds int, maxPoints int) []map[string]any {
  261. return systemMetrics.aggregate(metric, bucketSeconds, maxPoints)
  262. }
  263. type LogEntry struct {
  264. DateTime time.Time
  265. FromAddress string
  266. ToAddress string
  267. Inbound string
  268. Outbound string
  269. Email string
  270. Event int
  271. }
  272. func getPublicIP(url string) string {
  273. client := &http.Client{
  274. Timeout: 3 * time.Second,
  275. }
  276. resp, err := client.Get(url)
  277. if err != nil {
  278. return "N/A"
  279. }
  280. defer resp.Body.Close()
  281. // Don't retry if access is blocked or region-restricted
  282. if resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusUnavailableForLegalReasons {
  283. return "N/A"
  284. }
  285. if resp.StatusCode != http.StatusOK {
  286. return "N/A"
  287. }
  288. ip, err := io.ReadAll(resp.Body)
  289. if err != nil {
  290. return "N/A"
  291. }
  292. ipString := strings.TrimSpace(string(ip))
  293. if ipString == "" {
  294. return "N/A"
  295. }
  296. return ipString
  297. }
  298. func (s *ServerService) GetStatus(lastStatus *Status) *Status {
  299. now := time.Now()
  300. status := &Status{
  301. T: now,
  302. }
  303. // CPU stats
  304. util, err := s.sampleCPUUtilization()
  305. if err != nil {
  306. logger.Warning("get cpu percent failed:", err)
  307. } else {
  308. status.Cpu = util
  309. }
  310. status.CpuCores, err = cpu.Counts(false)
  311. if err != nil {
  312. logger.Warning("get cpu cores count failed:", err)
  313. }
  314. status.LogicalPro = runtime.NumCPU()
  315. if status.CpuSpeedMhz = s.cachedCpuSpeedMhz; s.cachedCpuSpeedMhz == 0 && time.Since(s.lastCpuInfoAttempt) > 5*time.Minute {
  316. s.lastCpuInfoAttempt = time.Now()
  317. done := make(chan struct{})
  318. go func() {
  319. defer close(done)
  320. cpuInfos, err := cpu.Info()
  321. if err != nil {
  322. logger.Warning("get cpu info failed:", err)
  323. return
  324. }
  325. if len(cpuInfos) > 0 {
  326. s.cachedCpuSpeedMhz = cpuInfos[0].Mhz
  327. status.CpuSpeedMhz = s.cachedCpuSpeedMhz
  328. } else {
  329. logger.Warning("could not find cpu info")
  330. }
  331. }()
  332. select {
  333. case <-done:
  334. case <-time.After(1500 * time.Millisecond):
  335. logger.Warning("cpu info query timed out; will retry later")
  336. }
  337. } else if s.cachedCpuSpeedMhz != 0 {
  338. status.CpuSpeedMhz = s.cachedCpuSpeedMhz
  339. }
  340. // Uptime
  341. upTime, err := host.Uptime()
  342. if err != nil {
  343. logger.Warning("get uptime failed:", err)
  344. } else {
  345. status.Uptime = upTime
  346. }
  347. // Memory stats
  348. memInfo, err := mem.VirtualMemory()
  349. if err != nil {
  350. logger.Warning("get virtual memory failed:", err)
  351. } else {
  352. status.Mem.Current = memInfo.Used
  353. status.Mem.Total = memInfo.Total
  354. }
  355. swapInfo, err := mem.SwapMemory()
  356. if err != nil {
  357. logger.Warning("get swap memory failed:", err)
  358. } else {
  359. status.Swap.Current = swapInfo.Used
  360. status.Swap.Total = swapInfo.Total
  361. }
  362. // Disk stats
  363. diskInfo, err := disk.Usage("/")
  364. if err != nil {
  365. logger.Warning("get disk usage failed:", err)
  366. } else {
  367. status.Disk.Current = diskInfo.Used
  368. status.Disk.Total = diskInfo.Total
  369. }
  370. diskIOStats, err := disk.IOCounters()
  371. if err != nil {
  372. logger.Warning("get disk io counters failed:", err)
  373. } else {
  374. var totalRead, totalWrite uint64
  375. for _, counter := range diskIOStats {
  376. totalRead += counter.ReadBytes
  377. totalWrite += counter.WriteBytes
  378. }
  379. status.DiskTraffic.Read = totalRead
  380. status.DiskTraffic.Write = totalWrite
  381. if lastStatus != nil {
  382. duration := now.Sub(lastStatus.T)
  383. seconds := float64(duration) / float64(time.Second)
  384. if seconds > 0 && status.DiskTraffic.Read >= lastStatus.DiskTraffic.Read {
  385. status.DiskIO.Read = uint64(float64(status.DiskTraffic.Read-lastStatus.DiskTraffic.Read) / seconds)
  386. }
  387. if seconds > 0 && status.DiskTraffic.Write >= lastStatus.DiskTraffic.Write {
  388. status.DiskIO.Write = uint64(float64(status.DiskTraffic.Write-lastStatus.DiskTraffic.Write) / seconds)
  389. }
  390. }
  391. }
  392. // Load averages
  393. avgState, err := load.Avg()
  394. if err != nil {
  395. logger.Warning("get load avg failed:", err)
  396. } else {
  397. status.Loads = []float64{avgState.Load1, avgState.Load5, avgState.Load15}
  398. }
  399. // Network stats
  400. ioStats, err := net.IOCounters(true)
  401. if err != nil {
  402. logger.Warning("get io counters failed:", err)
  403. } else {
  404. var totalSent, totalRecv, totalPktSent, totalPktRecv uint64
  405. for _, iface := range ioStats {
  406. name := strings.ToLower(iface.Name)
  407. if isVirtualInterface(name) {
  408. continue
  409. }
  410. totalSent += iface.BytesSent
  411. totalRecv += iface.BytesRecv
  412. totalPktSent += iface.PacketsSent
  413. totalPktRecv += iface.PacketsRecv
  414. }
  415. status.NetTraffic.Sent = totalSent
  416. status.NetTraffic.Recv = totalRecv
  417. status.NetTraffic.PktSent = totalPktSent
  418. status.NetTraffic.PktRecv = totalPktRecv
  419. if lastStatus != nil {
  420. duration := now.Sub(lastStatus.T)
  421. seconds := float64(duration) / float64(time.Second)
  422. up := uint64(float64(status.NetTraffic.Sent-lastStatus.NetTraffic.Sent) / seconds)
  423. down := uint64(float64(status.NetTraffic.Recv-lastStatus.NetTraffic.Recv) / seconds)
  424. status.NetIO.Up = up
  425. status.NetIO.Down = down
  426. if seconds > 0 && status.NetTraffic.PktSent >= lastStatus.NetTraffic.PktSent {
  427. status.NetIO.PktUp = uint64(float64(status.NetTraffic.PktSent-lastStatus.NetTraffic.PktSent) / seconds)
  428. }
  429. if seconds > 0 && status.NetTraffic.PktRecv >= lastStatus.NetTraffic.PktRecv {
  430. status.NetIO.PktDown = uint64(float64(status.NetTraffic.PktRecv-lastStatus.NetTraffic.PktRecv) / seconds)
  431. }
  432. }
  433. }
  434. // TCP/UDP connections
  435. status.TcpCount, err = sys.GetTCPCount()
  436. if err != nil {
  437. logger.Warning("get tcp connections failed:", err)
  438. }
  439. status.UdpCount, err = sys.GetUDPCount()
  440. if err != nil {
  441. logger.Warning("get udp connections failed:", err)
  442. }
  443. // IP fetching with caching
  444. showIp4ServiceLists := []string{
  445. "https://api4.ipify.org",
  446. "https://ipv4.icanhazip.com",
  447. "https://v4.api.ipinfo.io/ip",
  448. "https://ipv4.myexternalip.com/raw",
  449. "https://4.ident.me",
  450. "https://check-host.net/ip",
  451. }
  452. showIp6ServiceLists := []string{
  453. "https://api6.ipify.org",
  454. "https://ipv6.icanhazip.com",
  455. "https://v6.api.ipinfo.io/ip",
  456. "https://ipv6.myexternalip.com/raw",
  457. "https://6.ident.me",
  458. }
  459. if s.cachedIPv4 == "" {
  460. for _, ip4Service := range showIp4ServiceLists {
  461. s.cachedIPv4 = getPublicIP(ip4Service)
  462. if s.cachedIPv4 != "N/A" {
  463. break
  464. }
  465. }
  466. }
  467. if s.cachedIPv6 == "" && !s.noIPv6 {
  468. for _, ip6Service := range showIp6ServiceLists {
  469. s.cachedIPv6 = getPublicIP(ip6Service)
  470. if s.cachedIPv6 != "N/A" {
  471. break
  472. }
  473. }
  474. }
  475. if s.cachedIPv6 == "N/A" {
  476. s.noIPv6 = true
  477. }
  478. status.PublicIP.IPv4 = s.cachedIPv4
  479. status.PublicIP.IPv6 = s.cachedIPv6
  480. // Xray status
  481. if s.xrayService.IsXrayRunning() {
  482. status.Xray.State = Running
  483. status.Xray.ErrorMsg = ""
  484. } else {
  485. err := s.xrayService.GetXrayErr()
  486. if err != nil {
  487. status.Xray.State = Error
  488. } else {
  489. status.Xray.State = Stop
  490. }
  491. status.Xray.ErrorMsg = s.xrayService.GetXrayResult()
  492. }
  493. status.Xray.Version = s.xrayService.GetXrayVersion()
  494. status.PanelVersion = config.GetVersion()
  495. if guid, err := s.settingService.GetPanelGuid(); err == nil {
  496. status.PanelGuid = guid
  497. }
  498. // Application stats
  499. var rtm runtime.MemStats
  500. runtime.ReadMemStats(&rtm)
  501. status.AppStats.Mem = rtm.Sys
  502. status.AppStats.Threads = uint32(runtime.NumGoroutine())
  503. if p != nil && p.IsRunning() {
  504. status.AppStats.Uptime = p.GetUptime()
  505. } else {
  506. status.AppStats.Uptime = 0
  507. }
  508. return status
  509. }
  510. // AppendCpuSample is preserved for callers that only have the CPU number.
  511. // New callers should prefer AppendStatusSample which writes the full set.
  512. func (s *ServerService) AppendCpuSample(t time.Time, v float64) {
  513. systemMetrics.append("cpu", t, v)
  514. }
  515. // AppendStatusSample writes one tick of every metric we keep — CPU, memory
  516. // percent, network throughput (bytes/s), online client count, and the three
  517. // load averages. Called by RefreshStatus on the same @2s cadence as
  518. // AppendCpuSample, so all series stay aligned.
  519. func (s *ServerService) AppendStatusSample(t time.Time, status *Status) {
  520. if status == nil {
  521. return
  522. }
  523. systemMetrics.append("cpu", t, status.Cpu)
  524. if status.Mem.Total > 0 {
  525. systemMetrics.append("mem", t, float64(status.Mem.Current)*100.0/float64(status.Mem.Total))
  526. }
  527. if status.Swap.Total > 0 {
  528. systemMetrics.append("swap", t, float64(status.Swap.Current)*100.0/float64(status.Swap.Total))
  529. } else {
  530. systemMetrics.append("swap", t, 0)
  531. }
  532. systemMetrics.append("netUp", t, float64(status.NetIO.Up))
  533. systemMetrics.append("netDown", t, float64(status.NetIO.Down))
  534. systemMetrics.append("diskRead", t, float64(status.DiskIO.Read))
  535. systemMetrics.append("diskWrite", t, float64(status.DiskIO.Write))
  536. if status.Disk.Total > 0 {
  537. systemMetrics.append("diskUsage", t, float64(status.Disk.Current)*100.0/float64(status.Disk.Total))
  538. }
  539. systemMetrics.append("pktUp", t, float64(status.NetIO.PktUp))
  540. systemMetrics.append("pktDown", t, float64(status.NetIO.PktDown))
  541. systemMetrics.append("tcpCount", t, float64(status.TcpCount))
  542. systemMetrics.append("udpCount", t, float64(status.UdpCount))
  543. online := 0
  544. if p != nil && p.IsRunning() {
  545. online = len(p.GetOnlineClients())
  546. }
  547. systemMetrics.append("online", t, float64(online))
  548. if len(status.Loads) >= 3 {
  549. systemMetrics.append("load1", t, status.Loads[0])
  550. systemMetrics.append("load5", t, status.Loads[1])
  551. systemMetrics.append("load15", t, status.Loads[2])
  552. }
  553. }
  554. func (s *ServerService) sampleCPUUtilization() (float64, error) {
  555. // Try native platform-specific CPU implementation first (Windows, Linux, macOS)
  556. if pct, err := sys.CPUPercentRaw(); err == nil {
  557. s.mu.Lock()
  558. // First call to native method returns 0 (initializes baseline)
  559. if !s.hasNativeCPUSample {
  560. s.hasNativeCPUSample = true
  561. s.mu.Unlock()
  562. return 0, nil
  563. }
  564. // Smooth with EMA
  565. const alpha = 0.3
  566. if s.emaCPU == 0 {
  567. s.emaCPU = pct
  568. } else {
  569. s.emaCPU = alpha*pct + (1-alpha)*s.emaCPU
  570. }
  571. val := s.emaCPU
  572. s.mu.Unlock()
  573. return val, nil
  574. }
  575. // If native call fails, fall back to gopsutil times
  576. // Read aggregate CPU times (all CPUs combined)
  577. times, err := cpu.Times(false)
  578. if err != nil {
  579. return 0, err
  580. }
  581. if len(times) == 0 {
  582. return 0, fmt.Errorf("no cpu times available")
  583. }
  584. cur := times[0]
  585. s.mu.Lock()
  586. defer s.mu.Unlock()
  587. // If this is the first sample, initialize and return current EMA (0 by default)
  588. if !s.hasLastCPUSample {
  589. s.lastCPUTimes = cur
  590. s.hasLastCPUSample = true
  591. return s.emaCPU, nil
  592. }
  593. // Compute busy and total deltas
  594. // Note: Guest and GuestNice times are already included in User and Nice respectively,
  595. // so we exclude them to avoid double-counting (Linux kernel accounting)
  596. idleDelta := cur.Idle - s.lastCPUTimes.Idle
  597. busyDelta := (cur.User - s.lastCPUTimes.User) +
  598. (cur.System - s.lastCPUTimes.System) +
  599. (cur.Nice - s.lastCPUTimes.Nice) +
  600. (cur.Iowait - s.lastCPUTimes.Iowait) +
  601. (cur.Irq - s.lastCPUTimes.Irq) +
  602. (cur.Softirq - s.lastCPUTimes.Softirq) +
  603. (cur.Steal - s.lastCPUTimes.Steal)
  604. totalDelta := busyDelta + idleDelta
  605. // Update last sample for next time
  606. s.lastCPUTimes = cur
  607. // Guard against division by zero or negative deltas (e.g., counter resets)
  608. if totalDelta <= 0 {
  609. return s.emaCPU, nil
  610. }
  611. raw := 100.0 * (busyDelta / totalDelta)
  612. if raw < 0 {
  613. raw = 0
  614. }
  615. if raw > 100 {
  616. raw = 100
  617. }
  618. // Exponential moving average to smooth spikes
  619. const alpha = 0.3 // smoothing factor (0<alpha<=1). Higher = more responsive, lower = smoother
  620. if s.emaCPU == 0 {
  621. // Initialize EMA with the first real reading to avoid long warm-up from zero
  622. s.emaCPU = raw
  623. } else {
  624. s.emaCPU = alpha*raw + (1-alpha)*s.emaCPU
  625. }
  626. return s.emaCPU, nil
  627. }
  628. const (
  629. maxXrayArchiveBytes = 200 << 20
  630. maxXrayBinaryBytes = 200 << 20
  631. // maxXrayDigestBytes caps the .dgst checksum sidecar read; it is a few
  632. // hundred bytes in practice.
  633. maxXrayDigestBytes = 64 << 10
  634. )
  635. func (s *ServerService) GetXrayVersions() ([]string, error) {
  636. const (
  637. XrayURL = "https://api.github.com/repos/XTLS/Xray-core/releases"
  638. bufferSize = 8192
  639. )
  640. resp, err := s.settingService.NewProxiedHTTPClient(10 * time.Second).Get(XrayURL)
  641. if err != nil {
  642. return nil, err
  643. }
  644. defer resp.Body.Close()
  645. // Check HTTP status code - GitHub API returns object instead of array on error
  646. if resp.StatusCode != http.StatusOK {
  647. bodyBytes, _ := io.ReadAll(resp.Body)
  648. var errorResponse struct {
  649. Message string `json:"message"`
  650. }
  651. if json.Unmarshal(bodyBytes, &errorResponse) == nil && errorResponse.Message != "" {
  652. return nil, fmt.Errorf("GitHub API error: %s", errorResponse.Message)
  653. }
  654. return nil, fmt.Errorf("GitHub API returned status %d: %s", resp.StatusCode, resp.Status)
  655. }
  656. buffer := bytes.NewBuffer(make([]byte, bufferSize))
  657. buffer.Reset()
  658. if _, err := buffer.ReadFrom(resp.Body); err != nil {
  659. return nil, err
  660. }
  661. var releases []Release
  662. if err := json.Unmarshal(buffer.Bytes(), &releases); err != nil {
  663. return nil, err
  664. }
  665. var versions []string
  666. for _, release := range releases {
  667. tagVersion := strings.TrimPrefix(release.TagName, "v")
  668. tagParts := strings.Split(tagVersion, ".")
  669. if len(tagParts) != 3 {
  670. continue
  671. }
  672. major, err1 := strconv.Atoi(tagParts[0])
  673. minor, err2 := strconv.Atoi(tagParts[1])
  674. patch, err3 := strconv.Atoi(tagParts[2])
  675. if err1 != nil || err2 != nil || err3 != nil {
  676. continue
  677. }
  678. if major > 26 || (major == 26 && minor > 4) || (major == 26 && minor == 4 && patch >= 25) {
  679. versions = append(versions, release.TagName)
  680. }
  681. }
  682. return versions, nil
  683. }
  684. func (s *ServerService) StopXrayService() error {
  685. err := s.xrayService.StopXray()
  686. if err != nil {
  687. logger.Error("stop xray failed:", err)
  688. return err
  689. }
  690. return nil
  691. }
  692. func (s *ServerService) RestartXrayService() error {
  693. err := s.xrayService.RestartXray(true)
  694. if err != nil {
  695. logger.Error("start xray failed:", err)
  696. return err
  697. }
  698. return nil
  699. }
  700. func (s *ServerService) downloadXRay(version string) (string, error) {
  701. osName := runtime.GOOS
  702. arch := runtime.GOARCH
  703. switch osName {
  704. case "darwin":
  705. osName = "macos"
  706. case "windows":
  707. osName = "windows"
  708. }
  709. switch arch {
  710. case "amd64":
  711. arch = "64"
  712. case "arm64":
  713. arch = "arm64-v8a"
  714. case "armv7":
  715. arch = "arm32-v7a"
  716. case "armv6":
  717. arch = "arm32-v6"
  718. case "armv5":
  719. arch = "arm32-v5"
  720. case "386":
  721. arch = "32"
  722. case "s390x":
  723. arch = "s390x"
  724. }
  725. fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)
  726. url := fmt.Sprintf("https://github.com/XTLS/Xray-core/releases/download/%s/%s", version, fileName)
  727. client := s.settingService.NewProxiedHTTPClient(60 * time.Second)
  728. resp, err := client.Get(url)
  729. if err != nil {
  730. return "", err
  731. }
  732. defer resp.Body.Close()
  733. if resp.StatusCode != http.StatusOK {
  734. return "", fmt.Errorf("download xray: unexpected HTTP %d", resp.StatusCode)
  735. }
  736. if resp.ContentLength > maxXrayArchiveBytes {
  737. return "", fmt.Errorf("download xray: archive exceeds %d bytes", maxXrayArchiveBytes)
  738. }
  739. file, err := os.CreateTemp("", "xray-*.zip")
  740. if err != nil {
  741. return "", err
  742. }
  743. path := file.Name()
  744. ok := false
  745. defer func() {
  746. _ = file.Close()
  747. if !ok {
  748. _ = os.Remove(path)
  749. }
  750. }()
  751. n, err := io.Copy(file, io.LimitReader(resp.Body, maxXrayArchiveBytes+1))
  752. if err != nil {
  753. return "", err
  754. }
  755. if n > maxXrayArchiveBytes {
  756. return "", fmt.Errorf("download xray: archive exceeds %d bytes", maxXrayArchiveBytes)
  757. }
  758. // Verify the archive against the SHA2-256 published in the release's .dgst
  759. // sidecar before installing it. TLS protects the transport, not the artifact;
  760. // a corrupted or tampered asset must not be installed and run as xray.
  761. want, err := s.fetchXrayDigestSHA256(client, url+".dgst")
  762. if err != nil {
  763. return "", err
  764. }
  765. if _, err := file.Seek(0, io.SeekStart); err != nil {
  766. return "", err
  767. }
  768. hasher := sha256.New()
  769. if _, err := io.Copy(hasher, file); err != nil {
  770. return "", err
  771. }
  772. if got := hex.EncodeToString(hasher.Sum(nil)); !strings.EqualFold(got, want) {
  773. // User-facing warning: the archive's SHA-256 does not match the official
  774. // release checksum, so the download is corrupted or has been tampered
  775. // with. Abort the install so a bad binary is never run, and tell the user
  776. // to retry/re-download rather than proceed with a mismatched image.
  777. return "", fmt.Errorf("Xray update aborted: the downloaded archive does not match the official SHA-256 checksum, so the image is corrupted or differs from the official release. Please exit and re-download the official image, then try again (expected %s, got %s)", want, got)
  778. }
  779. ok = true
  780. return path, nil
  781. }
  782. // fetchXrayDigestSHA256 downloads the .dgst sidecar XTLS publishes next to each
  783. // release asset and returns the SHA2-256 hex digest it lists.
  784. func (s *ServerService) fetchXrayDigestSHA256(client *http.Client, dgstURL string) (string, error) {
  785. resp, err := client.Get(dgstURL)
  786. if err != nil {
  787. return "", fmt.Errorf("download xray checksum: %w", err)
  788. }
  789. defer resp.Body.Close()
  790. if resp.StatusCode != http.StatusOK {
  791. return "", fmt.Errorf("download xray checksum: unexpected HTTP %d", resp.StatusCode)
  792. }
  793. raw, err := io.ReadAll(io.LimitReader(resp.Body, maxXrayDigestBytes))
  794. if err != nil {
  795. return "", fmt.Errorf("download xray checksum: %w", err)
  796. }
  797. return parseXrayDigestSHA256(raw)
  798. }
  799. // parseXrayDigestSHA256 extracts the lowercase SHA2-256 hex from an XTLS .dgst
  800. // file, whose lines are "ALGO= <hex>" (the relevant one being "SHA2-256= ...").
  801. func parseXrayDigestSHA256(dgst []byte) (string, error) {
  802. for _, line := range strings.Split(string(dgst), "\n") {
  803. rest, ok := strings.CutPrefix(strings.TrimSpace(line), "SHA2-256=")
  804. if !ok {
  805. continue
  806. }
  807. h := strings.ToLower(strings.TrimSpace(rest))
  808. if len(h) != 64 {
  809. return "", fmt.Errorf("xray checksum: malformed SHA2-256 entry in digest")
  810. }
  811. return h, nil
  812. }
  813. return "", fmt.Errorf("xray checksum: no SHA2-256 entry in digest")
  814. }
  815. func (s *ServerService) UpdateXray(version string) error {
  816. versions, err := s.GetXrayVersions()
  817. if err != nil {
  818. return err
  819. }
  820. if !slices.Contains(versions, version) {
  821. return fmt.Errorf("xray version %q is not in the fetched release list", version)
  822. }
  823. // 1. Stop xray before doing anything
  824. if err := s.StopXrayService(); err != nil {
  825. logger.Warning("failed to stop xray before update:", err)
  826. }
  827. // 2. Download the zip
  828. zipFileName, err := s.downloadXRay(version)
  829. if err != nil {
  830. return err
  831. }
  832. defer os.Remove(zipFileName)
  833. zipFile, err := os.Open(zipFileName)
  834. if err != nil {
  835. return err
  836. }
  837. defer zipFile.Close()
  838. stat, err := zipFile.Stat()
  839. if err != nil {
  840. return err
  841. }
  842. reader, err := zip.NewReader(zipFile, stat.Size())
  843. if err != nil {
  844. return err
  845. }
  846. // 3. Helper to extract files
  847. copyZipFile := func(zipName string, fileName string) error {
  848. zipFile, err := reader.Open(zipName)
  849. if err != nil {
  850. return err
  851. }
  852. defer zipFile.Close()
  853. if err := os.MkdirAll(filepath.Dir(fileName), 0755); err != nil {
  854. return err
  855. }
  856. tmpFile, err := os.CreateTemp(filepath.Dir(fileName), ".xray-*")
  857. if err != nil {
  858. return err
  859. }
  860. tmpPath := tmpFile.Name()
  861. ok := false
  862. defer func() {
  863. _ = tmpFile.Close()
  864. if !ok {
  865. _ = os.Remove(tmpPath)
  866. }
  867. }()
  868. n, err := io.Copy(tmpFile, io.LimitReader(zipFile, maxXrayBinaryBytes+1))
  869. if err != nil {
  870. return err
  871. }
  872. if n > maxXrayBinaryBytes {
  873. return fmt.Errorf("xray binary exceeds %d bytes", maxXrayBinaryBytes)
  874. }
  875. if err := tmpFile.Chmod(0755); err != nil {
  876. return err
  877. }
  878. if err := tmpFile.Close(); err != nil {
  879. return err
  880. }
  881. if runtime.GOOS == "windows" {
  882. _ = os.Remove(fileName)
  883. }
  884. if err := os.Rename(tmpPath, fileName); err != nil {
  885. return err
  886. }
  887. ok = true
  888. return nil
  889. }
  890. // 4. Extract correct binary
  891. if runtime.GOOS == "windows" {
  892. targetBinary := filepath.Join(config.GetBinFolderPath(), "xray-windows-amd64.exe")
  893. err = copyZipFile("xray.exe", targetBinary)
  894. } else {
  895. err = copyZipFile("xray", xray.GetBinaryPath())
  896. }
  897. if err != nil {
  898. return err
  899. }
  900. // 5. Restart xray
  901. if err := s.xrayService.RestartXray(true); err != nil {
  902. logger.Error("start xray failed:", err)
  903. return err
  904. }
  905. return nil
  906. }
  907. func (s *ServerService) GetLogs(count string, level string, syslog string) []string {
  908. c, _ := strconv.Atoi(count)
  909. var lines []string
  910. if syslog == "true" {
  911. // Check if running on Windows - journalctl is not available
  912. if runtime.GOOS == "windows" {
  913. return []string{"Syslog is not supported on Windows. Please use application logs instead by unchecking the 'Syslog' option."}
  914. }
  915. // Validate and sanitize count parameter
  916. countInt, err := strconv.Atoi(count)
  917. if err != nil || countInt < 1 || countInt > 10000 {
  918. return []string{"Invalid count parameter - must be a number between 1 and 10000"}
  919. }
  920. // Validate level parameter - only allow valid syslog levels
  921. validLevels := map[string]bool{
  922. "0": true, "emerg": true,
  923. "1": true, "alert": true,
  924. "2": true, "crit": true,
  925. "3": true, "err": true,
  926. "4": true, "warning": true,
  927. "5": true, "notice": true,
  928. "6": true, "info": true,
  929. "7": true, "debug": true,
  930. }
  931. if !validLevels[level] {
  932. return []string{"Invalid level parameter - must be a valid syslog level"}
  933. }
  934. // Use hardcoded command with validated parameters
  935. cmd := exec.Command("journalctl", "-u", "x-ui", "--no-pager", "-n", strconv.Itoa(countInt), "-p", level)
  936. var out bytes.Buffer
  937. cmd.Stdout = &out
  938. err = cmd.Run()
  939. if err != nil {
  940. return []string{"Failed to run journalctl command! Make sure systemd is available and x-ui service is registered."}
  941. }
  942. lines = strings.Split(out.String(), "\n")
  943. } else {
  944. lines = logger.GetLogs(c, level)
  945. }
  946. return lines
  947. }
  948. func (s *ServerService) GetXrayLogs(
  949. count string,
  950. filter string,
  951. showDirect string,
  952. showBlocked string,
  953. showProxy string,
  954. freedoms []string,
  955. blackholes []string) []LogEntry {
  956. const (
  957. Direct = iota
  958. Blocked
  959. Proxied
  960. )
  961. countInt, _ := strconv.Atoi(count)
  962. var entries []LogEntry
  963. pathToAccessLog, err := xray.GetAccessLogPath()
  964. if err != nil {
  965. return nil
  966. }
  967. file, err := os.Open(pathToAccessLog)
  968. if err != nil {
  969. return nil
  970. }
  971. defer file.Close()
  972. scanner := bufio.NewScanner(file)
  973. for scanner.Scan() {
  974. line := strings.TrimSpace(scanner.Text())
  975. if line == "" || strings.Contains(line, "api -> api") {
  976. //skipping empty lines and api calls
  977. continue
  978. }
  979. if filter != "" && !strings.Contains(line, filter) {
  980. //applying filter if it's not empty
  981. continue
  982. }
  983. var entry LogEntry
  984. parts := strings.Fields(line)
  985. for i, part := range parts {
  986. if i == 0 {
  987. dateTime, err := time.ParseInLocation("2006/01/02 15:04:05.999999", parts[0]+" "+parts[1], time.Local)
  988. if err != nil {
  989. continue
  990. }
  991. entry.DateTime = dateTime.UTC()
  992. }
  993. if part == "from" {
  994. entry.FromAddress = strings.TrimLeft(parts[i+1], "/")
  995. } else if part == "accepted" {
  996. entry.ToAddress = strings.TrimLeft(parts[i+1], "/")
  997. } else if strings.HasPrefix(part, "[") {
  998. entry.Inbound = part[1:]
  999. } else if strings.HasSuffix(part, "]") {
  1000. entry.Outbound = part[:len(part)-1]
  1001. } else if part == "email:" {
  1002. entry.Email = parts[i+1]
  1003. }
  1004. }
  1005. if logEntryContains(line, freedoms) {
  1006. if showDirect == "false" {
  1007. continue
  1008. }
  1009. entry.Event = Direct
  1010. } else if logEntryContains(line, blackholes) {
  1011. if showBlocked == "false" {
  1012. continue
  1013. }
  1014. entry.Event = Blocked
  1015. } else {
  1016. if showProxy == "false" {
  1017. continue
  1018. }
  1019. entry.Event = Proxied
  1020. }
  1021. entries = append(entries, entry)
  1022. }
  1023. if err := scanner.Err(); err != nil {
  1024. return nil
  1025. }
  1026. if len(entries) > countInt {
  1027. entries = entries[len(entries)-countInt:]
  1028. }
  1029. return entries
  1030. }
  1031. // isVirtualInterface returns true for loopback and virtual/tunnel interfaces
  1032. // that should be excluded from network traffic statistics.
  1033. func isVirtualInterface(name string) bool {
  1034. // Exact matches
  1035. if name == "lo" || name == "lo0" {
  1036. return true
  1037. }
  1038. // Prefix matches for virtual/tunnel interfaces
  1039. virtualPrefixes := []string{
  1040. "loopback",
  1041. "docker",
  1042. "br-",
  1043. "veth",
  1044. "virbr",
  1045. "tun",
  1046. "tap",
  1047. "wg",
  1048. "tailscale",
  1049. "zt",
  1050. }
  1051. for _, prefix := range virtualPrefixes {
  1052. if strings.HasPrefix(name, prefix) {
  1053. return true
  1054. }
  1055. }
  1056. return false
  1057. }
  1058. func logEntryContains(line string, suffixes []string) bool {
  1059. for _, sfx := range suffixes {
  1060. if strings.Contains(line, sfx+"]") {
  1061. return true
  1062. }
  1063. }
  1064. return false
  1065. }
  1066. func (s *ServerService) GetConfigJson() (any, error) {
  1067. config, err := s.xrayService.GetXrayConfig()
  1068. if err != nil {
  1069. return nil, err
  1070. }
  1071. contents, err := json.MarshalIndent(config, "", " ")
  1072. if err != nil {
  1073. return nil, err
  1074. }
  1075. var jsonData any
  1076. err = json.Unmarshal(contents, &jsonData)
  1077. if err != nil {
  1078. return nil, err
  1079. }
  1080. return jsonData, nil
  1081. }
  1082. func (s *ServerService) GetDb() ([]byte, error) {
  1083. if database.IsPostgres() {
  1084. return s.exportPostgresDB()
  1085. }
  1086. // Update by manually trigger a checkpoint operation
  1087. err := database.Checkpoint()
  1088. if err != nil {
  1089. return nil, err
  1090. }
  1091. // Open the file for reading
  1092. file, err := os.Open(config.GetDBPath())
  1093. if err != nil {
  1094. return nil, err
  1095. }
  1096. defer file.Close()
  1097. // Read the file contents
  1098. fileContents, err := io.ReadAll(file)
  1099. if err != nil {
  1100. return nil, err
  1101. }
  1102. return fileContents, nil
  1103. }
  1104. // GetMigration produces a cross-engine migration file plus its filename: on a
  1105. // SQLite panel it returns a portable .dump (SQL text), and on a PostgreSQL panel
  1106. // it returns a .db SQLite database built from the live data. Either output can
  1107. // then seed a panel running on the other backend.
  1108. func (s *ServerService) GetMigration() ([]byte, string, error) {
  1109. if database.IsPostgres() {
  1110. tmp, err := os.CreateTemp("", "x-ui-migration-*.db")
  1111. if err != nil {
  1112. return nil, "", err
  1113. }
  1114. tmpPath := tmp.Name()
  1115. tmp.Close()
  1116. defer os.Remove(tmpPath)
  1117. if err := database.ExportPostgresToSQLite(config.GetDBDSN(), tmpPath); err != nil {
  1118. return nil, "", err
  1119. }
  1120. data, err := os.ReadFile(tmpPath)
  1121. if err != nil {
  1122. return nil, "", err
  1123. }
  1124. return data, "x-ui.db", nil
  1125. }
  1126. // SQLite panel: checkpoint so the .db reflects the latest writes, then dump.
  1127. if err := database.Checkpoint(); err != nil {
  1128. return nil, "", err
  1129. }
  1130. data, err := database.DumpSQLiteToBytes(config.GetDBPath())
  1131. if err != nil {
  1132. return nil, "", err
  1133. }
  1134. return data, "x-ui.dump", nil
  1135. }
  1136. func (s *ServerService) ImportDB(file multipart.File) error {
  1137. if database.IsPostgres() {
  1138. return s.importPostgresDB(file)
  1139. }
  1140. // Check if the file is a SQLite database
  1141. isValidDb, err := database.IsSQLiteDB(file)
  1142. if err != nil {
  1143. return common.NewErrorf("Error checking db file format: %v", err)
  1144. }
  1145. if !isValidDb {
  1146. return common.NewError("Invalid db file format")
  1147. }
  1148. // Reset the file reader to the beginning
  1149. _, err = file.Seek(0, 0)
  1150. if err != nil {
  1151. return common.NewErrorf("Error resetting file reader: %v", err)
  1152. }
  1153. // Save the file as a temporary file
  1154. tempPath := fmt.Sprintf("%s.temp", config.GetDBPath())
  1155. // Remove the existing temporary file (if any)
  1156. if _, err := os.Stat(tempPath); err == nil {
  1157. if errRemove := os.Remove(tempPath); errRemove != nil {
  1158. return common.NewErrorf("Error removing existing temporary db file: %v", errRemove)
  1159. }
  1160. }
  1161. // Create the temporary file
  1162. tempFile, err := os.Create(tempPath)
  1163. if err != nil {
  1164. return common.NewErrorf("Error creating temporary db file: %v", err)
  1165. }
  1166. // Robust deferred cleanup for the temporary file
  1167. defer func() {
  1168. if tempFile != nil {
  1169. if cerr := tempFile.Close(); cerr != nil {
  1170. logger.Warningf("Warning: failed to close temp file: %v", cerr)
  1171. }
  1172. }
  1173. if _, err := os.Stat(tempPath); err == nil {
  1174. if rerr := os.Remove(tempPath); rerr != nil {
  1175. logger.Warningf("Warning: failed to remove temp file: %v", rerr)
  1176. }
  1177. }
  1178. }()
  1179. // Save uploaded file to temporary file
  1180. if _, err = io.Copy(tempFile, file); err != nil {
  1181. return common.NewErrorf("Error saving db: %v", err)
  1182. }
  1183. // Close temp file before opening via sqlite
  1184. if err = tempFile.Close(); err != nil {
  1185. return common.NewErrorf("Error closing temporary db file: %v", err)
  1186. }
  1187. tempFile = nil
  1188. // Validate integrity (no migrations / side effects)
  1189. if err = database.ValidateSQLiteDB(tempPath); err != nil {
  1190. return common.NewErrorf("Invalid or corrupt db file: %v", err)
  1191. }
  1192. xrayStopped := true
  1193. defer func() {
  1194. if xrayStopped {
  1195. if errR := s.RestartXrayService(); errR != nil {
  1196. logger.Warningf("Failed to restart Xray after DB import error: %v", errR)
  1197. }
  1198. }
  1199. }()
  1200. if errStop := s.StopXrayService(); errStop != nil {
  1201. logger.Warningf("Failed to stop Xray before DB import: %v", errStop)
  1202. }
  1203. if errClose := database.CloseDB(); errClose != nil {
  1204. logger.Warningf("Failed to close existing DB before replacement: %v", errClose)
  1205. }
  1206. // Backup the current database for fallback
  1207. fallbackPath := fmt.Sprintf("%s.backup", config.GetDBPath())
  1208. // Remove the existing fallback file (if any)
  1209. if _, err := os.Stat(fallbackPath); err == nil {
  1210. if errRemove := os.Remove(fallbackPath); errRemove != nil {
  1211. return common.NewErrorf("Error removing existing fallback db file: %v", errRemove)
  1212. }
  1213. }
  1214. // Move the current database to the fallback location
  1215. if err = os.Rename(config.GetDBPath(), fallbackPath); err != nil {
  1216. return common.NewErrorf("Error backing up current db file: %v", err)
  1217. }
  1218. // Defer fallback cleanup ONLY if everything goes well
  1219. defer func() {
  1220. if _, err := os.Stat(fallbackPath); err == nil {
  1221. if rerr := os.Remove(fallbackPath); rerr != nil {
  1222. logger.Warningf("Warning: failed to remove fallback file: %v", rerr)
  1223. }
  1224. }
  1225. }()
  1226. // Move temp to DB path
  1227. if err = os.Rename(tempPath, config.GetDBPath()); err != nil {
  1228. // Restore from fallback
  1229. if errRename := os.Rename(fallbackPath, config.GetDBPath()); errRename != nil {
  1230. return common.NewErrorf("Error moving db file and restoring fallback: %v", errRename)
  1231. }
  1232. return common.NewErrorf("Error moving db file: %v", err)
  1233. }
  1234. // Open & migrate new DB
  1235. if err = database.InitDB(config.GetDBPath()); err != nil {
  1236. if errRename := os.Rename(fallbackPath, config.GetDBPath()); errRename != nil {
  1237. return common.NewErrorf("Error migrating db and restoring fallback: %v", errRename)
  1238. }
  1239. return common.NewErrorf("Error migrating db: %v", err)
  1240. }
  1241. s.inboundService.MigrateDB()
  1242. xrayStopped = false
  1243. if err = s.RestartXrayService(); err != nil {
  1244. return common.NewErrorf("Imported DB but failed to start Xray: %v", err)
  1245. }
  1246. return nil
  1247. }
  1248. // pgConnEnv turns the configured PostgreSQL DSN into the PG* environment used by
  1249. // pg_dump/pg_restore, keeping the password out of the process argument list.
  1250. func pgConnEnv(dsn string) (env []string, dbname string, err error) {
  1251. u, err := url.Parse(strings.TrimSpace(dsn))
  1252. if err != nil {
  1253. return nil, "", err
  1254. }
  1255. if u.Scheme != "postgres" && u.Scheme != "postgresql" {
  1256. return nil, "", common.NewErrorf("unsupported DSN scheme %q", u.Scheme)
  1257. }
  1258. dbname = strings.TrimPrefix(u.Path, "/")
  1259. if dbname == "" {
  1260. return nil, "", common.NewError("PostgreSQL DSN is missing a database name")
  1261. }
  1262. host := u.Hostname()
  1263. if host == "" {
  1264. host = "127.0.0.1"
  1265. }
  1266. port := u.Port()
  1267. if port == "" {
  1268. port = "5432"
  1269. }
  1270. env = append(os.Environ(), "PGHOST="+host, "PGPORT="+port, "PGDATABASE="+dbname)
  1271. if user := u.User.Username(); user != "" {
  1272. env = append(env, "PGUSER="+user)
  1273. }
  1274. if pass, ok := u.User.Password(); ok {
  1275. env = append(env, "PGPASSWORD="+pass)
  1276. }
  1277. if sslmode := u.Query().Get("sslmode"); sslmode != "" {
  1278. env = append(env, "PGSSLMODE="+sslmode)
  1279. }
  1280. return env, dbname, nil
  1281. }
  1282. func (s *ServerService) exportPostgresDB() ([]byte, error) {
  1283. bin, err := exec.LookPath("pg_dump")
  1284. if err != nil {
  1285. return nil, common.NewError("pg_dump not found on the server; install the postgresql-client package to back up a PostgreSQL database")
  1286. }
  1287. env, dbname, err := pgConnEnv(config.GetDBDSN())
  1288. if err != nil {
  1289. return nil, common.NewErrorf("invalid PostgreSQL DSN: %v", err)
  1290. }
  1291. cmd := exec.Command(bin, "--format=custom", "--no-owner", "--no-privileges", "--dbname", dbname)
  1292. cmd.Env = env
  1293. var out, stderr bytes.Buffer
  1294. cmd.Stdout = &out
  1295. cmd.Stderr = &stderr
  1296. if err := cmd.Run(); err != nil {
  1297. return nil, common.NewErrorf("pg_dump failed: %v: %s", err, strings.TrimSpace(stderr.String()))
  1298. }
  1299. return out.Bytes(), nil
  1300. }
  1301. func (s *ServerService) importPostgresDB(file multipart.File) error {
  1302. header := make([]byte, 5)
  1303. if _, err := file.ReadAt(header, 0); err != nil {
  1304. return common.NewErrorf("Error reading dump file: %v", err)
  1305. }
  1306. if string(header) != "PGDMP" {
  1307. return common.NewError("Invalid file: expected a PostgreSQL custom-format dump (.dump) created by this panel's Back Up")
  1308. }
  1309. if _, err := file.Seek(0, 0); err != nil {
  1310. return common.NewErrorf("Error resetting file reader: %v", err)
  1311. }
  1312. bin, err := exec.LookPath("pg_restore")
  1313. if err != nil {
  1314. return common.NewError("pg_restore not found on the server; install the postgresql-client package to restore a PostgreSQL database")
  1315. }
  1316. env, dbname, err := pgConnEnv(config.GetDBDSN())
  1317. if err != nil {
  1318. return common.NewErrorf("invalid PostgreSQL DSN: %v", err)
  1319. }
  1320. tempFile, err := os.CreateTemp("", "x-ui-pg-restore-*.dump")
  1321. if err != nil {
  1322. return common.NewErrorf("Error creating temporary dump file: %v", err)
  1323. }
  1324. tempPath := tempFile.Name()
  1325. defer os.Remove(tempPath)
  1326. if _, err := io.Copy(tempFile, file); err != nil {
  1327. tempFile.Close()
  1328. return common.NewErrorf("Error saving dump: %v", err)
  1329. }
  1330. if err := tempFile.Close(); err != nil {
  1331. return common.NewErrorf("Error closing temporary dump file: %v", err)
  1332. }
  1333. xrayStopped := true
  1334. defer func() {
  1335. if xrayStopped {
  1336. if errR := s.RestartXrayService(); errR != nil {
  1337. logger.Warningf("Failed to restart Xray after DB restore error: %v", errR)
  1338. }
  1339. }
  1340. }()
  1341. if errStop := s.StopXrayService(); errStop != nil {
  1342. logger.Warningf("Failed to stop Xray before DB restore: %v", errStop)
  1343. }
  1344. if errClose := database.CloseDB(); errClose != nil {
  1345. logger.Warningf("Failed to close existing DB before restore: %v", errClose)
  1346. }
  1347. cmd := exec.Command(bin,
  1348. "--clean", "--if-exists", "--no-owner", "--no-privileges",
  1349. "--single-transaction", "--dbname", dbname, tempPath,
  1350. )
  1351. cmd.Env = env
  1352. var stderr bytes.Buffer
  1353. cmd.Stderr = &stderr
  1354. runErr := cmd.Run()
  1355. if errInit := database.InitDB(config.GetDBPath()); errInit != nil {
  1356. return common.NewErrorf("Restore finished but reopening the database failed: %v", errInit)
  1357. }
  1358. s.inboundService.MigrateDB()
  1359. if runErr != nil {
  1360. return common.NewErrorf("pg_restore failed (database left unchanged): %v: %s", runErr, strings.TrimSpace(stderr.String()))
  1361. }
  1362. xrayStopped = false
  1363. if err := s.RestartXrayService(); err != nil {
  1364. return common.NewErrorf("Restored DB but failed to start Xray: %v", err)
  1365. }
  1366. return nil
  1367. }
  1368. // IsValidGeofileName validates that the filename is safe for geofile operations.
  1369. // It checks for path traversal attempts and ensures the filename contains only safe characters.
  1370. func (s *ServerService) IsValidGeofileName(filename string) bool {
  1371. if filename == "" {
  1372. return false
  1373. }
  1374. // Check for path traversal attempts
  1375. if strings.Contains(filename, "..") {
  1376. return false
  1377. }
  1378. // Check for path separators (both forward and backward slash)
  1379. if strings.ContainsAny(filename, `/\`) {
  1380. return false
  1381. }
  1382. // Check for absolute path indicators
  1383. if filepath.IsAbs(filename) {
  1384. return false
  1385. }
  1386. // Additional security: only allow alphanumeric, dots, underscores, and hyphens
  1387. // This is stricter than the general filename regex
  1388. validGeofilePattern := `^[a-zA-Z0-9._-]+\.dat$`
  1389. matched, _ := regexp.MatchString(validGeofilePattern, filename)
  1390. return matched
  1391. }
  1392. func (s *ServerService) UpdateGeofile(fileName string) error {
  1393. type geofileEntry struct {
  1394. URL string
  1395. FileName string
  1396. }
  1397. geofileAllowlist := map[string]geofileEntry{
  1398. "geoip.dat": {"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat", "geoip.dat"},
  1399. "geosite.dat": {"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat", "geosite.dat"},
  1400. "geoip_IR.dat": {"https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat", "geoip_IR.dat"},
  1401. "geosite_IR.dat": {"https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat", "geosite_IR.dat"},
  1402. "geoip_RU.dat": {"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat", "geoip_RU.dat"},
  1403. "geosite_RU.dat": {"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat", "geosite_RU.dat"},
  1404. }
  1405. // Strict allowlist check to avoid writing uncontrolled files
  1406. if fileName != "" {
  1407. if _, ok := geofileAllowlist[fileName]; !ok {
  1408. return common.NewErrorf("Invalid geofile name: %q not in allowlist", fileName)
  1409. }
  1410. }
  1411. client := s.settingService.NewProxiedHTTPClient(0)
  1412. downloadFile := func(url, destPath string) error {
  1413. var req *http.Request
  1414. req, err := http.NewRequest("GET", url, nil)
  1415. if err != nil {
  1416. return common.NewErrorf("Failed to create HTTP request for %s: %v", url, err)
  1417. }
  1418. var localFileModTime time.Time
  1419. if fileInfo, err := os.Stat(destPath); err == nil {
  1420. localFileModTime = fileInfo.ModTime()
  1421. if !localFileModTime.IsZero() {
  1422. req.Header.Set("If-Modified-Since", localFileModTime.UTC().Format(http.TimeFormat))
  1423. }
  1424. }
  1425. resp, err := client.Do(req)
  1426. if err != nil {
  1427. return common.NewErrorf("Failed to download Geofile from %s: %v", url, err)
  1428. }
  1429. defer resp.Body.Close()
  1430. // Parse Last-Modified header from server
  1431. var serverModTime time.Time
  1432. serverModTimeStr := resp.Header.Get("Last-Modified")
  1433. if serverModTimeStr != "" {
  1434. parsedTime, err := time.Parse(http.TimeFormat, serverModTimeStr)
  1435. if err != nil {
  1436. logger.Warningf("Failed to parse Last-Modified header for %s: %v", url, err)
  1437. } else {
  1438. serverModTime = parsedTime
  1439. }
  1440. }
  1441. // Function to update local file's modification time
  1442. updateFileModTime := func() {
  1443. if !serverModTime.IsZero() {
  1444. if err := os.Chtimes(destPath, serverModTime, serverModTime); err != nil {
  1445. logger.Warningf("Failed to update modification time for %s: %v", destPath, err)
  1446. }
  1447. }
  1448. }
  1449. // Handle 304 Not Modified
  1450. if resp.StatusCode == http.StatusNotModified {
  1451. updateFileModTime()
  1452. return nil
  1453. }
  1454. if resp.StatusCode != http.StatusOK {
  1455. return common.NewErrorf("Failed to download Geofile from %s: received status code %d", url, resp.StatusCode)
  1456. }
  1457. file, err := os.Create(destPath)
  1458. if err != nil {
  1459. return common.NewErrorf("Failed to create Geofile %s: %v", destPath, err)
  1460. }
  1461. defer file.Close()
  1462. _, err = io.Copy(file, resp.Body)
  1463. if err != nil {
  1464. return common.NewErrorf("Failed to save Geofile %s: %v", destPath, err)
  1465. }
  1466. updateFileModTime()
  1467. return nil
  1468. }
  1469. var errorMessages []string
  1470. if fileName == "" {
  1471. // Download all geofiles
  1472. for _, entry := range geofileAllowlist {
  1473. destPath := filepath.Join(config.GetBinFolderPath(), entry.FileName)
  1474. if err := downloadFile(entry.URL, destPath); err != nil {
  1475. errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", entry.FileName, err))
  1476. }
  1477. }
  1478. } else {
  1479. entry := geofileAllowlist[fileName]
  1480. destPath := filepath.Join(config.GetBinFolderPath(), entry.FileName)
  1481. if err := downloadFile(entry.URL, destPath); err != nil {
  1482. errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", entry.FileName, err))
  1483. }
  1484. }
  1485. err := s.RestartXrayService()
  1486. if err != nil {
  1487. errorMessages = append(errorMessages, fmt.Sprintf("Updated Geofile '%s' but Failed to start Xray: %v", fileName, err))
  1488. }
  1489. if len(errorMessages) > 0 {
  1490. return common.NewErrorf("%s", strings.Join(errorMessages, "\r\n"))
  1491. }
  1492. return nil
  1493. }
  1494. func (s *ServerService) GetNewX25519Cert() (any, error) {
  1495. // Run the command
  1496. cmd := exec.Command(xray.GetBinaryPath(), "x25519")
  1497. var out bytes.Buffer
  1498. cmd.Stdout = &out
  1499. err := cmd.Run()
  1500. if err != nil {
  1501. return nil, err
  1502. }
  1503. lines := strings.Split(out.String(), "\n")
  1504. privateKeyLine := strings.Split(lines[0], ":")
  1505. publicKeyLine := strings.Split(lines[1], ":")
  1506. privateKey := strings.TrimSpace(privateKeyLine[1])
  1507. publicKey := strings.TrimSpace(publicKeyLine[1])
  1508. keyPair := map[string]any{
  1509. "privateKey": privateKey,
  1510. "publicKey": publicKey,
  1511. }
  1512. return keyPair, nil
  1513. }
  1514. func (s *ServerService) GetNewmldsa65() (any, error) {
  1515. // Run the command
  1516. cmd := exec.Command(xray.GetBinaryPath(), "mldsa65")
  1517. var out bytes.Buffer
  1518. cmd.Stdout = &out
  1519. err := cmd.Run()
  1520. if err != nil {
  1521. return nil, err
  1522. }
  1523. lines := strings.Split(out.String(), "\n")
  1524. SeedLine := strings.Split(lines[0], ":")
  1525. VerifyLine := strings.Split(lines[1], ":")
  1526. seed := strings.TrimSpace(SeedLine[1])
  1527. verify := strings.TrimSpace(VerifyLine[1])
  1528. keyPair := map[string]any{
  1529. "seed": seed,
  1530. "verify": verify,
  1531. }
  1532. return keyPair, nil
  1533. }
  1534. // GetCertHash parses a certificate (from a file path or inline PEM/DER content)
  1535. // and returns the hex-encoded SHA-256 over each certificate's raw DER — the
  1536. // value xray-core's pinnedPeerCertSha256 (pcs) expects. Lets the panel fill the
  1537. // pinned-cert field from the inbound's own certificate without the user
  1538. // computing the hash by hand.
  1539. func (s *ServerService) GetCertHash(certFile string, certContent string) ([]string, error) {
  1540. var certBytes []byte
  1541. if path := strings.TrimSpace(certFile); path != "" {
  1542. b, err := os.ReadFile(path)
  1543. if err != nil {
  1544. return nil, err
  1545. }
  1546. certBytes = b
  1547. } else if strings.TrimSpace(certContent) != "" {
  1548. certBytes = []byte(certContent)
  1549. } else {
  1550. return nil, common.NewError("no certificate provided")
  1551. }
  1552. var certs []*x509.Certificate
  1553. if bytes.Contains(certBytes, []byte("BEGIN")) {
  1554. rest := certBytes
  1555. for {
  1556. block, remain := pem.Decode(rest)
  1557. if block == nil {
  1558. break
  1559. }
  1560. cert, err := x509.ParseCertificate(block.Bytes)
  1561. if err != nil {
  1562. return nil, common.NewError("unable to decode certificate: ", err)
  1563. }
  1564. certs = append(certs, cert)
  1565. rest = remain
  1566. }
  1567. } else {
  1568. parsed, err := x509.ParseCertificates(certBytes)
  1569. if err != nil {
  1570. return nil, common.NewError("unable to parse certificates: ", err)
  1571. }
  1572. certs = parsed
  1573. }
  1574. if len(certs) == 0 {
  1575. return nil, common.NewError("no certificates found")
  1576. }
  1577. hashes := make([]string, 0, len(certs))
  1578. for _, cert := range certs {
  1579. sum := sha256.Sum256(cert.Raw)
  1580. hashes = append(hashes, hex.EncodeToString(sum[:]))
  1581. }
  1582. return hashes, nil
  1583. }
  1584. // GetRemoteCertHash runs `xray tls ping <server>` to fetch the live certificate
  1585. // SHA-256 of a remote endpoint — the value to put in pinnedPeerCertSha256 (pcs)
  1586. // when pinning a server whose certificate file you don't hold (a CDN front, a
  1587. // REALITY dest, an external proxy). Returns the unique leaf-certificate hashes.
  1588. func (s *ServerService) GetRemoteCertHash(server string) ([]string, error) {
  1589. server = strings.TrimSpace(server)
  1590. if server == "" {
  1591. return nil, common.NewError("no server provided")
  1592. }
  1593. ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
  1594. defer cancel()
  1595. cmd := exec.CommandContext(ctx, xray.GetBinaryPath(), "tls", "ping", server)
  1596. var out bytes.Buffer
  1597. cmd.Stdout = &out
  1598. cmd.Stderr = &out
  1599. if err := cmd.Run(); err != nil && out.Len() == 0 {
  1600. return nil, err
  1601. }
  1602. hexRe := regexp.MustCompile(`[0-9a-fA-F]{64}`)
  1603. seen := make(map[string]struct{})
  1604. var leaves []string
  1605. for _, line := range strings.Split(out.String(), "\n") {
  1606. if !strings.Contains(line, "leaf SHA256") {
  1607. continue
  1608. }
  1609. hash := strings.ToLower(hexRe.FindString(line))
  1610. if hash == "" {
  1611. continue
  1612. }
  1613. if _, ok := seen[hash]; !ok {
  1614. seen[hash] = struct{}{}
  1615. leaves = append(leaves, hash)
  1616. }
  1617. }
  1618. if len(leaves) == 0 {
  1619. return nil, common.NewError("no certificate hash found for ", server)
  1620. }
  1621. return leaves, nil
  1622. }
  1623. func (s *ServerService) GetNewEchCert(sni string) (any, error) {
  1624. // Run the command
  1625. cmd := exec.Command(xray.GetBinaryPath(), "tls", "ech", "--serverName", sni)
  1626. var out bytes.Buffer
  1627. cmd.Stdout = &out
  1628. err := cmd.Run()
  1629. if err != nil {
  1630. return nil, err
  1631. }
  1632. lines := strings.Split(out.String(), "\n")
  1633. if len(lines) < 4 {
  1634. return nil, common.NewError("invalid ech cert")
  1635. }
  1636. configList := lines[1]
  1637. serverKeys := lines[3]
  1638. return map[string]any{
  1639. "echServerKeys": serverKeys,
  1640. "echConfigList": configList,
  1641. }, nil
  1642. }
  1643. func (s *ServerService) GetNewVlessEnc() (any, error) {
  1644. cmd := exec.Command(xray.GetBinaryPath(), "vlessenc")
  1645. var out bytes.Buffer
  1646. cmd.Stdout = &out
  1647. if err := cmd.Run(); err != nil {
  1648. return nil, err
  1649. }
  1650. return map[string]any{
  1651. "auths": parseVlessEncAuths(out.String()),
  1652. }, nil
  1653. }
  1654. func parseVlessEncAuths(output string) []map[string]string {
  1655. lines := strings.Split(output, "\n")
  1656. var auths []map[string]string
  1657. var current map[string]string
  1658. for _, line := range lines {
  1659. line = strings.TrimSpace(line)
  1660. if strings.HasPrefix(line, "Authentication:") {
  1661. if current != nil {
  1662. auths = append(auths, current)
  1663. }
  1664. label := strings.TrimSpace(strings.TrimPrefix(line, "Authentication:"))
  1665. current = map[string]string{
  1666. "id": vlessEncAuthID(label),
  1667. "label": label,
  1668. }
  1669. } else if strings.HasPrefix(line, `"decryption"`) || strings.HasPrefix(line, `"encryption"`) {
  1670. parts := strings.SplitN(line, ":", 2)
  1671. if len(parts) == 2 && current != nil {
  1672. key := strings.Trim(parts[0], `" `)
  1673. val := strings.TrimSpace(parts[1])
  1674. val = strings.TrimSuffix(val, ",")
  1675. val = strings.Trim(val, `" `)
  1676. current[key] = val
  1677. }
  1678. }
  1679. }
  1680. if current != nil {
  1681. auths = append(auths, current)
  1682. }
  1683. return auths
  1684. }
  1685. func vlessEncAuthID(label string) string {
  1686. normalized := strings.NewReplacer("-", "", "_", "", " ", "").Replace(strings.ToLower(label))
  1687. switch {
  1688. case strings.Contains(normalized, "mlkem768"):
  1689. return "mlkem768"
  1690. case strings.Contains(normalized, "x25519"):
  1691. return "x25519"
  1692. default:
  1693. return normalized
  1694. }
  1695. }
  1696. func (s *ServerService) GetNewUUID() (map[string]string, error) {
  1697. newUUID, err := uuid.NewRandom()
  1698. if err != nil {
  1699. return nil, fmt.Errorf("failed to generate UUID: %w", err)
  1700. }
  1701. return map[string]string{
  1702. "uuid": newUUID.String(),
  1703. }, nil
  1704. }
  1705. func (s *ServerService) GetNewmlkem768() (any, error) {
  1706. // Run the command
  1707. cmd := exec.Command(xray.GetBinaryPath(), "mlkem768")
  1708. var out bytes.Buffer
  1709. cmd.Stdout = &out
  1710. err := cmd.Run()
  1711. if err != nil {
  1712. return nil, err
  1713. }
  1714. lines := strings.Split(out.String(), "\n")
  1715. SeedLine := strings.Split(lines[0], ":")
  1716. ClientLine := strings.Split(lines[1], ":")
  1717. seed := strings.TrimSpace(SeedLine[1])
  1718. client := strings.TrimSpace(ClientLine[1])
  1719. keyPair := map[string]any{
  1720. "seed": seed,
  1721. "client": client,
  1722. }
  1723. return keyPair, nil
  1724. }