Feature: update config API support raw yaml payload
This commit is contained in:
parent
45b938ea42
commit
88e7005382
4 changed files with 94 additions and 60 deletions
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue