feat: download/upgrade XD to external-ui

This commit is contained in:
Larvan2 2023-09-15 23:25:56 +08:00 committed by Larvan2
parent af99b52527
commit c3d72f6883
7 changed files with 165 additions and 23 deletions

View file

@ -1,17 +1,11 @@
package config
import (
"context"
"fmt"
"io"
"net/http"
"os"
"runtime"
"time"
"github.com/Dreamacro/clash/component/geodata"
_ "github.com/Dreamacro/clash/component/geodata/standard"
clashHttp "github.com/Dreamacro/clash/component/http"
C "github.com/Dreamacro/clash/constant"
"github.com/oschwald/maxminddb-golang"
@ -72,19 +66,3 @@ func UpdateGeoDatabases() error {
return nil
}
func downloadForBytes(url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel()
resp, err := clashHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {"clash"}}, 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)
}

123
config/update_xd.go Normal file
View file

@ -0,0 +1,123 @@
package config
import (
"archive/zip"
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"
"sync"
C "github.com/Dreamacro/clash/constant"
)
const xdURL = "https://codeload.github.com/MetaCubeX/metacubexd/zip/refs/heads/gh-pages"
var xdMutex sync.Mutex
func UpdateXD() error {
xdMutex.Lock()
defer xdMutex.Unlock()
err := cleanup(C.UIPath)
if err != nil {
return fmt.Errorf("cleanup exist file error: %w", err)
}
data, err := downloadForBytes(xdURL)
if err != nil {
return fmt.Errorf("can't download XD file: %w", err)
}
saved := path.Join(C.UIPath, "xd.zip")
if saveFile(data, saved) != nil {
return fmt.Errorf("can't save XD zip file: %w", err)
}
defer os.Remove(saved)
err = unzip(saved, C.UIPath)
if err != nil {
return fmt.Errorf("can't extract XD zip file: %w", err)
}
err = os.Rename(path.Join(C.UIPath, "metacubexd-gh-pages"), path.Join(C.UIPath, "xd"))
if err != nil {
return fmt.Errorf("can't rename folder: %w", err)
}
return nil
}
func unzip(src, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()
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)
}
if f.FileInfo().IsDir() {
os.MkdirAll(fpath, os.ModePerm)
continue
}
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
return err
}
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
rc, err := f.Open()
if err != nil {
return err
}
_, err = io.Copy(outFile, rc)
outFile.Close()
rc.Close()
if err != nil {
return err
}
}
return nil
}
func cleanup(root string) error {
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
}
}
return nil
})
}

View file

@ -1,15 +1,37 @@
package config
import (
"context"
"fmt"
"io"
"net"
"net/http"
"net/netip"
"os"
"strings"
"time"
"github.com/Dreamacro/clash/adapter/outboundgroup"
"github.com/Dreamacro/clash/common/structure"
clashHttp "github.com/Dreamacro/clash/component/http"
)
func downloadForBytes(url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel()
resp, err := clashHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {"clash"}}, 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) {
for _, e := range arr {
r = append(r, strings.Trim(e, " "))

View file

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

View file

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

View file

@ -5,6 +5,7 @@ import (
"net/http"
"os"
"github.com/Dreamacro/clash/config"
"github.com/Dreamacro/clash/hub/updater"
"github.com/Dreamacro/clash/log"
@ -15,6 +16,7 @@ import (
func upgradeRouter() http.Handler {
r := chi.NewRouter()
r.Post("/", upgrade)
r.Post("/xd", updateXD)
return r
}
@ -43,3 +45,18 @@ func upgrade(w http.ResponseWriter, r *http.Request) {
go restartExecutable(execPath)
}
func updateXD(w http.ResponseWriter, r *http.Request) {
err := config.UpdateXD()
if err != nil {
log.Warnln("%s", err)
render.Status(r, http.StatusInternalServerError)
render.JSON(w, r, newError(fmt.Sprintf("%s", err)))
return
}
render.JSON(w, r, render.M{"status": "ok"})
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
}

View file

@ -32,7 +32,7 @@ var (
workDir string
// mu protects all fields below.
mu sync.RWMutex
mu sync.Mutex
currentExeName string // 当前可执行文件
updateDir string // 更新目录