diff --git a/hub/updater/updater.go b/component/updater/update_core.go similarity index 99% rename from hub/updater/updater.go rename to component/updater/update_core.go index df5da3f4..0070fbb1 100644 --- a/hub/updater/updater.go +++ b/component/updater/update_core.go @@ -67,7 +67,7 @@ func (e *updateError) Error() string { // Update performs the auto-updater. It returns an error if the updater failed. // If firstRun is true, it assumes the configuration file doesn't exist. -func Update(execPath string) (err error) { +func UpdateCore(execPath string) (err error) { mu.Lock() defer mu.Unlock() diff --git a/config/update_geo.go b/component/updater/update_geo.go similarity index 52% rename from config/update_geo.go rename to component/updater/update_geo.go index 43cac25c..a98d94dc 100644 --- a/config/update_geo.go +++ b/component/updater/update_geo.go @@ -1,18 +1,29 @@ -package config +package updater import ( + "errors" "fmt" + "os" "runtime" + "sync" + "time" + "github.com/metacubex/mihomo/common/atomic" "github.com/metacubex/mihomo/component/geodata" _ "github.com/metacubex/mihomo/component/geodata/standard" "github.com/metacubex/mihomo/component/mmdb" C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" "github.com/oschwald/maxminddb-golang" ) -func UpdateGeoDatabases() error { +var ( + updateGeoMux sync.Mutex + UpdatingGeo atomic.Bool +) + +func updateGeoDatabases() error { defer runtime.GC() geoLoader, err := geodata.GetGeoDataLoader("standard") if err != nil { @@ -88,3 +99,82 @@ func UpdateGeoDatabases() error { return nil } + +func UpdateGeoDatabases() error { + log.Infoln("[GEO] Start updating GEO database") + + updateGeoMux.Lock() + + if UpdatingGeo.Load() { + updateGeoMux.Unlock() + return errors.New("GEO database is updating, skip") + } + + UpdatingGeo.Store(true) + updateGeoMux.Unlock() + + defer func() { + UpdatingGeo.Store(false) + }() + + log.Infoln("[GEO] Updating GEO database") + + if err := updateGeoDatabases(); err != nil { + log.Errorln("[GEO] update GEO database error: %s", err.Error()) + return err + } + + return nil +} + +func getUpdateTime() (err error, time time.Time) { + var fileInfo os.FileInfo + if C.GeodataMode { + fileInfo, err = os.Stat(C.Path.GeoIP()) + if err != nil { + return err, time + } + } else { + fileInfo, err = os.Stat(C.Path.MMDB()) + if err != nil { + return err, time + } + } + + return nil, fileInfo.ModTime() +} + +func RegisterGeoUpdater() { + if C.GeoUpdateInterval <= 0 { + log.Errorln("[GEO] Invalid update interval: %d", C.GeoUpdateInterval) + return + } + + ticker := time.NewTicker(time.Duration(C.GeoUpdateInterval) * time.Hour) + defer ticker.Stop() + + log.Infoln("[GEO] update GEO database every %d hours", C.GeoUpdateInterval) + go func() { + err, lastUpdate := getUpdateTime() + if err != nil { + log.Errorln("[GEO] Get GEO database update time error: %s", err.Error()) + return + } + + log.Infoln("[GEO] last update time %s", lastUpdate) + if lastUpdate.Add(time.Duration(C.GeoUpdateInterval) * time.Hour).Before(time.Now()) { + log.Infoln("[GEO] Database has not been updated for %v, update now", time.Duration(C.GeoUpdateInterval)*time.Hour) + if err := UpdateGeoDatabases(); err != nil { + log.Errorln("[GEO] Failed to update GEO database: %s", err.Error()) + return + } + } + + for range ticker.C { + if err := UpdateGeoDatabases(); err != nil { + log.Errorln("[GEO] Failed to update GEO database: %s", err.Error()) + return + } + } + }() +} diff --git a/config/update_ui.go b/component/updater/update_ui.go similarity index 97% rename from config/update_ui.go rename to component/updater/update_ui.go index cff1d6d7..85452ba5 100644 --- a/config/update_ui.go +++ b/component/updater/update_ui.go @@ -1,4 +1,4 @@ -package config +package updater import ( "archive/zip" @@ -29,7 +29,7 @@ func UpdateUI() error { xdMutex.Lock() defer xdMutex.Unlock() - err := prepare() + err := prepare_ui() if err != nil { return err } @@ -64,7 +64,7 @@ func UpdateUI() error { return nil } -func prepare() error { +func prepare_ui() error { if ExternalUIPath == "" || ExternalUIURL == "" { return ErrIncompleteConf } diff --git a/hub/updater/limitedreader.go b/component/updater/utils.go similarity index 70% rename from hub/updater/limitedreader.go rename to component/updater/utils.go index c31db601..0eecfc6c 100644 --- a/hub/updater/limitedreader.go +++ b/component/updater/utils.go @@ -1,12 +1,35 @@ package updater import ( + "context" "fmt" "io" + "net/http" + "os" + "time" + + mihomoHttp "github.com/metacubex/mihomo/component/http" + C "github.com/metacubex/mihomo/constant" "golang.org/x/exp/constraints" ) +func downloadForBytes(url string) ([]byte, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) + defer cancel() + resp, err := mihomoHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + return io.ReadAll(resp.Body) +} + +func saveFile(bytes []byte, path string) error { + return os.WriteFile(path, bytes, 0o644) +} + // LimitReachedError records the limit and the operation that caused it. type LimitReachedError struct { Limit int64 diff --git a/config/config.go b/config/config.go index 311fd2e2..9bc0afc8 100644 --- a/config/config.go +++ b/config/config.go @@ -28,6 +28,7 @@ import ( SNIFF "github.com/metacubex/mihomo/component/sniffer" tlsC "github.com/metacubex/mihomo/component/tls" "github.com/metacubex/mihomo/component/trie" + "github.com/metacubex/mihomo/component/updater" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/constant/features" providerTypes "github.com/metacubex/mihomo/constant/provider" @@ -640,28 +641,28 @@ func parseGeneral(cfg *RawConfig) (*General, error) { N.KeepAliveInterval = time.Duration(cfg.KeepAliveInterval) * time.Second } - ExternalUIPath = cfg.ExternalUI + updater.ExternalUIPath = cfg.ExternalUI // checkout externalUI exist - if ExternalUIPath != "" { - ExternalUIPath = C.Path.Resolve(ExternalUIPath) - if _, err := os.Stat(ExternalUIPath); os.IsNotExist(err) { + if updater.ExternalUIPath != "" { + updater.ExternalUIPath = C.Path.Resolve(updater.ExternalUIPath) + if _, err := os.Stat(updater.ExternalUIPath); os.IsNotExist(err) { defaultUIpath := path.Join(C.Path.HomeDir(), "ui") - log.Warnln("external-ui: %s does not exist, creating folder in %s", ExternalUIPath, defaultUIpath) + log.Warnln("external-ui: %s does not exist, creating folder in %s", updater.ExternalUIPath, defaultUIpath) if err := os.MkdirAll(defaultUIpath, os.ModePerm); err != nil { return nil, err } - ExternalUIPath = defaultUIpath + updater.ExternalUIPath = defaultUIpath cfg.ExternalUI = defaultUIpath } } // checkout UIpath/name exist if cfg.ExternalUIName != "" { - ExternalUIName = cfg.ExternalUIName + updater.ExternalUIName = cfg.ExternalUIName } else { - ExternalUIFolder = ExternalUIPath + updater.ExternalUIFolder = updater.ExternalUIPath } if cfg.ExternalUIURL != "" { - ExternalUIURL = cfg.ExternalUIURL + updater.ExternalUIURL = cfg.ExternalUIURL } cfg.Tun.RedirectToTun = cfg.EBpf.RedirectToTun diff --git a/config/utils.go b/config/utils.go index 66bf3441..f87fb341 100644 --- a/config/utils.go +++ b/config/utils.go @@ -1,38 +1,15 @@ package config import ( - "context" "fmt" - "io" "net" - "net/http" "net/netip" - "os" "strings" - "time" "github.com/metacubex/mihomo/adapter/outboundgroup" "github.com/metacubex/mihomo/common/structure" - mihomoHttp "github.com/metacubex/mihomo/component/http" - C "github.com/metacubex/mihomo/constant" ) -func downloadForBytes(url string) ([]byte, error) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) - defer cancel() - resp, err := mihomoHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - return io.ReadAll(resp.Body) -} - -func saveFile(bytes []byte, path string) error { - return os.WriteFile(path, bytes, 0o644) -} - func trimArr(arr []string) (r []string) { for _, e := range arr { r = append(r, strings.Trim(e, " ")) diff --git a/hub/route/configs.go b/hub/route/configs.go index 653e4351..47fd26e0 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -4,11 +4,11 @@ import ( "net/http" "net/netip" "path/filepath" - "sync" "github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/resolver" + "github.com/metacubex/mihomo/component/updater" "github.com/metacubex/mihomo/config" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/hub/executor" @@ -21,11 +21,6 @@ import ( "github.com/go-chi/render" ) -var ( - updateGeoMux sync.Mutex - updatingGeo = false -) - func configRouter() http.Handler { r := chi.NewRouter() r.Get("/", getConfigs) @@ -369,30 +364,20 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) { } func updateGeoDatabases(w http.ResponseWriter, r *http.Request) { - updateGeoMux.Lock() - - if updatingGeo { - updateGeoMux.Unlock() + if updater.UpdatingGeo.Load() { render.Status(r, http.StatusBadRequest) render.JSON(w, r, newError("updating...")) return } - updatingGeo = true - updateGeoMux.Unlock() + err := updater.UpdateGeoDatabases() + if err != nil { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, newError(err.Error())) + return + } 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(C.Path.Config()) if err != nil { log.Errorln("[REST-API] update GEO databases failed: %v", err) diff --git a/hub/route/upgrade.go b/hub/route/upgrade.go index ea371798..db00af5c 100644 --- a/hub/route/upgrade.go +++ b/hub/route/upgrade.go @@ -6,8 +6,7 @@ import ( "net/http" "os" - "github.com/metacubex/mihomo/config" - "github.com/metacubex/mihomo/hub/updater" + "github.com/metacubex/mihomo/component/updater" "github.com/metacubex/mihomo/log" "github.com/go-chi/chi/v5" @@ -18,6 +17,7 @@ func upgradeRouter() http.Handler { r := chi.NewRouter() r.Post("/", upgradeCore) r.Post("/ui", updateUI) + r.Post("/geo", updateGeoDatabases) return r } @@ -31,7 +31,7 @@ func upgradeCore(w http.ResponseWriter, r *http.Request) { return } - err = updater.Update(execPath) + err = updater.UpdateCore(execPath) if err != nil { log.Warnln("%s", err) render.Status(r, http.StatusInternalServerError) @@ -48,9 +48,9 @@ func upgradeCore(w http.ResponseWriter, r *http.Request) { } func updateUI(w http.ResponseWriter, r *http.Request) { - err := config.UpdateUI() + err := updater.UpdateUI() if err != nil { - if errors.Is(err, config.ErrIncompleteConf) { + if errors.Is(err, updater.ErrIncompleteConf) { log.Warnln("%s", err) render.Status(r, http.StatusNotImplemented) render.JSON(w, r, newError(fmt.Sprintf("%s", err))) diff --git a/main.go b/main.go index afe9cfd2..1d16f8bf 100644 --- a/main.go +++ b/main.go @@ -8,10 +8,9 @@ import ( "path/filepath" "runtime" "strings" - "sync" "syscall" - "time" + "github.com/metacubex/mihomo/component/updater" "github.com/metacubex/mihomo/config" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/constant/features" @@ -32,8 +31,6 @@ var ( externalController string externalControllerUnix string secret string - updateGeoMux sync.Mutex - updatingGeo = false ) func init() { @@ -116,14 +113,7 @@ func main() { } if C.GeoAutoUpdate { - ticker := time.NewTicker(time.Duration(C.GeoUpdateInterval) * time.Hour) - - log.Infoln("[GEO] Start update GEO database every %d hours", C.GeoUpdateInterval) - go func() { - for range ticker.C { - updateGeoDatabases() - } - }() + updater.RegisterGeoUpdater() } defer executor.Shutdown() @@ -145,39 +135,3 @@ func main() { } } } - -func updateGeoDatabases() { - log.Infoln("[GEO] Start updating GEO database") - updateGeoMux.Lock() - - if updatingGeo { - updateGeoMux.Unlock() - log.Infoln("[GEO] GEO database is updating, skip") - return - } - - updatingGeo = true - updateGeoMux.Unlock() - - go func() { - defer func() { - updatingGeo = false - }() - - log.Infoln("[GEO] Updating GEO database") - - if err := config.UpdateGeoDatabases(); err != nil { - log.Errorln("[GEO] update GEO database error: %s", err.Error()) - return - } - - cfg, err := executor.ParseWithPath(C.Path.Config()) - if err != nil { - log.Errorln("[GEO] update GEO database failed: %s", err.Error()) - return - } - - log.Infoln("[GEO] Update GEO database success, apply new config") - executor.ApplyConfig(cfg, false) - }() -}