From 904db6bd61785ecab7ffed017c66704739281444 Mon Sep 17 00:00:00 2001 From: vcptr <51714622+vcptr@users.noreply.github.com> Date: Sat, 14 Dec 2019 21:43:47 +0800 Subject: [PATCH] mconfig subcommand ready --- common/log/logger.go | 9 ++++ common/platform/ctlcmd/ctlcmd.go | 3 ++ infra/conf/serial/loader.go | 11 ++++- infra/conf/v2ray.go | 7 ++- infra/control/fetch.go | 44 ++++++++++-------- infra/control/main/main.go | 5 +- infra/control/mconfig.go | 78 ++++++++++++++++++++++++++++++++ 7 files changed, 135 insertions(+), 22 deletions(-) create mode 100644 infra/control/mconfig.go diff --git a/common/log/logger.go b/common/log/logger.go index 9a2591cb9..a6b8316b1 100644 --- a/common/log/logger.go +++ b/common/log/logger.go @@ -119,6 +119,15 @@ func CreateStdoutLogWriter() WriterCreator { } } +// CreateStderrLogWriter returns a LogWriterCreator that creates LogWriter for stderr. +func CreateStderrLogWriter() WriterCreator { + return func() Writer { + return &consoleLogWriter{ + logger: log.New(os.Stderr, "", log.Ldate|log.Ltime), + } + } +} + // CreateFileLogWriter returns a LogWriterCreator that creates LogWriter for the given file. func CreateFileLogWriter(path string) (WriterCreator, error) { file, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) diff --git a/common/platform/ctlcmd/ctlcmd.go b/common/platform/ctlcmd/ctlcmd.go index ed770b555..91c22e504 100644 --- a/common/platform/ctlcmd/ctlcmd.go +++ b/common/platform/ctlcmd/ctlcmd.go @@ -39,6 +39,9 @@ func Run(args []string, input io.Reader) (buf.MultiBuffer, error) { } return nil, newError(msg).Base(err) } + if !errBuffer.IsEmpty() { + newError("v2ctl > ", errBuffer.String()).AtInfo().WriteToLog() + } return outBuffer.MultiBuffer, nil } diff --git a/infra/conf/serial/loader.go b/infra/conf/serial/loader.go index 1d084778d..cdff522a4 100644 --- a/infra/conf/serial/loader.go +++ b/infra/conf/serial/loader.go @@ -38,7 +38,7 @@ func findOffset(b []byte, o int) *offset { return &offset{line: line, char: char} } -func LoadJSONConfig(reader io.Reader) (*core.Config, error) { +func DecodeJSONConfig(reader io.Reader) (*conf.Config, error) { jsonConfig := &conf.Config{} jsonContent := bytes.NewBuffer(make([]byte, 0, 10240)) @@ -62,6 +62,15 @@ func LoadJSONConfig(reader io.Reader) (*core.Config, error) { return nil, newError("failed to read config file").Base(err) } + return jsonConfig, nil +} + +func LoadJSONConfig(reader io.Reader) (*core.Config, error) { + jsonConfig, err := DecodeJSONConfig(reader) + if err != nil { + return nil, err + } + pbConfig, err := jsonConfig.Build() if err != nil { return nil, newError("failed to parse json config").Base(err) diff --git a/infra/conf/v2ray.go b/infra/conf/v2ray.go index afe1fee6d..1a666f134 100644 --- a/infra/conf/v2ray.go +++ b/infra/conf/v2ray.go @@ -327,7 +327,7 @@ func (c *Config) findOutboundTag(tag string) int { } // Override method accepts another Config overrides the current attribute -func (c *Config) Override(o *Config) { +func (c *Config) Override(o *Config, fn string) { // only process the non-deprecated members @@ -361,9 +361,10 @@ func (c *Config) Override(o *Config) { if len(c.InboundConfigs) > 0 && len(o.InboundConfigs) == 1 { if idx := c.findInboundTag(o.InboundConfigs[0].Tag); idx > -1 { c.InboundConfigs[idx] = o.InboundConfigs[0] - newError("updated inbound with tag: ", o.InboundConfigs[0].Tag).AtInfo().WriteToLog() + newError("<", fn, "> updated inbound with tag: ", o.InboundConfigs[0].Tag).AtInfo().WriteToLog() } else { c.InboundConfigs = append(c.InboundConfigs, o.InboundConfigs[0]) + newError("<", fn, "> appended inbound with tag: ", o.InboundConfigs[0].Tag).AtInfo().WriteToLog() } } else { c.InboundConfigs = o.InboundConfigs @@ -375,8 +376,10 @@ func (c *Config) Override(o *Config) { if len(c.OutboundConfigs) > 0 && len(o.OutboundConfigs) == 1 { if idx := c.findOutboundTag(o.OutboundConfigs[0].Tag); idx > -1 { c.OutboundConfigs[idx] = o.OutboundConfigs[0] + newError("<", fn, "> updated outbound with tag: ", o.OutboundConfigs[0].Tag).AtInfo().WriteToLog() } else { c.OutboundConfigs = append(c.OutboundConfigs, o.OutboundConfigs[0]) + newError("<", fn, "> updated outbound with tag: ", o.OutboundConfigs[0].Tag).AtInfo().WriteToLog() } } else { c.OutboundConfigs = o.OutboundConfigs diff --git a/infra/control/fetch.go b/infra/control/fetch.go index 167c7483c..81e600954 100644 --- a/infra/control/fetch.go +++ b/infra/control/fetch.go @@ -5,6 +5,7 @@ import ( "net/url" "os" "strings" + "time" "v2ray.com/core/common" "v2ray.com/core/common/buf" @@ -23,46 +24,53 @@ func (c *FetchCommand) Description() Description { } } -func (c *FetchCommand) isValidScheme(scheme string) bool { - scheme = strings.ToLower(scheme) - return scheme == "http" || scheme == "https" -} - func (c *FetchCommand) Execute(args []string) error { if len(args) < 1 { return newError("empty url") } - target := args[0] - parsedTarget, err := url.Parse(target) + content, err := FetchHTTPContent(args[0]) if err != nil { - return newError("invalid URL: ", target).Base(err) - } - if !c.isValidScheme(parsedTarget.Scheme) { - return newError("invalid scheme: ", parsedTarget.Scheme) + return newError("failed to read HTTP response").Base(err) } - client := &http.Client{} + os.Stdout.Write(content) + return nil +} + +func FetchHTTPContent(target string) ([]byte, error) { + + parsedTarget, err := url.Parse(target) + if err != nil { + return nil, newError("invalid URL: ", target).Base(err) + } + + if s := strings.ToLower(parsedTarget.Scheme); s != "http" && s != "https" { + return nil, newError("invalid scheme: ", parsedTarget.Scheme) + } + + client := &http.Client{ + Timeout: 30 * time.Second, + } resp, err := client.Do(&http.Request{ Method: "GET", URL: parsedTarget, Close: true, }) if err != nil { - return newError("failed to dial to ", target).Base(err) + return nil, newError("failed to dial to ", target).Base(err) } + defer resp.Body.Close() if resp.StatusCode != 200 { - return newError("unexpected HTTP status code: ", resp.StatusCode) + return nil, newError("unexpected HTTP status code: ", resp.StatusCode) } content, err := buf.ReadAllToBytes(resp.Body) if err != nil { - return newError("failed to read HTTP response").Base(err) + return nil, newError("failed to read HTTP response").Base(err) } - os.Stdout.Write(content) - - return nil + return content, nil } func init() { diff --git a/infra/control/main/main.go b/infra/control/main/main.go index 5c41aeb0e..e78fa5d12 100644 --- a/infra/control/main/main.go +++ b/infra/control/main/main.go @@ -5,7 +5,8 @@ import ( "fmt" "os" - _ "v2ray.com/core/infra/conf/command" + commlog "v2ray.com/core/common/log" + // _ "v2ray.com/core/infra/conf/command" "v2ray.com/core/infra/control" ) @@ -17,6 +18,8 @@ func getCommandName() string { } func main() { + // let the v2ctl prints log at stderr + commlog.RegisterHandler(commlog.NewLogger(commlog.CreateStderrLogWriter())) name := getCommandName() cmd := control.GetCommand(name) if cmd == nil { diff --git a/infra/control/mconfig.go b/infra/control/mconfig.go new file mode 100644 index 000000000..5222df41b --- /dev/null +++ b/infra/control/mconfig.go @@ -0,0 +1,78 @@ +package control + +import ( + "bytes" + "io" + "io/ioutil" + "os" + "strings" + + "github.com/golang/protobuf/proto" + "v2ray.com/core/common" + "v2ray.com/core/infra/conf" + "v2ray.com/core/infra/conf/serial" +) + +type MconfigCommand struct{} + +func (c *MconfigCommand) Name() string { + return "mconfig" +} + +func (c *MconfigCommand) Description() Description { + return Description{ + Short: "merge multiple json config", + Usage: []string{"v2ctl mconfig 1.json 2.json .json"}, + } +} + +func (c *MconfigCommand) Execute(args []string) error { + if len(args) < 1 { + return newError("empty config list") + } + + conf := &conf.Config{} + for _, arg := range args { + r, err := c.LoadArg(arg) + common.Must(err) + c, err := serial.DecodeJSONConfig(r) + common.Must(err) + conf.Override(c, arg) + } + + pbConfig, err := conf.Build() + if err != nil { + return err + } + + bytesConfig, err := proto.Marshal(pbConfig) + if err != nil { + return newError("failed to marshal proto config").Base(err) + } + + if _, err := os.Stdout.Write(bytesConfig); err != nil { + return newError("failed to write proto config").Base(err) + } + + return nil +} + +func (c *MconfigCommand) LoadArg(arg string) (out io.Reader, err error) { + + var data []byte + if strings.HasPrefix(arg, "http://") || strings.HasPrefix(arg, "https://") { + data, err = FetchHTTPContent(arg) + } else { + data, err = ioutil.ReadFile(arg) + } + + if err != nil { + return + } + out = bytes.NewBuffer(data) + return +} + +func init() { + common.Must(RegisterCommand(&MconfigCommand{})) +}