feat: RESTful API support update Geo file

and can set update url by user, eg.
geox-url:
  geoip: "http://xxxx/gepip.dat"
  mmdb: "http://xxxx/country.mmdb"
  geosite: "http://xxxx/geosite.dat"
This commit is contained in:
adlyq 2022-05-24 15:04:13 +08:00
parent 149b4b5b43
commit 7431001ed6
6 changed files with 160 additions and 12 deletions

View file

@ -21,16 +21,16 @@ func SetLoader(newLoader string) {
geoLoaderName = newLoader
}
func Verify(name string) bool {
func Verify(name string) error {
switch name {
case C.GeositeName:
_, _, err := LoadGeoSiteMatcher("CN")
return err == nil
return err
case C.GeoipName:
_, _, err := LoadGeoIPMatcher("CN")
return err == nil
return err
default:
return false
return fmt.Errorf("not support name")
}
}

View file

@ -219,11 +219,18 @@ type RawConfig struct {
IPTables IPTables `yaml:"iptables"`
Experimental Experimental `yaml:"experimental"`
Profile Profile `yaml:"profile"`
GeoXUrl RawGeoXUrl `yaml:"geox-url"`
Proxy []map[string]any `yaml:"proxies"`
ProxyGroup []map[string]any `yaml:"proxy-groups"`
Rule []string `yaml:"rules"`
}
type RawGeoXUrl struct {
GeoIp string `yaml:"geoip" json:"geoip"`
Mmdb string `yaml:"mmdb" json:"mmdb"`
GeoSite string `yaml:"geosite" json:"geosite"`
}
type RawSniffer struct {
Enable bool `yaml:"enable" json:"enable"`
Sniffing []string `yaml:"sniffing" json:"sniffing"`
@ -309,6 +316,11 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
Profile: Profile{
StoreSelected: true,
},
GeoXUrl: RawGeoXUrl{
GeoIp: "https://raw.githubusercontents.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat",
Mmdb: "https://raw.githubusercontents.com/Loyalsoldier/geoip/release/Country.mmdb",
GeoSite: "https://raw.githubusercontents.com/Loyalsoldier/v2ray-rules-dat/release/geosite.dat",
},
}
if err := yaml.Unmarshal(buf, rawCfg); err != nil {

View file

@ -15,7 +15,7 @@ import (
var initMode = true
func downloadMMDB(path string) (err error) {
resp, err := http.Get("https://raw.githubusercontents.com/Loyalsoldier/geoip/release/Country.mmdb")
resp, err := http.Get(C.MmdbUrl)
if err != nil {
return
}
@ -32,7 +32,7 @@ func downloadMMDB(path string) (err error) {
}
func downloadGeoIP(path string) (err error) {
resp, err := http.Get("https://raw.githubusercontents.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat")
resp, err := http.Get(C.GeoIpUrl)
if err != nil {
return
}
@ -49,7 +49,7 @@ func downloadGeoIP(path string) (err error) {
}
func downloadGeoSite(path string) (err error) {
resp, err := http.Get("https://raw.githubusercontents.com/Loyalsoldier/v2ray-rules-dat/release/geosite.dat")
resp, err := http.Get(C.GeoSiteUrl)
if err != nil {
return
}
@ -74,8 +74,8 @@ func initGeoSite() error {
log.Infoln("Download GeoSite.dat finish")
}
if initMode {
if !geodata.Verify(C.GeositeName) {
log.Warnln("GeoSite.dat invalid, remove and download")
if err := geodata.Verify(C.GeositeName); err != nil {
log.Warnln("GeoSite.dat invalid, remove and download: %s", err)
if err := os.Remove(C.Path.GeoSite()); err != nil {
return fmt.Errorf("can't remove invalid GeoSite.dat: %s", err.Error())
}
@ -97,8 +97,8 @@ func initGeoIP() error {
log.Infoln("Download GeoIP.dat finish")
}
if !geodata.Verify(C.GeoipName) {
log.Warnln("GeoIP.dat invalid, remove and download")
if err := geodata.Verify(C.GeoipName); err != nil {
log.Warnln("GeoIP.dat invalid, remove and download: %s", err)
if err := os.Remove(C.Path.GeoIP()); err != nil {
return fmt.Errorf("can't remove invalid GeoIP.dat: %s", err.Error())
}
@ -158,6 +158,9 @@ func Init(dir string) error {
}
if !C.GeodataMode {
C.GeodataMode = rawCfg.GeodataMode
C.GeoIpUrl = rawCfg.GeoXUrl.GeoIp
C.GeoSiteUrl = rawCfg.GeoXUrl.GeoSite
C.MmdbUrl = rawCfg.GeoXUrl.Mmdb
}
// initial GeoIP
if err := initGeoIP(); err != nil {

82
config/updateGeo.go Normal file
View file

@ -0,0 +1,82 @@
package config
import (
"fmt"
"github.com/Dreamacro/clash/component/geodata"
_ "github.com/Dreamacro/clash/component/geodata/standard"
C "github.com/Dreamacro/clash/constant"
"github.com/oschwald/geoip2-golang"
"os"
"runtime"
)
func UpdateGeoDatabases() error {
var (
tmpMMDB = C.Path.Resolve("temp_country.mmdb")
tmpGepIP = C.Path.Resolve("temp_geoip.dat")
tmpGeoSite = C.Path.Resolve("temp_geosite.dat")
)
if C.GeodataMode {
if err := downloadGeoIP(tmpGepIP); err != nil {
return fmt.Errorf("can't download MMDB database file: %w", err)
}
if err := verifyGeoSite("temp_geoip.dat"); err != nil {
_ = os.Remove(tmpGepIP)
return fmt.Errorf("invalid GeoIP database file: %s", err)
}
if err := os.Rename(tmpGepIP, C.Path.GeoIP()); err != nil {
return fmt.Errorf("can't rename MMDB database file: %w", err)
}
} else {
if err := downloadMMDB(tmpMMDB); err != nil {
return fmt.Errorf("can't download MMDB database file: %w", err)
}
if err := verifyMMDB("temp_country.mmdb"); err != nil {
_ = os.Remove(tmpMMDB)
return fmt.Errorf("invalid MMDB database file: %s", 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("temp_geosite.dat"); err != nil {
_ = os.Remove(tmpGeoSite)
return fmt.Errorf("invalid GeoSite database file: %s", err)
}
if err := os.Rename(tmpGeoSite, C.Path.GeoSite()); err != nil {
return fmt.Errorf("can't rename GeoSite database file: %w", err)
}
runtime.GC()
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")
return err
}

View file

@ -1,3 +1,8 @@
package constant
var GeodataMode bool
var (
GeodataMode bool
GeoIpUrl string
MmdbUrl string
GeoSiteUrl string
)

View file

@ -3,6 +3,7 @@ package route
import (
"net/http"
"path/filepath"
"sync"
"github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/config"
@ -16,10 +17,16 @@ import (
"github.com/go-chi/render"
)
var (
updateGeoMux sync.Mutex
updatingGeo = false
)
func configRouter() http.Handler {
r := chi.NewRouter()
r.Get("/", getConfigs)
r.Put("/", updateConfigs)
r.Post("/geo", updateGeoDatabases)
r.Patch("/", patchConfigs)
return r
}
@ -143,3 +150,42 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
executor.ApplyConfig(cfg, force)
render.NoContent(w, 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)
}