feat: download/upgrade XD to external-ui
This commit is contained in:
parent
af99b52527
commit
c3d72f6883
7 changed files with 165 additions and 23 deletions
|
@ -1,17 +1,11 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/geodata"
|
"github.com/Dreamacro/clash/component/geodata"
|
||||||
_ "github.com/Dreamacro/clash/component/geodata/standard"
|
_ "github.com/Dreamacro/clash/component/geodata/standard"
|
||||||
clashHttp "github.com/Dreamacro/clash/component/http"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
"github.com/oschwald/maxminddb-golang"
|
"github.com/oschwald/maxminddb-golang"
|
||||||
|
@ -72,19 +66,3 @@ func UpdateGeoDatabases() error {
|
||||||
|
|
||||||
return nil
|
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
123
config/update_xd.go
Normal 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
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,15 +1,37 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
"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) {
|
func trimArr(arr []string) (r []string) {
|
||||||
for _, e := range arr {
|
for _, e := range arr {
|
||||||
r = append(r, strings.Trim(e, " "))
|
r = append(r, strings.Trim(e, " "))
|
||||||
|
|
|
@ -15,6 +15,7 @@ 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
|
||||||
|
|
|
@ -49,6 +49,7 @@ 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,
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/config"
|
||||||
"github.com/Dreamacro/clash/hub/updater"
|
"github.com/Dreamacro/clash/hub/updater"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
|
@ -15,6 +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)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,3 +45,18 @@ func upgrade(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
go restartExecutable(execPath)
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ var (
|
||||||
workDir string
|
workDir string
|
||||||
|
|
||||||
// mu protects all fields below.
|
// mu protects all fields below.
|
||||||
mu sync.RWMutex
|
mu sync.Mutex
|
||||||
|
|
||||||
currentExeName string // 当前可执行文件
|
currentExeName string // 当前可执行文件
|
||||||
updateDir string // 更新目录
|
updateDir string // 更新目录
|
||||||
|
|
Loading…
Reference in a new issue