Feature: update config API support raw yaml payload

This commit is contained in:
gVisor bot 2019-12-01 13:22:47 +08:00
parent 45b938ea42
commit 88e7005382
4 changed files with 94 additions and 60 deletions

View file

@ -2,11 +2,9 @@ package config
import ( import (
"fmt" "fmt"
"io/ioutil"
"net" "net"
"net/url" "net/url"
"os" "os"
"path/filepath"
"strings" "strings"
adapters "github.com/Dreamacro/clash/adapters/outbound" adapters "github.com/Dreamacro/clash/adapters/outbound"
@ -109,40 +107,12 @@ type rawConfig struct {
Rule []string `yaml:"Rule"` Rule []string `yaml:"Rule"`
} }
// forward compatibility before 1.0 // Parse config
func readRawConfig(path string) ([]byte, error) { func Parse(buf []byte) (*Config, error) {
data, err := ioutil.ReadFile(path) config := &Config{}
if err == nil && len(data) != 0 {
return data, nil
}
if filepath.Ext(path) != ".yaml" {
return nil, err
}
path = path[:len(path)-5] + ".yml"
if _, fallbackErr := os.Stat(path); fallbackErr == nil {
return ioutil.ReadFile(path)
}
return data, err
}
func readConfig(path string) (*rawConfig, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
return nil, err
}
data, err := readRawConfig(path)
if err != nil {
return nil, err
}
if len(data) == 0 {
return nil, fmt.Errorf("Configuration file %s is empty", path)
}
// config with some default value // config with some default value
rawConfig := &rawConfig{ rawCfg := &rawConfig{
AllowLan: false, AllowLan: false,
BindAddress: "*", BindAddress: "*",
Mode: T.Rule, Mode: T.Rule,
@ -164,18 +134,10 @@ func readConfig(path string) (*rawConfig, error) {
}, },
}, },
} }
err = yaml.Unmarshal([]byte(data), &rawConfig) if err := yaml.Unmarshal(buf, &rawCfg); err != nil {
return rawConfig, err
}
// Parse config
func Parse(path string) (*Config, error) {
config := &Config{}
rawCfg, err := readConfig(path)
if err != nil {
return nil, err return nil, err
} }
config.Experimental = &rawCfg.Experimental config.Experimental = &rawCfg.Experimental
general, err := parseGeneral(rawCfg) general, err := parseGeneral(rawCfg)
@ -226,9 +188,7 @@ func parseGeneral(cfg *rawConfig) (*General, error) {
logLevel := cfg.LogLevel logLevel := cfg.LogLevel
if externalUI != "" { if externalUI != "" {
if !filepath.IsAbs(externalUI) { externalUI = C.Path.Reslove(externalUI)
externalUI = filepath.Join(C.Path.HomeDir(), externalUI)
}
if _, err := os.Stat(externalUI); os.IsNotExist(err) { if _, err := os.Stat(externalUI); os.IsNotExist(err) {
return nil, fmt.Errorf("external-ui: %s not exist", externalUI) return nil, fmt.Errorf("external-ui: %s not exist", externalUI)

View file

@ -3,6 +3,7 @@ package constant
import ( import (
"os" "os"
P "path" P "path"
"path/filepath"
) )
const Name = "clash" const Name = "clash"
@ -43,6 +44,15 @@ func (p *path) Config() string {
return p.configFile return p.configFile
} }
// Reslove return a absolute path or a relative path with homedir
func (p *path) Reslove(path string) string {
if !filepath.IsAbs(path) {
return filepath.Join(p.HomeDir(), path)
}
return path
}
func (p *path) MMDB() string { func (p *path) MMDB() string {
return P.Join(p.homeDir, "Country.mmdb") return P.Join(p.homeDir, "Country.mmdb")
} }

View file

@ -1,6 +1,11 @@
package executor package executor
import ( import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/auth"
trie "github.com/Dreamacro/clash/component/domain-trie" trie "github.com/Dreamacro/clash/component/domain-trie"
"github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/config"
@ -12,6 +17,41 @@ import (
T "github.com/Dreamacro/clash/tunnel" T "github.com/Dreamacro/clash/tunnel"
) )
// forward compatibility before 1.0
func readRawConfig(path string) ([]byte, error) {
data, err := ioutil.ReadFile(path)
if err == nil && len(data) != 0 {
return data, nil
}
if filepath.Ext(path) != ".yaml" {
return nil, err
}
path = path[:len(path)-5] + ".yml"
if _, fallbackErr := os.Stat(path); fallbackErr == nil {
return ioutil.ReadFile(path)
}
return data, err
}
func readConfig(path string) ([]byte, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
return nil, err
}
data, err := readRawConfig(path)
if err != nil {
return nil, err
}
if len(data) == 0 {
return nil, fmt.Errorf("Configuration file %s is empty", path)
}
return data, err
}
// Parse config with default config path // Parse config with default config path
func Parse() (*config.Config, error) { func Parse() (*config.Config, error) {
return ParseWithPath(C.Path.Config()) return ParseWithPath(C.Path.Config())
@ -19,7 +59,17 @@ func Parse() (*config.Config, error) {
// ParseWithPath parse config with custom config path // ParseWithPath parse config with custom config path
func ParseWithPath(path string) (*config.Config, error) { func ParseWithPath(path string) (*config.Config, error) {
return config.Parse(path) buf, err := readConfig(path)
if err != nil {
return nil, err
}
return config.Parse(buf)
}
// Parse config with default config path
func ParseWithBytes(buf []byte) (*config.Config, error) {
return ParseWithPath(C.Path.Config())
} }
// ApplyConfig dispatch configure to all parts // ApplyConfig dispatch configure to all parts

View file

@ -4,6 +4,7 @@ import (
"net/http" "net/http"
"path/filepath" "path/filepath"
"github.com/Dreamacro/clash/config"
"github.com/Dreamacro/clash/hub/executor" "github.com/Dreamacro/clash/hub/executor"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
P "github.com/Dreamacro/clash/proxy" P "github.com/Dreamacro/clash/proxy"
@ -78,6 +79,7 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
type updateConfigRequest struct { type updateConfigRequest struct {
Path string `json:"path"` Path string `json:"path"`
Payload string `json:"payload"`
} }
func updateConfigs(w http.ResponseWriter, r *http.Request) { func updateConfigs(w http.ResponseWriter, r *http.Request) {
@ -88,19 +90,31 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
return return
} }
force := r.URL.Query().Get("force") == "true"
var cfg *config.Config
var err error
if req.Payload != "" {
cfg, err = executor.ParseWithBytes([]byte(req.Payload))
if err != nil {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError(err.Error()))
return
}
} else {
if !filepath.IsAbs(req.Path) { if !filepath.IsAbs(req.Path) {
render.Status(r, http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError("path is not a absoluted path")) render.JSON(w, r, newError("path is not a absoluted path"))
return return
} }
force := r.URL.Query().Get("force") == "true" cfg, err = executor.ParseWithPath(req.Path)
cfg, err := executor.ParseWithPath(req.Path)
if err != nil { if err != nil {
render.Status(r, http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError(err.Error())) render.JSON(w, r, newError(err.Error()))
return return
} }
}
executor.ApplyConfig(cfg, force) executor.ApplyConfig(cfg, force)
render.NoContent(w, r) render.NoContent(w, r)