diff --git a/config/config.go b/config/config.go index db021618..a2e64026 100644 --- a/config/config.go +++ b/config/config.go @@ -8,6 +8,8 @@ import ( "net/netip" "net/url" "os" + "path" + "path/filepath" "regexp" "strings" "time" @@ -277,6 +279,8 @@ type RawConfig struct { ExternalController string `yaml:"external-controller"` ExternalControllerTLS string `yaml:"external-controller-tls"` ExternalUI string `yaml:"external-ui"` + ExternalUIURL string `yaml:"external-ui-url" json:"external-ui-url"` + ExternalUIName string `yaml:"external-ui-name" json:"external-ui-name"` Secret string `yaml:"secret"` Interface string `yaml:"interface-name"` RoutingMark int `yaml:"routing-mark"` @@ -569,7 +573,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { } func parseGeneral(cfg *RawConfig) (*General, error) { - externalUI := cfg.ExternalUI geodata.SetLoader(cfg.GeodataLoader) C.GeoIpUrl = cfg.GeoXUrl.GeoIp C.GeoSiteUrl = cfg.GeoXUrl.GeoSite @@ -580,14 +583,34 @@ func parseGeneral(cfg *RawConfig) (*General, error) { N.KeepAliveInterval = time.Duration(cfg.KeepAliveInterval) * time.Second } - log.Debugln("TCP Keep Alive Interval set %+v", N.KeepAliveInterval) + if cfg.ExternalUIURL != "" { + ExternalUIURL = cfg.ExternalUIURL + } + ExternalUIPath = cfg.ExternalUI // checkout externalUI exist - if externalUI != "" { - externalUI = C.Path.Resolve(externalUI) - if _, err := os.Stat(externalUI); os.IsNotExist(err) { - return nil, fmt.Errorf("external-ui: %s not exist", externalUI) + if ExternalUIPath != "" { + ExternalUIPath = C.Path.Resolve(ExternalUIPath) + if _, err := os.Stat(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) + if err := os.MkdirAll(defaultUIpath, os.ModePerm); err != nil { + return nil, err + } + ExternalUIPath = defaultUIpath + cfg.ExternalUI = defaultUIpath } } + // checkout UIpath/name exist + if cfg.ExternalUIName != "" { + ExternalUIName = cfg.ExternalUIName + ExternalUIFolder = filepath.Clean(path.Join(ExternalUIPath, cfg.ExternalUIName)) + if _, err := os.Stat(ExternalUIPath); os.IsNotExist(err) { + if err := os.MkdirAll(ExternalUIPath, os.ModePerm); err != nil { + return nil, err + } + } + } + cfg.Tun.RedirectToTun = cfg.EBpf.RedirectToTun return &General{ Inbound: Inbound{ diff --git a/config/update_xd.go b/config/update_ui.go similarity index 54% rename from config/update_xd.go rename to config/update_ui.go index 696768b4..81b5bb03 100644 --- a/config/update_xd.go +++ b/config/update_ui.go @@ -9,112 +9,109 @@ import ( "path/filepath" "strings" "sync" - - C "github.com/Dreamacro/clash/constant" ) -const xdURL = "https://codeload.github.com/MetaCubeX/metacubexd/zip/refs/heads/gh-pages" +var ( + ExternalUIURL string + ExternalUIPath string + ExternalUIFolder string + ExternalUIName string +) var xdMutex sync.Mutex -func UpdateXD() error { +func UpdateUI() error { xdMutex.Lock() defer xdMutex.Unlock() - err := cleanup(C.UIPath) - if err != nil { - return fmt.Errorf("cleanup exist file error: %w", err) + if ExternalUIPath == "" || ExternalUIFolder == "" || ExternalUIName == "" { + return fmt.Errorf("ExternalUI configure incomplete") } - data, err := downloadForBytes(xdURL) + err := cleanup(ExternalUIFolder) if err != nil { - return fmt.Errorf("can't download XD file: %w", err) + if !os.IsNotExist(err) { + return fmt.Errorf("cleanup exist file error: %w", err) + } } - saved := path.Join(C.UIPath, "xd.zip") + data, err := downloadForBytes(ExternalUIURL) + if err != nil { + return fmt.Errorf("can't download file: %w", err) + } + + saved := path.Join(ExternalUIPath, "download.zip") if saveFile(data, saved) != nil { - return fmt.Errorf("can't save XD zip file: %w", err) + return fmt.Errorf("can't save zip file: %w", err) } defer os.Remove(saved) - err = unzip(saved, C.UIPath) + unzipFolder, err := unzip(saved, ExternalUIPath) if err != nil { - return fmt.Errorf("can't extract XD zip file: %w", err) + return fmt.Errorf("can't extract zip file: %w", err) } - err = os.Rename(path.Join(C.UIPath, "metacubexd-gh-pages"), path.Join(C.UIPath, "xd")) + err = os.Rename(unzipFolder, ExternalUIFolder) if err != nil { return fmt.Errorf("can't rename folder: %w", err) } return nil } -func unzip(src, dest string) error { +func unzip(src, dest string) (string, error) { r, err := zip.OpenReader(src) if err != nil { - return err + return "", err } defer r.Close() - + var extractedFolder string for _, f := range r.File { fpath := filepath.Join(dest, f.Name) - if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) { - return fmt.Errorf("invalid file path: %s", fpath) + return "", fmt.Errorf("invalid file path: %s", fpath) } - if f.FileInfo().IsDir() { os.MkdirAll(fpath, os.ModePerm) continue } - if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil { - return err + return "", err } - outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) if err != nil { - return err + return "", err } - rc, err := f.Open() if err != nil { - return err + return "", err } - _, err = io.Copy(outFile, rc) - outFile.Close() rc.Close() - if err != nil { - return err + return "", err + } + if extractedFolder == "" { + extractedFolder = filepath.Dir(fpath) } } - return nil + return extractedFolder, nil } func cleanup(root string) error { + if _, err := os.Stat(root); os.IsNotExist(err) { + return nil + } return filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil { return err } - if path == root { - // skip root itself - return nil - } if info.IsDir() { if err := os.RemoveAll(path); err != nil { - if os.IsNotExist(err) { - return nil - } return err } } else { if err := os.Remove(path); err != nil { - if os.IsNotExist(err) { - return nil - } return err } } diff --git a/constant/path.go b/constant/path.go index e595d920..d7477e0e 100644 --- a/constant/path.go +++ b/constant/path.go @@ -15,7 +15,6 @@ const Name = "clash" var ( GeositeName = "GeoSite.dat" GeoipName = "GeoIP.dat" - UIPath = "" ) // Path is used to get the configuration path diff --git a/docs/config.yaml b/docs/config.yaml index ada57905..274eaedd 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -41,7 +41,11 @@ external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要 # secret: "123456" # `Authorization:Bearer ${secret}` # tcp-concurrent: true # TCP 并发连接所有 IP, 将使用最快握手的 TCP -external-ui: /path/to/ui/folder # 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问 + +# 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问 +external-ui: /path/to/ui/folder/ +external-ui-name: xd +external-ui-url: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip" # interface-name: en0 # 设置出口网卡 diff --git a/hub/route/server.go b/hub/route/server.go index 3df6196a..d2fecd05 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -49,7 +49,6 @@ type Memory struct { func SetUIPath(path string) { uiPath = C.Path.Resolve(path) - C.UIPath = uiPath } func Start(addr string, tlsAddr string, secret string, diff --git a/hub/route/upgrade.go b/hub/route/upgrade.go index ef5dffaf..be226616 100644 --- a/hub/route/upgrade.go +++ b/hub/route/upgrade.go @@ -16,7 +16,7 @@ import ( func upgradeRouter() http.Handler { r := chi.NewRouter() r.Post("/", upgrade) - r.Post("/xd", updateXD) + r.Post("/ui", updateUI) return r } @@ -46,8 +46,8 @@ func upgrade(w http.ResponseWriter, r *http.Request) { go restartExecutable(execPath) } -func updateXD(w http.ResponseWriter, r *http.Request) { - err := config.UpdateXD() +func updateUI(w http.ResponseWriter, r *http.Request) { + err := config.UpdateUI() if err != nil { log.Warnln("%s", err) render.Status(r, http.StatusInternalServerError)