server.go 46 KB

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