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:
parent
09690122e4
commit
6f2dc0336f
6 changed files with 160 additions and 12 deletions
|
@ -21,16 +21,16 @@ func SetLoader(newLoader string) {
|
||||||
geoLoaderName = newLoader
|
geoLoaderName = newLoader
|
||||||
}
|
}
|
||||||
|
|
||||||
func Verify(name string) bool {
|
func Verify(name string) error {
|
||||||
switch name {
|
switch name {
|
||||||
case C.GeositeName:
|
case C.GeositeName:
|
||||||
_, _, err := LoadGeoSiteMatcher("CN")
|
_, _, err := LoadGeoSiteMatcher("CN")
|
||||||
return err == nil
|
return err
|
||||||
case C.GeoipName:
|
case C.GeoipName:
|
||||||
_, _, err := LoadGeoIPMatcher("CN")
|
_, _, err := LoadGeoIPMatcher("CN")
|
||||||
return err == nil
|
return err
|
||||||
default:
|
default:
|
||||||
return false
|
return fmt.Errorf("not support name")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -219,11 +219,18 @@ type RawConfig struct {
|
||||||
IPTables IPTables `yaml:"iptables"`
|
IPTables IPTables `yaml:"iptables"`
|
||||||
Experimental Experimental `yaml:"experimental"`
|
Experimental Experimental `yaml:"experimental"`
|
||||||
Profile Profile `yaml:"profile"`
|
Profile Profile `yaml:"profile"`
|
||||||
|
GeoXUrl RawGeoXUrl `yaml:"geox-url"`
|
||||||
Proxy []map[string]any `yaml:"proxies"`
|
Proxy []map[string]any `yaml:"proxies"`
|
||||||
ProxyGroup []map[string]any `yaml:"proxy-groups"`
|
ProxyGroup []map[string]any `yaml:"proxy-groups"`
|
||||||
Rule []string `yaml:"rules"`
|
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 {
|
type RawSniffer struct {
|
||||||
Enable bool `yaml:"enable" json:"enable"`
|
Enable bool `yaml:"enable" json:"enable"`
|
||||||
Sniffing []string `yaml:"sniffing" json:"sniffing"`
|
Sniffing []string `yaml:"sniffing" json:"sniffing"`
|
||||||
|
@ -309,6 +316,11 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||||
Profile: Profile{
|
Profile: Profile{
|
||||||
StoreSelected: true,
|
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 {
|
if err := yaml.Unmarshal(buf, rawCfg); err != nil {
|
||||||
|
|
|
@ -15,7 +15,7 @@ import (
|
||||||
var initMode = true
|
var initMode = true
|
||||||
|
|
||||||
func downloadMMDB(path string) (err error) {
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ func downloadMMDB(path string) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadGeoIP(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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ func downloadGeoIP(path string) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadGeoSite(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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -74,8 +74,8 @@ func initGeoSite() error {
|
||||||
log.Infoln("Download GeoSite.dat finish")
|
log.Infoln("Download GeoSite.dat finish")
|
||||||
}
|
}
|
||||||
if initMode {
|
if initMode {
|
||||||
if !geodata.Verify(C.GeositeName) {
|
if err := geodata.Verify(C.GeositeName); err != nil {
|
||||||
log.Warnln("GeoSite.dat invalid, remove and download")
|
log.Warnln("GeoSite.dat invalid, remove and download: %s", err)
|
||||||
if err := os.Remove(C.Path.GeoSite()); err != nil {
|
if err := os.Remove(C.Path.GeoSite()); err != nil {
|
||||||
return fmt.Errorf("can't remove invalid GeoSite.dat: %s", err.Error())
|
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")
|
log.Infoln("Download GeoIP.dat finish")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !geodata.Verify(C.GeoipName) {
|
if err := geodata.Verify(C.GeoipName); err != nil {
|
||||||
log.Warnln("GeoIP.dat invalid, remove and download")
|
log.Warnln("GeoIP.dat invalid, remove and download: %s", err)
|
||||||
if err := os.Remove(C.Path.GeoIP()); err != nil {
|
if err := os.Remove(C.Path.GeoIP()); err != nil {
|
||||||
return fmt.Errorf("can't remove invalid GeoIP.dat: %s", err.Error())
|
return fmt.Errorf("can't remove invalid GeoIP.dat: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
@ -158,6 +158,9 @@ func Init(dir string) error {
|
||||||
}
|
}
|
||||||
if !C.GeodataMode {
|
if !C.GeodataMode {
|
||||||
C.GeodataMode = rawCfg.GeodataMode
|
C.GeodataMode = rawCfg.GeodataMode
|
||||||
|
C.GeoIpUrl = rawCfg.GeoXUrl.GeoIp
|
||||||
|
C.GeoSiteUrl = rawCfg.GeoXUrl.GeoSite
|
||||||
|
C.MmdbUrl = rawCfg.GeoXUrl.Mmdb
|
||||||
}
|
}
|
||||||
// initial GeoIP
|
// initial GeoIP
|
||||||
if err := initGeoIP(); err != nil {
|
if err := initGeoIP(); err != nil {
|
||||||
|
|
82
config/updateGeo.go
Normal file
82
config/updateGeo.go
Normal 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
|
||||||
|
}
|
|
@ -1,3 +1,8 @@
|
||||||
package constant
|
package constant
|
||||||
|
|
||||||
var GeodataMode bool
|
var (
|
||||||
|
GeodataMode bool
|
||||||
|
GeoIpUrl string
|
||||||
|
MmdbUrl string
|
||||||
|
GeoSiteUrl string
|
||||||
|
)
|
||||||
|
|
|
@ -3,6 +3,7 @@ package route
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
"github.com/Dreamacro/clash/config"
|
"github.com/Dreamacro/clash/config"
|
||||||
|
@ -16,10 +17,16 @@ 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)
|
||||||
r.Put("/", updateConfigs)
|
r.Put("/", updateConfigs)
|
||||||
|
r.Post("/geo", updateGeoDatabases)
|
||||||
r.Patch("/", patchConfigs)
|
r.Patch("/", patchConfigs)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
@ -143,3 +150,42 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
|
||||||
executor.ApplyConfig(cfg, force)
|
executor.ApplyConfig(cfg, force)
|
||||||
render.NoContent(w, r)
|
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)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue