@@ -92,6 +92,20 @@ type Status struct {
9292 IPv4 string `json:"ipv4"`
9393 IPv6 string `json:"ipv6"`
9494 } `json:"publicIP"`
95+ GeoFiles struct {
96+ GeoIP struct {
97+ Exists bool `json:"exists"`
98+ Size uint64 `json:"size"`
99+ UpdatedAt int64 `json:"updatedAt"`
100+ Version string `json:"version"`
101+ } `json:"geoip"`
102+ GeoSite struct {
103+ Exists bool `json:"exists"`
104+ Size uint64 `json:"size"`
105+ UpdatedAt int64 `json:"updatedAt"`
106+ Version string `json:"version"`
107+ } `json:"geosite"`
108+ } `json:"geoFiles"`
95109 AppStats struct {
96110 Threads uint32 `json:"threads"`
97111 Mem uint64 `json:"mem"`
@@ -234,6 +248,27 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
234248
235249 status .PublicIP .IPv4 = getPublicIP ("8.8.8.8:80" )
236250 status .PublicIP .IPv6 = getPublicIP ("[2001:4860:4860::8888]:80" )
251+ fillGeoFileStatus := func (path string , dest * struct {
252+ Exists bool `json:"exists"`
253+ Size uint64 `json:"size"`
254+ UpdatedAt int64 `json:"updatedAt"`
255+ Version string `json:"version"`
256+ }) {
257+ info , err := os .Stat (path )
258+ if err != nil {
259+ dest .Exists = false
260+ dest .Size = 0
261+ dest .UpdatedAt = 0
262+ dest .Version = ""
263+ return
264+ }
265+ dest .Exists = true
266+ dest .Size = uint64 (info .Size ())
267+ dest .UpdatedAt = info .ModTime ().Unix ()
268+ dest .Version = readGeoFileVersion (path )
269+ }
270+ fillGeoFileStatus (xray .GetGeoipPath (), & status .GeoFiles .GeoIP )
271+ fillGeoFileStatus (xray .GetGeositePath (), & status .GeoFiles .GeoSite )
237272
238273 status .HostName , _ = os .Hostname ()
239274
@@ -468,6 +503,137 @@ func (s *ServerService) UpdateXray(version string) error {
468503 return nil
469504}
470505
506+ func (s * ServerService ) UpdateGeoFiles () error {
507+ files := []struct {
508+ url string
509+ path string
510+ name string
511+ }{
512+ {
513+ url : "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat" ,
514+ path : xray .GetGeoipPath (),
515+ name : "geoip.dat" ,
516+ },
517+ {
518+ url : "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat" ,
519+ path : xray .GetGeositePath (),
520+ name : "geosite.dat" ,
521+ },
522+ }
523+
524+ client := & http.Client {Timeout : 2 * time .Minute }
525+ for _ , file := range files {
526+ version , err := getLatestReleaseTag (file .url , file .name )
527+ if err != nil {
528+ logger .Warningf ("failed to detect %s version: %v" , file .name , err )
529+ version = ""
530+ }
531+
532+ req , err := http .NewRequest (http .MethodGet , file .url , nil )
533+ if err != nil {
534+ return fmt .Errorf ("create request for %s: %w" , file .name , err )
535+ }
536+ req .Header .Set ("User-Agent" , "tx-ui" )
537+
538+ resp , err := client .Do (req )
539+ if err != nil {
540+ return fmt .Errorf ("download %s: %w" , file .name , err )
541+ }
542+ if resp .StatusCode != http .StatusOK {
543+ resp .Body .Close ()
544+ return fmt .Errorf ("download %s: unexpected status %s" , file .name , resp .Status )
545+ }
546+
547+ if err := os .MkdirAll (filepath .Dir (file .path ), 0o755 ); err != nil {
548+ resp .Body .Close ()
549+ return fmt .Errorf ("prepare directory for %s: %w" , file .name , err )
550+ }
551+
552+ tmpPath := file .path + ".tmp"
553+ tmpFile , err := os .OpenFile (tmpPath , os .O_CREATE | os .O_RDWR | os .O_TRUNC , 0o644 )
554+ if err != nil {
555+ resp .Body .Close ()
556+ return fmt .Errorf ("create temp file for %s: %w" , file .name , err )
557+ }
558+
559+ _ , copyErr := io .Copy (tmpFile , resp .Body )
560+ closeErr := tmpFile .Close ()
561+ resp .Body .Close ()
562+ if copyErr != nil {
563+ _ = os .Remove (tmpPath )
564+ return fmt .Errorf ("write %s: %w" , file .name , copyErr )
565+ }
566+ if closeErr != nil {
567+ _ = os .Remove (tmpPath )
568+ return fmt .Errorf ("close temp file for %s: %w" , file .name , closeErr )
569+ }
570+ if err := os .Rename (tmpPath , file .path ); err != nil {
571+ _ = os .Remove (tmpPath )
572+ return fmt .Errorf ("replace %s: %w" , file .name , err )
573+ }
574+
575+ if version != "" {
576+ if err := os .WriteFile (file .path + ".version" , []byte (version ), 0o644 ); err != nil {
577+ logger .Warningf ("failed to write %s version file: %v" , file .name , err )
578+ }
579+ }
580+ }
581+
582+ if err := s .xrayService .RestartXray (true ); err != nil {
583+ logger .Error ("restart xray after geo files update failed:" , err )
584+ return err
585+ }
586+
587+ return nil
588+ }
589+
590+ func extractReleaseTag (path string , fileName string ) string {
591+ parts := strings .Split (path , "/" )
592+ for i := 0 ; i < len (parts )- 2 ; i ++ {
593+ if parts [i ] == "download" && parts [i + 2 ] == fileName {
594+ return strings .TrimSpace (parts [i + 1 ])
595+ }
596+ }
597+ return ""
598+ }
599+
600+ func getLatestReleaseTag (assetURL string , assetName string ) (string , error ) {
601+ noRedirectClient := & http.Client {
602+ Timeout : 20 * time .Second ,
603+ CheckRedirect : func (req * http.Request , via []* http.Request ) error {
604+ return http .ErrUseLastResponse
605+ },
606+ }
607+
608+ req , err := http .NewRequest (http .MethodGet , assetURL , nil )
609+ if err != nil {
610+ return "" , err
611+ }
612+ req .Header .Set ("User-Agent" , "tx-ui" )
613+
614+ resp , err := noRedirectClient .Do (req )
615+ if err != nil {
616+ return "" , err
617+ }
618+ defer resp .Body .Close ()
619+
620+ location := resp .Header .Get ("Location" )
621+ if location == "" {
622+ // Some proxies may still follow redirects. Try current request URL path as fallback.
623+ return extractReleaseTag (resp .Request .URL .Path , assetName ), nil
624+ }
625+
626+ return extractReleaseTag (location , assetName ), nil
627+ }
628+
629+ func readGeoFileVersion (path string ) string {
630+ data , err := os .ReadFile (path + ".version" )
631+ if err != nil {
632+ return ""
633+ }
634+ return strings .TrimSpace (string (data ))
635+ }
636+
471637func (s * ServerService ) UpdatePanel (version string ) {
472638 fmt .Println ("Starting x-ui installation..." )
473639
0 commit comments