fix: geo auto update #1261

This commit is contained in:
Larvan2 2024-05-17 11:49:09 +08:00
parent fe88f0e437
commit 5c3a9b1dfc
9 changed files with 144 additions and 114 deletions

View File

@ -67,7 +67,7 @@ func (e *updateError) Error() string {
// Update performs the auto-updater. It returns an error if the updater failed. // 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. // 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() mu.Lock()
defer mu.Unlock() defer mu.Unlock()

View File

@ -1,18 +1,29 @@
package config package updater
import ( import (
"errors"
"fmt" "fmt"
"os"
"runtime" "runtime"
"sync"
"time"
"github.com/metacubex/mihomo/common/atomic"
"github.com/metacubex/mihomo/component/geodata" "github.com/metacubex/mihomo/component/geodata"
_ "github.com/metacubex/mihomo/component/geodata/standard" _ "github.com/metacubex/mihomo/component/geodata/standard"
"github.com/metacubex/mihomo/component/mmdb" "github.com/metacubex/mihomo/component/mmdb"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
"github.com/oschwald/maxminddb-golang" "github.com/oschwald/maxminddb-golang"
) )
func UpdateGeoDatabases() error { var (
updateGeoMux sync.Mutex
UpdatingGeo atomic.Bool
)
func updateGeoDatabases() error {
defer runtime.GC() defer runtime.GC()
geoLoader, err := geodata.GetGeoDataLoader("standard") geoLoader, err := geodata.GetGeoDataLoader("standard")
if err != nil { if err != nil {
@ -88,3 +99,82 @@ func UpdateGeoDatabases() error {
return nil 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
}
}
}()
}

View File

@ -1,4 +1,4 @@
package config package updater
import ( import (
"archive/zip" "archive/zip"
@ -29,7 +29,7 @@ func UpdateUI() error {
xdMutex.Lock() xdMutex.Lock()
defer xdMutex.Unlock() defer xdMutex.Unlock()
err := prepare() err := prepare_ui()
if err != nil { if err != nil {
return err return err
} }
@ -64,7 +64,7 @@ func UpdateUI() error {
return nil return nil
} }
func prepare() error { func prepare_ui() error {
if ExternalUIPath == "" || ExternalUIURL == "" { if ExternalUIPath == "" || ExternalUIURL == "" {
return ErrIncompleteConf return ErrIncompleteConf
} }

View File

@ -1,12 +1,35 @@
package updater package updater
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"net/http"
"os"
"time"
mihomoHttp "github.com/metacubex/mihomo/component/http"
C "github.com/metacubex/mihomo/constant"
"golang.org/x/exp/constraints" "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. // LimitReachedError records the limit and the operation that caused it.
type LimitReachedError struct { type LimitReachedError struct {
Limit int64 Limit int64

View File

@ -28,6 +28,7 @@ import (
SNIFF "github.com/metacubex/mihomo/component/sniffer" SNIFF "github.com/metacubex/mihomo/component/sniffer"
tlsC "github.com/metacubex/mihomo/component/tls" tlsC "github.com/metacubex/mihomo/component/tls"
"github.com/metacubex/mihomo/component/trie" "github.com/metacubex/mihomo/component/trie"
"github.com/metacubex/mihomo/component/updater"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/features" "github.com/metacubex/mihomo/constant/features"
providerTypes "github.com/metacubex/mihomo/constant/provider" 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 N.KeepAliveInterval = time.Duration(cfg.KeepAliveInterval) * time.Second
} }
ExternalUIPath = cfg.ExternalUI updater.ExternalUIPath = cfg.ExternalUI
// checkout externalUI exist // checkout externalUI exist
if ExternalUIPath != "" { if updater.ExternalUIPath != "" {
ExternalUIPath = C.Path.Resolve(ExternalUIPath) updater.ExternalUIPath = C.Path.Resolve(updater.ExternalUIPath)
if _, err := os.Stat(ExternalUIPath); os.IsNotExist(err) { if _, err := os.Stat(updater.ExternalUIPath); os.IsNotExist(err) {
defaultUIpath := path.Join(C.Path.HomeDir(), "ui") 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 { if err := os.MkdirAll(defaultUIpath, os.ModePerm); err != nil {
return nil, err return nil, err
} }
ExternalUIPath = defaultUIpath updater.ExternalUIPath = defaultUIpath
cfg.ExternalUI = defaultUIpath cfg.ExternalUI = defaultUIpath
} }
} }
// checkout UIpath/name exist // checkout UIpath/name exist
if cfg.ExternalUIName != "" { if cfg.ExternalUIName != "" {
ExternalUIName = cfg.ExternalUIName updater.ExternalUIName = cfg.ExternalUIName
} else { } else {
ExternalUIFolder = ExternalUIPath updater.ExternalUIFolder = updater.ExternalUIPath
} }
if cfg.ExternalUIURL != "" { if cfg.ExternalUIURL != "" {
ExternalUIURL = cfg.ExternalUIURL updater.ExternalUIURL = cfg.ExternalUIURL
} }
cfg.Tun.RedirectToTun = cfg.EBpf.RedirectToTun cfg.Tun.RedirectToTun = cfg.EBpf.RedirectToTun

View File

@ -1,38 +1,15 @@
package config package config
import ( import (
"context"
"fmt" "fmt"
"io"
"net" "net"
"net/http"
"net/netip" "net/netip"
"os"
"strings" "strings"
"time"
"github.com/metacubex/mihomo/adapter/outboundgroup" "github.com/metacubex/mihomo/adapter/outboundgroup"
"github.com/metacubex/mihomo/common/structure" "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) { func trimArr(arr []string) (r []string) {
for _, e := range arr { for _, e := range arr {
r = append(r, strings.Trim(e, " ")) r = append(r, strings.Trim(e, " "))

View File

@ -4,11 +4,11 @@ import (
"net/http" "net/http"
"net/netip" "net/netip"
"path/filepath" "path/filepath"
"sync"
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/component/updater"
"github.com/metacubex/mihomo/config" "github.com/metacubex/mihomo/config"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/hub/executor" "github.com/metacubex/mihomo/hub/executor"
@ -21,11 +21,6 @@ import (
"github.com/go-chi/render" "github.com/go-chi/render"
) )
var (
updateGeoMux sync.Mutex
updatingGeo = false
)
func configRouter() http.Handler { func configRouter() http.Handler {
r := chi.NewRouter() r := chi.NewRouter()
r.Get("/", getConfigs) r.Get("/", getConfigs)
@ -369,30 +364,20 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
} }
func updateGeoDatabases(w http.ResponseWriter, r *http.Request) { func updateGeoDatabases(w http.ResponseWriter, r *http.Request) {
updateGeoMux.Lock() if updater.UpdatingGeo.Load() {
if updatingGeo {
updateGeoMux.Unlock()
render.Status(r, http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError("updating...")) render.JSON(w, r, newError("updating..."))
return return
} }
updatingGeo = true err := updater.UpdateGeoDatabases()
updateGeoMux.Unlock() if err != nil {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError(err.Error()))
return
}
go func() { 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()) cfg, err := executor.ParseWithPath(C.Path.Config())
if err != nil { if err != nil {
log.Errorln("[REST-API] update GEO databases failed: %v", err) log.Errorln("[REST-API] update GEO databases failed: %v", err)

View File

@ -6,8 +6,7 @@ import (
"net/http" "net/http"
"os" "os"
"github.com/metacubex/mihomo/config" "github.com/metacubex/mihomo/component/updater"
"github.com/metacubex/mihomo/hub/updater"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
@ -18,6 +17,7 @@ func upgradeRouter() http.Handler {
r := chi.NewRouter() r := chi.NewRouter()
r.Post("/", upgradeCore) r.Post("/", upgradeCore)
r.Post("/ui", updateUI) r.Post("/ui", updateUI)
r.Post("/geo", updateGeoDatabases)
return r return r
} }
@ -31,7 +31,7 @@ func upgradeCore(w http.ResponseWriter, r *http.Request) {
return return
} }
err = updater.Update(execPath) err = updater.UpdateCore(execPath)
if err != nil { if err != nil {
log.Warnln("%s", err) log.Warnln("%s", err)
render.Status(r, http.StatusInternalServerError) 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) { func updateUI(w http.ResponseWriter, r *http.Request) {
err := config.UpdateUI() err := updater.UpdateUI()
if err != nil { if err != nil {
if errors.Is(err, config.ErrIncompleteConf) { if errors.Is(err, updater.ErrIncompleteConf) {
log.Warnln("%s", err) log.Warnln("%s", err)
render.Status(r, http.StatusNotImplemented) render.Status(r, http.StatusNotImplemented)
render.JSON(w, r, newError(fmt.Sprintf("%s", err))) render.JSON(w, r, newError(fmt.Sprintf("%s", err)))

50
main.go
View File

@ -8,10 +8,9 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"sync"
"syscall" "syscall"
"time"
"github.com/metacubex/mihomo/component/updater"
"github.com/metacubex/mihomo/config" "github.com/metacubex/mihomo/config"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/features" "github.com/metacubex/mihomo/constant/features"
@ -32,8 +31,6 @@ var (
externalController string externalController string
externalControllerUnix string externalControllerUnix string
secret string secret string
updateGeoMux sync.Mutex
updatingGeo = false
) )
func init() { func init() {
@ -116,14 +113,7 @@ func main() {
} }
if C.GeoAutoUpdate { if C.GeoAutoUpdate {
ticker := time.NewTicker(time.Duration(C.GeoUpdateInterval) * time.Hour) updater.RegisterGeoUpdater()
log.Infoln("[GEO] Start update GEO database every %d hours", C.GeoUpdateInterval)
go func() {
for range ticker.C {
updateGeoDatabases()
}
}()
} }
defer executor.Shutdown() 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)
}()
}