feat: update external-ui

This commit is contained in:
Larvan2 2023-09-18 19:21:30 +08:00
parent 6a5a94f48f
commit 7c21768e99
6 changed files with 75 additions and 53 deletions

View file

@ -8,6 +8,8 @@ import (
"net/netip" "net/netip"
"net/url" "net/url"
"os" "os"
"path"
"path/filepath"
"regexp" "regexp"
"strings" "strings"
"time" "time"
@ -277,6 +279,8 @@ type RawConfig struct {
ExternalController string `yaml:"external-controller"` ExternalController string `yaml:"external-controller"`
ExternalControllerTLS string `yaml:"external-controller-tls"` ExternalControllerTLS string `yaml:"external-controller-tls"`
ExternalUI string `yaml:"external-ui"` 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"` Secret string `yaml:"secret"`
Interface string `yaml:"interface-name"` Interface string `yaml:"interface-name"`
RoutingMark int `yaml:"routing-mark"` RoutingMark int `yaml:"routing-mark"`
@ -569,7 +573,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
} }
func parseGeneral(cfg *RawConfig) (*General, error) { func parseGeneral(cfg *RawConfig) (*General, error) {
externalUI := cfg.ExternalUI
geodata.SetLoader(cfg.GeodataLoader) geodata.SetLoader(cfg.GeodataLoader)
C.GeoIpUrl = cfg.GeoXUrl.GeoIp C.GeoIpUrl = cfg.GeoXUrl.GeoIp
C.GeoSiteUrl = cfg.GeoXUrl.GeoSite C.GeoSiteUrl = cfg.GeoXUrl.GeoSite
@ -580,14 +583,34 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
N.KeepAliveInterval = time.Duration(cfg.KeepAliveInterval) * time.Second 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 // checkout externalUI exist
if externalUI != "" { if ExternalUIPath != "" {
externalUI = C.Path.Resolve(externalUI) ExternalUIPath = C.Path.Resolve(ExternalUIPath)
if _, err := os.Stat(externalUI); os.IsNotExist(err) { if _, err := os.Stat(ExternalUIPath); os.IsNotExist(err) {
return nil, fmt.Errorf("external-ui: %s not exist", externalUI) 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 cfg.Tun.RedirectToTun = cfg.EBpf.RedirectToTun
return &General{ return &General{
Inbound: Inbound{ Inbound: Inbound{

View file

@ -9,112 +9,109 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"sync" "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 var xdMutex sync.Mutex
func UpdateXD() error { func UpdateUI() error {
xdMutex.Lock() xdMutex.Lock()
defer xdMutex.Unlock() defer xdMutex.Unlock()
err := cleanup(C.UIPath) if ExternalUIPath == "" || ExternalUIFolder == "" || ExternalUIName == "" {
if err != nil { return fmt.Errorf("ExternalUI configure incomplete")
return fmt.Errorf("cleanup exist file error: %w", err)
} }
data, err := downloadForBytes(xdURL) err := cleanup(ExternalUIFolder)
if err != nil { 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 { 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) defer os.Remove(saved)
err = unzip(saved, C.UIPath) unzipFolder, err := unzip(saved, ExternalUIPath)
if err != nil { 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 { if err != nil {
return fmt.Errorf("can't rename folder: %w", err) return fmt.Errorf("can't rename folder: %w", err)
} }
return nil return nil
} }
func unzip(src, dest string) error { func unzip(src, dest string) (string, error) {
r, err := zip.OpenReader(src) r, err := zip.OpenReader(src)
if err != nil { if err != nil {
return err return "", err
} }
defer r.Close() defer r.Close()
var extractedFolder string
for _, f := range r.File { for _, f := range r.File {
fpath := filepath.Join(dest, f.Name) fpath := filepath.Join(dest, f.Name)
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) { 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() { if f.FileInfo().IsDir() {
os.MkdirAll(fpath, os.ModePerm) os.MkdirAll(fpath, os.ModePerm)
continue continue
} }
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil { 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()) outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil { if err != nil {
return err return "", err
} }
rc, err := f.Open() rc, err := f.Open()
if err != nil { if err != nil {
return err return "", err
} }
_, err = io.Copy(outFile, rc) _, err = io.Copy(outFile, rc)
outFile.Close() outFile.Close()
rc.Close() rc.Close()
if err != nil { if err != nil {
return err return "", err
}
if extractedFolder == "" {
extractedFolder = filepath.Dir(fpath)
} }
} }
return nil return extractedFolder, nil
} }
func cleanup(root string) error { 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 { return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
return err return err
} }
if path == root {
// skip root itself
return nil
}
if info.IsDir() { if info.IsDir() {
if err := os.RemoveAll(path); err != nil { if err := os.RemoveAll(path); err != nil {
if os.IsNotExist(err) {
return nil
}
return err return err
} }
} else { } else {
if err := os.Remove(path); err != nil { if err := os.Remove(path); err != nil {
if os.IsNotExist(err) {
return nil
}
return err return err
} }
} }

View file

@ -15,7 +15,6 @@ const Name = "clash"
var ( var (
GeositeName = "GeoSite.dat" GeositeName = "GeoSite.dat"
GeoipName = "GeoIP.dat" GeoipName = "GeoIP.dat"
UIPath = ""
) )
// Path is used to get the configuration path // Path is used to get the configuration path

View file

@ -41,7 +41,11 @@ external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要
# secret: "123456" # `Authorization:Bearer ${secret}` # secret: "123456" # `Authorization:Bearer ${secret}`
# tcp-concurrent: true # TCP 并发连接所有 IP, 将使用最快握手的 TCP # 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 # 设置出口网卡 # interface-name: en0 # 设置出口网卡

View file

@ -49,7 +49,6 @@ type Memory struct {
func SetUIPath(path string) { func SetUIPath(path string) {
uiPath = C.Path.Resolve(path) uiPath = C.Path.Resolve(path)
C.UIPath = uiPath
} }
func Start(addr string, tlsAddr string, secret string, func Start(addr string, tlsAddr string, secret string,

View file

@ -16,7 +16,7 @@ import (
func upgradeRouter() http.Handler { func upgradeRouter() http.Handler {
r := chi.NewRouter() r := chi.NewRouter()
r.Post("/", upgrade) r.Post("/", upgrade)
r.Post("/xd", updateXD) r.Post("/ui", updateUI)
return r return r
} }
@ -46,8 +46,8 @@ func upgrade(w http.ResponseWriter, r *http.Request) {
go restartExecutable(execPath) go restartExecutable(execPath)
} }
func updateXD(w http.ResponseWriter, r *http.Request) { func updateUI(w http.ResponseWriter, r *http.Request) {
err := config.UpdateXD() err := config.UpdateUI()
if err != nil { if err != nil {
log.Warnln("%s", err) log.Warnln("%s", err)
render.Status(r, http.StatusInternalServerError) render.Status(r, http.StatusInternalServerError)