server.go 44 KB

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