server.go 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548
  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. var xrayVersionsClient = &http.Client{Timeout: 10 * time.Second}
  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 := xrayVersionsClient.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. if tagVersion == "26.5.3" {
  596. continue
  597. }
  598. tagParts := strings.Split(tagVersion, ".")
  599. if len(tagParts) != 3 {
  600. continue
  601. }
  602. major, err1 := strconv.Atoi(tagParts[0])
  603. minor, err2 := strconv.Atoi(tagParts[1])
  604. patch, err3 := strconv.Atoi(tagParts[2])
  605. if err1 != nil || err2 != nil || err3 != nil {
  606. continue
  607. }
  608. if major > 26 || (major == 26 && minor > 4) || (major == 26 && minor == 4 && patch >= 25) {
  609. versions = append(versions, release.TagName)
  610. }
  611. }
  612. return versions, nil
  613. }
  614. func (s *ServerService) StopXrayService() error {
  615. err := s.xrayService.StopXray()
  616. if err != nil {
  617. logger.Error("stop xray failed:", err)
  618. return err
  619. }
  620. return nil
  621. }
  622. func (s *ServerService) RestartXrayService() error {
  623. err := s.xrayService.RestartXray(true)
  624. if err != nil {
  625. logger.Error("start xray failed:", err)
  626. return err
  627. }
  628. return nil
  629. }
  630. func (s *ServerService) downloadXRay(version string) (string, error) {
  631. osName := runtime.GOOS
  632. arch := runtime.GOARCH
  633. switch osName {
  634. case "darwin":
  635. osName = "macos"
  636. case "windows":
  637. osName = "windows"
  638. }
  639. switch arch {
  640. case "amd64":
  641. arch = "64"
  642. case "arm64":
  643. arch = "arm64-v8a"
  644. case "armv7":
  645. arch = "arm32-v7a"
  646. case "armv6":
  647. arch = "arm32-v6"
  648. case "armv5":
  649. arch = "arm32-v5"
  650. case "386":
  651. arch = "32"
  652. case "s390x":
  653. arch = "s390x"
  654. }
  655. fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)
  656. url := fmt.Sprintf("https://github.com/XTLS/Xray-core/releases/download/%s/%s", version, fileName)
  657. client := &http.Client{Timeout: 60 * time.Second}
  658. resp, err := client.Get(url)
  659. if err != nil {
  660. return "", err
  661. }
  662. defer resp.Body.Close()
  663. if resp.StatusCode != http.StatusOK {
  664. return "", fmt.Errorf("download xray: unexpected HTTP %d", resp.StatusCode)
  665. }
  666. if resp.ContentLength > maxXrayArchiveBytes {
  667. return "", fmt.Errorf("download xray: archive exceeds %d bytes", maxXrayArchiveBytes)
  668. }
  669. file, err := os.CreateTemp("", "xray-*.zip")
  670. if err != nil {
  671. return "", err
  672. }
  673. path := file.Name()
  674. ok := false
  675. defer func() {
  676. _ = file.Close()
  677. if !ok {
  678. _ = os.Remove(path)
  679. }
  680. }()
  681. n, err := io.Copy(file, io.LimitReader(resp.Body, maxXrayArchiveBytes+1))
  682. if err != nil {
  683. return "", err
  684. }
  685. if n > maxXrayArchiveBytes {
  686. return "", fmt.Errorf("download xray: archive exceeds %d bytes", maxXrayArchiveBytes)
  687. }
  688. ok = true
  689. return path, nil
  690. }
  691. func (s *ServerService) UpdateXray(version string) error {
  692. versions, err := s.GetXrayVersions()
  693. if err != nil {
  694. return err
  695. }
  696. if !slices.Contains(versions, version) {
  697. return fmt.Errorf("xray version %q is not in the fetched release list", version)
  698. }
  699. // 1. Stop xray before doing anything
  700. if err := s.StopXrayService(); err != nil {
  701. logger.Warning("failed to stop xray before update:", err)
  702. }
  703. // 2. Download the zip
  704. zipFileName, err := s.downloadXRay(version)
  705. if err != nil {
  706. return err
  707. }
  708. defer os.Remove(zipFileName)
  709. zipFile, err := os.Open(zipFileName)
  710. if err != nil {
  711. return err
  712. }
  713. defer zipFile.Close()
  714. stat, err := zipFile.Stat()
  715. if err != nil {
  716. return err
  717. }
  718. reader, err := zip.NewReader(zipFile, stat.Size())
  719. if err != nil {
  720. return err
  721. }
  722. // 3. Helper to extract files
  723. copyZipFile := func(zipName string, fileName string) error {
  724. zipFile, err := reader.Open(zipName)
  725. if err != nil {
  726. return err
  727. }
  728. defer zipFile.Close()
  729. if err := os.MkdirAll(filepath.Dir(fileName), 0755); err != nil {
  730. return err
  731. }
  732. tmpFile, err := os.CreateTemp(filepath.Dir(fileName), ".xray-*")
  733. if err != nil {
  734. return err
  735. }
  736. tmpPath := tmpFile.Name()
  737. ok := false
  738. defer func() {
  739. _ = tmpFile.Close()
  740. if !ok {
  741. _ = os.Remove(tmpPath)
  742. }
  743. }()
  744. n, err := io.Copy(tmpFile, io.LimitReader(zipFile, maxXrayBinaryBytes+1))
  745. if err != nil {
  746. return err
  747. }
  748. if n > maxXrayBinaryBytes {
  749. return fmt.Errorf("xray binary exceeds %d bytes", maxXrayBinaryBytes)
  750. }
  751. if err := tmpFile.Chmod(0755); err != nil {
  752. return err
  753. }
  754. if err := tmpFile.Close(); err != nil {
  755. return err
  756. }
  757. if runtime.GOOS == "windows" {
  758. _ = os.Remove(fileName)
  759. }
  760. if err := os.Rename(tmpPath, fileName); err != nil {
  761. return err
  762. }
  763. ok = true
  764. return nil
  765. }
  766. // 4. Extract correct binary
  767. if runtime.GOOS == "windows" {
  768. targetBinary := filepath.Join("bin", "xray-windows-amd64.exe")
  769. err = copyZipFile("xray.exe", targetBinary)
  770. } else {
  771. err = copyZipFile("xray", xray.GetBinaryPath())
  772. }
  773. if err != nil {
  774. return err
  775. }
  776. // 5. Restart xray
  777. if err := s.xrayService.RestartXray(true); err != nil {
  778. logger.Error("start xray failed:", err)
  779. return err
  780. }
  781. return nil
  782. }
  783. func (s *ServerService) GetLogs(count string, level string, syslog string) []string {
  784. c, _ := strconv.Atoi(count)
  785. var lines []string
  786. if syslog == "true" {
  787. // Check if running on Windows - journalctl is not available
  788. if runtime.GOOS == "windows" {
  789. return []string{"Syslog is not supported on Windows. Please use application logs instead by unchecking the 'Syslog' option."}
  790. }
  791. // Validate and sanitize count parameter
  792. countInt, err := strconv.Atoi(count)
  793. if err != nil || countInt < 1 || countInt > 10000 {
  794. return []string{"Invalid count parameter - must be a number between 1 and 10000"}
  795. }
  796. // Validate level parameter - only allow valid syslog levels
  797. validLevels := map[string]bool{
  798. "0": true, "emerg": true,
  799. "1": true, "alert": true,
  800. "2": true, "crit": true,
  801. "3": true, "err": true,
  802. "4": true, "warning": true,
  803. "5": true, "notice": true,
  804. "6": true, "info": true,
  805. "7": true, "debug": true,
  806. }
  807. if !validLevels[level] {
  808. return []string{"Invalid level parameter - must be a valid syslog level"}
  809. }
  810. // Use hardcoded command with validated parameters
  811. cmd := exec.Command("journalctl", "-u", "x-ui", "--no-pager", "-n", strconv.Itoa(countInt), "-p", level)
  812. var out bytes.Buffer
  813. cmd.Stdout = &out
  814. err = cmd.Run()
  815. if err != nil {
  816. return []string{"Failed to run journalctl command! Make sure systemd is available and x-ui service is registered."}
  817. }
  818. lines = strings.Split(out.String(), "\n")
  819. } else {
  820. lines = logger.GetLogs(c, level)
  821. }
  822. return lines
  823. }
  824. func (s *ServerService) GetXrayLogs(
  825. count string,
  826. filter string,
  827. showDirect string,
  828. showBlocked string,
  829. showProxy string,
  830. freedoms []string,
  831. blackholes []string) []LogEntry {
  832. const (
  833. Direct = iota
  834. Blocked
  835. Proxied
  836. )
  837. countInt, _ := strconv.Atoi(count)
  838. var entries []LogEntry
  839. pathToAccessLog, err := xray.GetAccessLogPath()
  840. if err != nil {
  841. return nil
  842. }
  843. file, err := os.Open(pathToAccessLog)
  844. if err != nil {
  845. return nil
  846. }
  847. defer file.Close()
  848. scanner := bufio.NewScanner(file)
  849. for scanner.Scan() {
  850. line := strings.TrimSpace(scanner.Text())
  851. if line == "" || strings.Contains(line, "api -> api") {
  852. //skipping empty lines and api calls
  853. continue
  854. }
  855. if filter != "" && !strings.Contains(line, filter) {
  856. //applying filter if it's not empty
  857. continue
  858. }
  859. var entry LogEntry
  860. parts := strings.Fields(line)
  861. for i, part := range parts {
  862. if i == 0 {
  863. dateTime, err := time.ParseInLocation("2006/01/02 15:04:05.999999", parts[0]+" "+parts[1], time.Local)
  864. if err != nil {
  865. continue
  866. }
  867. entry.DateTime = dateTime.UTC()
  868. }
  869. if part == "from" {
  870. entry.FromAddress = strings.TrimLeft(parts[i+1], "/")
  871. } else if part == "accepted" {
  872. entry.ToAddress = strings.TrimLeft(parts[i+1], "/")
  873. } else if strings.HasPrefix(part, "[") {
  874. entry.Inbound = part[1:]
  875. } else if strings.HasSuffix(part, "]") {
  876. entry.Outbound = part[:len(part)-1]
  877. } else if part == "email:" {
  878. entry.Email = parts[i+1]
  879. }
  880. }
  881. if logEntryContains(line, freedoms) {
  882. if showDirect == "false" {
  883. continue
  884. }
  885. entry.Event = Direct
  886. } else if logEntryContains(line, blackholes) {
  887. if showBlocked == "false" {
  888. continue
  889. }
  890. entry.Event = Blocked
  891. } else {
  892. if showProxy == "false" {
  893. continue
  894. }
  895. entry.Event = Proxied
  896. }
  897. entries = append(entries, entry)
  898. }
  899. if err := scanner.Err(); err != nil {
  900. return nil
  901. }
  902. if len(entries) > countInt {
  903. entries = entries[len(entries)-countInt:]
  904. }
  905. return entries
  906. }
  907. // isVirtualInterface returns true for loopback and virtual/tunnel interfaces
  908. // that should be excluded from network traffic statistics.
  909. func isVirtualInterface(name string) bool {
  910. // Exact matches
  911. if name == "lo" || name == "lo0" {
  912. return true
  913. }
  914. // Prefix matches for virtual/tunnel interfaces
  915. virtualPrefixes := []string{
  916. "loopback",
  917. "docker",
  918. "br-",
  919. "veth",
  920. "virbr",
  921. "tun",
  922. "tap",
  923. "wg",
  924. "tailscale",
  925. "zt",
  926. }
  927. for _, prefix := range virtualPrefixes {
  928. if strings.HasPrefix(name, prefix) {
  929. return true
  930. }
  931. }
  932. return false
  933. }
  934. func logEntryContains(line string, suffixes []string) bool {
  935. for _, sfx := range suffixes {
  936. if strings.Contains(line, sfx+"]") {
  937. return true
  938. }
  939. }
  940. return false
  941. }
  942. func (s *ServerService) GetConfigJson() (any, error) {
  943. config, err := s.xrayService.GetXrayConfig()
  944. if err != nil {
  945. return nil, err
  946. }
  947. contents, err := json.MarshalIndent(config, "", " ")
  948. if err != nil {
  949. return nil, err
  950. }
  951. var jsonData any
  952. err = json.Unmarshal(contents, &jsonData)
  953. if err != nil {
  954. return nil, err
  955. }
  956. return jsonData, nil
  957. }
  958. func (s *ServerService) GetDb() ([]byte, error) {
  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. // Check if the file is a SQLite database
  979. isValidDb, err := database.IsSQLiteDB(file)
  980. if err != nil {
  981. return common.NewErrorf("Error checking db file format: %v", err)
  982. }
  983. if !isValidDb {
  984. return common.NewError("Invalid db file format")
  985. }
  986. // Reset the file reader to the beginning
  987. _, err = file.Seek(0, 0)
  988. if err != nil {
  989. return common.NewErrorf("Error resetting file reader: %v", err)
  990. }
  991. // Save the file as a temporary file
  992. tempPath := fmt.Sprintf("%s.temp", config.GetDBPath())
  993. // Remove the existing temporary file (if any)
  994. if _, err := os.Stat(tempPath); err == nil {
  995. if errRemove := os.Remove(tempPath); errRemove != nil {
  996. return common.NewErrorf("Error removing existing temporary db file: %v", errRemove)
  997. }
  998. }
  999. // Create the temporary file
  1000. tempFile, err := os.Create(tempPath)
  1001. if err != nil {
  1002. return common.NewErrorf("Error creating temporary db file: %v", err)
  1003. }
  1004. // Robust deferred cleanup for the temporary file
  1005. defer func() {
  1006. if tempFile != nil {
  1007. if cerr := tempFile.Close(); cerr != nil {
  1008. logger.Warningf("Warning: failed to close temp file: %v", cerr)
  1009. }
  1010. }
  1011. if _, err := os.Stat(tempPath); err == nil {
  1012. if rerr := os.Remove(tempPath); rerr != nil {
  1013. logger.Warningf("Warning: failed to remove temp file: %v", rerr)
  1014. }
  1015. }
  1016. }()
  1017. // Save uploaded file to temporary file
  1018. if _, err = io.Copy(tempFile, file); err != nil {
  1019. return common.NewErrorf("Error saving db: %v", err)
  1020. }
  1021. // Close temp file before opening via sqlite
  1022. if err = tempFile.Close(); err != nil {
  1023. return common.NewErrorf("Error closing temporary db file: %v", err)
  1024. }
  1025. tempFile = nil
  1026. // Validate integrity (no migrations / side effects)
  1027. if err = database.ValidateSQLiteDB(tempPath); err != nil {
  1028. return common.NewErrorf("Invalid or corrupt db file: %v", err)
  1029. }
  1030. xrayStopped := true
  1031. defer func() {
  1032. if xrayStopped {
  1033. if errR := s.RestartXrayService(); errR != nil {
  1034. logger.Warningf("Failed to restart Xray after DB import error: %v", errR)
  1035. }
  1036. }
  1037. }()
  1038. if errStop := s.StopXrayService(); errStop != nil {
  1039. logger.Warningf("Failed to stop Xray before DB import: %v", errStop)
  1040. }
  1041. if errClose := database.CloseDB(); errClose != nil {
  1042. logger.Warningf("Failed to close existing DB before replacement: %v", errClose)
  1043. }
  1044. // Backup the current database for fallback
  1045. fallbackPath := fmt.Sprintf("%s.backup", config.GetDBPath())
  1046. // Remove the existing fallback file (if any)
  1047. if _, err := os.Stat(fallbackPath); err == nil {
  1048. if errRemove := os.Remove(fallbackPath); errRemove != nil {
  1049. return common.NewErrorf("Error removing existing fallback db file: %v", errRemove)
  1050. }
  1051. }
  1052. // Move the current database to the fallback location
  1053. if err = os.Rename(config.GetDBPath(), fallbackPath); err != nil {
  1054. return common.NewErrorf("Error backing up current db file: %v", err)
  1055. }
  1056. // Defer fallback cleanup ONLY if everything goes well
  1057. defer func() {
  1058. if _, err := os.Stat(fallbackPath); err == nil {
  1059. if rerr := os.Remove(fallbackPath); rerr != nil {
  1060. logger.Warningf("Warning: failed to remove fallback file: %v", rerr)
  1061. }
  1062. }
  1063. }()
  1064. // Move temp to DB path
  1065. if err = os.Rename(tempPath, config.GetDBPath()); err != nil {
  1066. // Restore from fallback
  1067. if errRename := os.Rename(fallbackPath, config.GetDBPath()); errRename != nil {
  1068. return common.NewErrorf("Error moving db file and restoring fallback: %v", errRename)
  1069. }
  1070. return common.NewErrorf("Error moving db file: %v", err)
  1071. }
  1072. // Open & migrate new DB
  1073. if err = database.InitDB(config.GetDBPath()); err != nil {
  1074. if errRename := os.Rename(fallbackPath, config.GetDBPath()); errRename != nil {
  1075. return common.NewErrorf("Error migrating db and restoring fallback: %v", errRename)
  1076. }
  1077. return common.NewErrorf("Error migrating db: %v", err)
  1078. }
  1079. s.inboundService.MigrateDB()
  1080. xrayStopped = false
  1081. if err = s.RestartXrayService(); err != nil {
  1082. return common.NewErrorf("Imported DB but failed to start Xray: %v", err)
  1083. }
  1084. return nil
  1085. }
  1086. // IsValidGeofileName validates that the filename is safe for geofile operations.
  1087. // It checks for path traversal attempts and ensures the filename contains only safe characters.
  1088. func (s *ServerService) IsValidGeofileName(filename string) bool {
  1089. if filename == "" {
  1090. return false
  1091. }
  1092. // Check for path traversal attempts
  1093. if strings.Contains(filename, "..") {
  1094. return false
  1095. }
  1096. // Check for path separators (both forward and backward slash)
  1097. if strings.ContainsAny(filename, `/\`) {
  1098. return false
  1099. }
  1100. // Check for absolute path indicators
  1101. if filepath.IsAbs(filename) {
  1102. return false
  1103. }
  1104. // Additional security: only allow alphanumeric, dots, underscores, and hyphens
  1105. // This is stricter than the general filename regex
  1106. validGeofilePattern := `^[a-zA-Z0-9._-]+\.dat$`
  1107. matched, _ := regexp.MatchString(validGeofilePattern, filename)
  1108. return matched
  1109. }
  1110. func (s *ServerService) UpdateGeofile(fileName string) error {
  1111. type geofileEntry struct {
  1112. URL string
  1113. FileName string
  1114. }
  1115. geofileAllowlist := map[string]geofileEntry{
  1116. "geoip.dat": {"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat", "geoip.dat"},
  1117. "geosite.dat": {"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat", "geosite.dat"},
  1118. "geoip_IR.dat": {"https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat", "geoip_IR.dat"},
  1119. "geosite_IR.dat": {"https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat", "geosite_IR.dat"},
  1120. "geoip_RU.dat": {"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat", "geoip_RU.dat"},
  1121. "geosite_RU.dat": {"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat", "geosite_RU.dat"},
  1122. }
  1123. // Strict allowlist check to avoid writing uncontrolled files
  1124. if fileName != "" {
  1125. if _, ok := geofileAllowlist[fileName]; !ok {
  1126. return common.NewErrorf("Invalid geofile name: %q not in allowlist", fileName)
  1127. }
  1128. }
  1129. downloadFile := func(url, destPath string) error {
  1130. var req *http.Request
  1131. req, err := http.NewRequest("GET", url, nil)
  1132. if err != nil {
  1133. return common.NewErrorf("Failed to create HTTP request for %s: %v", url, err)
  1134. }
  1135. var localFileModTime time.Time
  1136. if fileInfo, err := os.Stat(destPath); err == nil {
  1137. localFileModTime = fileInfo.ModTime()
  1138. if !localFileModTime.IsZero() {
  1139. req.Header.Set("If-Modified-Since", localFileModTime.UTC().Format(http.TimeFormat))
  1140. }
  1141. }
  1142. client := &http.Client{}
  1143. resp, err := client.Do(req)
  1144. if err != nil {
  1145. return common.NewErrorf("Failed to download Geofile from %s: %v", url, err)
  1146. }
  1147. defer resp.Body.Close()
  1148. // Parse Last-Modified header from server
  1149. var serverModTime time.Time
  1150. serverModTimeStr := resp.Header.Get("Last-Modified")
  1151. if serverModTimeStr != "" {
  1152. parsedTime, err := time.Parse(http.TimeFormat, serverModTimeStr)
  1153. if err != nil {
  1154. logger.Warningf("Failed to parse Last-Modified header for %s: %v", url, err)
  1155. } else {
  1156. serverModTime = parsedTime
  1157. }
  1158. }
  1159. // Function to update local file's modification time
  1160. updateFileModTime := func() {
  1161. if !serverModTime.IsZero() {
  1162. if err := os.Chtimes(destPath, serverModTime, serverModTime); err != nil {
  1163. logger.Warningf("Failed to update modification time for %s: %v", destPath, err)
  1164. }
  1165. }
  1166. }
  1167. // Handle 304 Not Modified
  1168. if resp.StatusCode == http.StatusNotModified {
  1169. updateFileModTime()
  1170. return nil
  1171. }
  1172. if resp.StatusCode != http.StatusOK {
  1173. return common.NewErrorf("Failed to download Geofile from %s: received status code %d", url, resp.StatusCode)
  1174. }
  1175. file, err := os.Create(destPath)
  1176. if err != nil {
  1177. return common.NewErrorf("Failed to create Geofile %s: %v", destPath, err)
  1178. }
  1179. defer file.Close()
  1180. _, err = io.Copy(file, resp.Body)
  1181. if err != nil {
  1182. return common.NewErrorf("Failed to save Geofile %s: %v", destPath, err)
  1183. }
  1184. updateFileModTime()
  1185. return nil
  1186. }
  1187. var errorMessages []string
  1188. if fileName == "" {
  1189. // Download all geofiles
  1190. for _, entry := range geofileAllowlist {
  1191. destPath := filepath.Join(config.GetBinFolderPath(), entry.FileName)
  1192. if err := downloadFile(entry.URL, destPath); err != nil {
  1193. errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", entry.FileName, err))
  1194. }
  1195. }
  1196. } else {
  1197. entry := geofileAllowlist[fileName]
  1198. destPath := filepath.Join(config.GetBinFolderPath(), entry.FileName)
  1199. if err := downloadFile(entry.URL, destPath); err != nil {
  1200. errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", entry.FileName, err))
  1201. }
  1202. }
  1203. err := s.RestartXrayService()
  1204. if err != nil {
  1205. errorMessages = append(errorMessages, fmt.Sprintf("Updated Geofile '%s' but Failed to start Xray: %v", fileName, err))
  1206. }
  1207. if len(errorMessages) > 0 {
  1208. return common.NewErrorf("%s", strings.Join(errorMessages, "\r\n"))
  1209. }
  1210. return nil
  1211. }
  1212. func (s *ServerService) GetNewX25519Cert() (any, error) {
  1213. // Run the command
  1214. cmd := exec.Command(xray.GetBinaryPath(), "x25519")
  1215. var out bytes.Buffer
  1216. cmd.Stdout = &out
  1217. err := cmd.Run()
  1218. if err != nil {
  1219. return nil, err
  1220. }
  1221. lines := strings.Split(out.String(), "\n")
  1222. privateKeyLine := strings.Split(lines[0], ":")
  1223. publicKeyLine := strings.Split(lines[1], ":")
  1224. privateKey := strings.TrimSpace(privateKeyLine[1])
  1225. publicKey := strings.TrimSpace(publicKeyLine[1])
  1226. keyPair := map[string]any{
  1227. "privateKey": privateKey,
  1228. "publicKey": publicKey,
  1229. }
  1230. return keyPair, nil
  1231. }
  1232. func (s *ServerService) GetNewmldsa65() (any, error) {
  1233. // Run the command
  1234. cmd := exec.Command(xray.GetBinaryPath(), "mldsa65")
  1235. var out bytes.Buffer
  1236. cmd.Stdout = &out
  1237. err := cmd.Run()
  1238. if err != nil {
  1239. return nil, err
  1240. }
  1241. lines := strings.Split(out.String(), "\n")
  1242. SeedLine := strings.Split(lines[0], ":")
  1243. VerifyLine := strings.Split(lines[1], ":")
  1244. seed := strings.TrimSpace(SeedLine[1])
  1245. verify := strings.TrimSpace(VerifyLine[1])
  1246. keyPair := map[string]any{
  1247. "seed": seed,
  1248. "verify": verify,
  1249. }
  1250. return keyPair, nil
  1251. }
  1252. func (s *ServerService) GetNewEchCert(sni string) (any, error) {
  1253. // Run the command
  1254. cmd := exec.Command(xray.GetBinaryPath(), "tls", "ech", "--serverName", sni)
  1255. var out bytes.Buffer
  1256. cmd.Stdout = &out
  1257. err := cmd.Run()
  1258. if err != nil {
  1259. return nil, err
  1260. }
  1261. lines := strings.Split(out.String(), "\n")
  1262. if len(lines) < 4 {
  1263. return nil, common.NewError("invalid ech cert")
  1264. }
  1265. configList := lines[1]
  1266. serverKeys := lines[3]
  1267. return map[string]any{
  1268. "echServerKeys": serverKeys,
  1269. "echConfigList": configList,
  1270. }, nil
  1271. }
  1272. func (s *ServerService) GetNewVlessEnc() (any, error) {
  1273. cmd := exec.Command(xray.GetBinaryPath(), "vlessenc")
  1274. var out bytes.Buffer
  1275. cmd.Stdout = &out
  1276. if err := cmd.Run(); err != nil {
  1277. return nil, err
  1278. }
  1279. return map[string]any{
  1280. "auths": parseVlessEncAuths(out.String()),
  1281. }, nil
  1282. }
  1283. func parseVlessEncAuths(output string) []map[string]string {
  1284. lines := strings.Split(output, "\n")
  1285. var auths []map[string]string
  1286. var current map[string]string
  1287. for _, line := range lines {
  1288. line = strings.TrimSpace(line)
  1289. if strings.HasPrefix(line, "Authentication:") {
  1290. if current != nil {
  1291. auths = append(auths, current)
  1292. }
  1293. label := strings.TrimSpace(strings.TrimPrefix(line, "Authentication:"))
  1294. current = map[string]string{
  1295. "id": vlessEncAuthID(label),
  1296. "label": label,
  1297. }
  1298. } else if strings.HasPrefix(line, `"decryption"`) || strings.HasPrefix(line, `"encryption"`) {
  1299. parts := strings.SplitN(line, ":", 2)
  1300. if len(parts) == 2 && current != nil {
  1301. key := strings.Trim(parts[0], `" `)
  1302. val := strings.TrimSpace(parts[1])
  1303. val = strings.TrimSuffix(val, ",")
  1304. val = strings.Trim(val, `" `)
  1305. current[key] = val
  1306. }
  1307. }
  1308. }
  1309. if current != nil {
  1310. auths = append(auths, current)
  1311. }
  1312. return auths
  1313. }
  1314. func vlessEncAuthID(label string) string {
  1315. normalized := strings.NewReplacer("-", "", "_", "", " ", "").Replace(strings.ToLower(label))
  1316. switch {
  1317. case strings.Contains(normalized, "mlkem768"):
  1318. return "mlkem768"
  1319. case strings.Contains(normalized, "x25519"):
  1320. return "x25519"
  1321. default:
  1322. return normalized
  1323. }
  1324. }
  1325. func (s *ServerService) GetNewUUID() (map[string]string, error) {
  1326. newUUID, err := uuid.NewRandom()
  1327. if err != nil {
  1328. return nil, fmt.Errorf("failed to generate UUID: %w", err)
  1329. }
  1330. return map[string]string{
  1331. "uuid": newUUID.String(),
  1332. }, nil
  1333. }
  1334. func (s *ServerService) GetNewmlkem768() (any, error) {
  1335. // Run the command
  1336. cmd := exec.Command(xray.GetBinaryPath(), "mlkem768")
  1337. var out bytes.Buffer
  1338. cmd.Stdout = &out
  1339. err := cmd.Run()
  1340. if err != nil {
  1341. return nil, err
  1342. }
  1343. lines := strings.Split(out.String(), "\n")
  1344. SeedLine := strings.Split(lines[0], ":")
  1345. ClientLine := strings.Split(lines[1], ":")
  1346. seed := strings.TrimSpace(SeedLine[1])
  1347. client := strings.TrimSpace(ClientLine[1])
  1348. keyPair := map[string]any{
  1349. "seed": seed,
  1350. "client": client,
  1351. }
  1352. return keyPair, nil
  1353. }