diff --git a/config/updateGeo.go b/config/updateGeo.go new file mode 100644 index 00000000..77fd9232 --- /dev/null +++ b/config/updateGeo.go @@ -0,0 +1,69 @@ +package config + +import ( + "fmt" + "os" + "runtime" + + "github.com/Dreamacro/clash/component/geodata" + _ "github.com/Dreamacro/clash/component/geodata/standard" + C "github.com/Dreamacro/clash/constant" + + "github.com/oschwald/geoip2-golang" +) + +func UpdateGeoDatabases() error { + var ( + tmpMMDB = C.Path.Resolve("temp_country.mmdb") + tmpGeoSite = C.Path.Resolve("temp_geosite.dat") + ) + + if err := downloadMMDB(tmpMMDB); err != nil { + return fmt.Errorf("can't download MMDB database file: %w", err) + } + + if err := verifyMMDB(tmpMMDB); err != nil { + _ = os.Remove(tmpMMDB) + return fmt.Errorf("invalid MMDB database file, %w", err) + } + + if err := os.Rename(tmpMMDB, C.Path.MMDB()); err != nil { + return fmt.Errorf("can't rename MMDB database file: %w", err) + } + + if err := downloadGeoSite(tmpGeoSite); err != nil { + return fmt.Errorf("can't download GeoSite database file: %w", err) + } + + if err := verifyGeoSite(tmpGeoSite); err != nil { + _ = os.Remove(tmpGeoSite) + return fmt.Errorf("invalid GeoSite database file, %w", err) + } + + if err := os.Rename(tmpGeoSite, C.Path.GeoSite()); err != nil { + return fmt.Errorf("can't rename GeoSite database file: %w", err) + } + + return nil +} + +func verifyMMDB(path string) error { + instance, err := geoip2.Open(path) + if err == nil { + _ = instance.Close() + } + return err +} + +func verifyGeoSite(path string) error { + geoLoader, err := geodata.GetGeoDataLoader("standard") + if err != nil { + return err + } + + _, err = geoLoader.LoadSite(path, "cn") + + runtime.GC() + + return err +} diff --git a/hub/route/configsGeo.go b/hub/route/configsGeo.go new file mode 100644 index 00000000..a38ab03a --- /dev/null +++ b/hub/route/configsGeo.go @@ -0,0 +1,64 @@ +package route + +import ( + "net/http" + "sync" + + "github.com/Dreamacro/clash/config" + "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/hub/executor" + "github.com/Dreamacro/clash/log" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" +) + +var ( + updatingGeo bool + updateGeoMux sync.Mutex +) + +func configGeoRouter() http.Handler { + r := chi.NewRouter() + r.Post("/", updateGeoDatabases) + return r +} + +func updateGeoDatabases(w http.ResponseWriter, r *http.Request) { + updateGeoMux.Lock() + + if updatingGeo { + updateGeoMux.Unlock() + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, newError("updating...")) + return + } + + updatingGeo = true + updateGeoMux.Unlock() + + go func() { + defer func() { + updatingGeo = false + }() + + log.Warnln("[REST-API] updating GEO databases...") + + if err := config.UpdateGeoDatabases(); err != nil { + log.Errorln("[REST-API] update GEO databases failed: %v", err) + return + } + + cfg, err := executor.ParseWithPath(constant.Path.Config()) + if err != nil { + log.Errorln("[REST-API] update GEO databases failed: %v", err) + return + } + + log.Warnln("[REST-API] update GEO databases successful, apply config...") + + executor.ApplyConfig(cfg, false) + }() + + render.NoContent(w, r) +} diff --git a/hub/route/server.go b/hub/route/server.go index 7aac0667..9beaafeb 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -67,6 +67,7 @@ func Start(addr string, secret string) { r.Get("/traffic", traffic) r.Get("/version", version) r.Mount("/configs", configRouter()) + r.Mount("/configs/geo", configGeoRouter()) r.Mount("/proxies", proxyRouter()) r.Mount("/rules", ruleRouter()) r.Mount("/connections", connectionRouter())