server.go 40 KB

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