diff --git a/api/panel/user.go b/api/panel/user.go index 8357a7d..c54f83f 100644 --- a/api/panel/user.go +++ b/api/panel/user.go @@ -2,6 +2,7 @@ package panel import ( "fmt" + log "github.com/sirupsen/logrus" "github.com/goccy/go-json" ) @@ -66,5 +67,6 @@ func (c *Client) ReportUserTraffic(userTraffic []UserTraffic) error { if err != nil { return err } + log.Println(r.String()) return nil } diff --git a/cmd/server.go b/cmd/server.go index 6dae1cf..18d5b11 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -1,19 +1,17 @@ package cmd import ( + "github.com/InazumaV/V2bX/conf" + vCore "github.com/InazumaV/V2bX/core" + "github.com/InazumaV/V2bX/limiter" + "github.com/InazumaV/V2bX/node" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "gopkg.in/natefinch/lumberjack.v2" "os" "os/signal" "runtime" "syscall" - - log "github.com/sirupsen/logrus" - - vCore "github.com/InazumaV/V2bX/core" - - "github.com/InazumaV/V2bX/conf" - "github.com/InazumaV/V2bX/limiter" - "github.com/InazumaV/V2bX/node" - "github.com/spf13/cobra" ) var ( @@ -56,6 +54,16 @@ func serverHandle(_ *cobra.Command, _ []string) { case "error": log.SetLevel(log.ErrorLevel) } + if c.LogConfig.Output != "" { + w := &lumberjack.Logger{ + Filename: c.LogConfig.Output, + MaxSize: 100, + MaxBackups: 3, + MaxAge: 28, + Compress: true, + } + log.SetOutput(w) + } limiter.Init() log.Info("Start V2bX...") vc, err := vCore.NewCore(c.CoresConfig) diff --git a/common/json5/json5.go b/common/json5/json5.go new file mode 100644 index 0000000..166ad78 --- /dev/null +++ b/common/json5/json5.go @@ -0,0 +1,112 @@ +package json5 + +import ( + "bytes" + "io" +) + +type TrimNodeReader struct { + r io.Reader + br *bytes.Reader +} + +func isNL(c byte) bool { + return c == '\n' || c == '\r' +} + +func isWS(c byte) bool { + return c == ' ' || c == '\t' || isNL(c) +} + +func consumeComment(s []byte, i int) int { + if i < len(s) && s[i] == '/' { + s[i-1] = ' ' + for ; i < len(s) && !isNL(s[i]); i += 1 { + s[i] = ' ' + } + } + if i < len(s) && s[i] == '*' { + s[i-1] = ' ' + s[i] = ' ' + for ; i < len(s); i += 1 { + if s[i] != '*' { + s[i] = ' ' + } else { + s[i] = ' ' + i++ + if i < len(s) { + if s[i] == '/' { + s[i] = ' ' + break + } + } + } + } + } + return i +} + +func prep(r io.Reader) (s []byte, err error) { + buf := &bytes.Buffer{} + _, err = io.Copy(buf, r) + s = buf.Bytes() + if err != nil { + return + } + + i := 0 + for i < len(s) { + switch s[i] { + case '"': + i += 1 + for i < len(s) { + if s[i] == '"' { + i += 1 + break + } else if s[i] == '\\' { + i += 1 + } + i += 1 + } + case '/': + i = consumeComment(s, i+1) + case ',': + j := i + for { + i += 1 + if i >= len(s) { + break + } else if s[i] == '}' || s[i] == ']' { + s[j] = ' ' + break + } else if s[i] == '/' { + i = consumeComment(s, i+1) + } else if !isWS(s[i]) { + break + } + } + default: + i += 1 + } + } + return +} + +// Read acts as a proxy for the underlying reader and cleans p +// of comments and trailing commas preceeding ] and } +// comments are delimitted by // up until the end the line +func (st *TrimNodeReader) Read(p []byte) (n int, err error) { + if st.br == nil { + var s []byte + if s, err = prep(st.r); err != nil { + return + } + st.br = bytes.NewReader(s) + } + return st.br.Read(p) +} + +// NewTrimNodeReader New returns an io.Reader acting as proxy to r +func NewTrimNodeReader(r io.Reader) io.Reader { + return &TrimNodeReader{r: r} +} diff --git a/conf/conf.go b/conf/conf.go index d00824b..7fdd933 100644 --- a/conf/conf.go +++ b/conf/conf.go @@ -2,6 +2,7 @@ package conf import ( "fmt" + "github.com/InazumaV/V2bX/common/json5" "os" "github.com/goccy/go-json" @@ -28,5 +29,5 @@ func (p *Conf) LoadFromPath(filePath string) error { return fmt.Errorf("open config file error: %s", err) } defer f.Close() - return json.NewDecoder(f).Decode(p) + return json.NewDecoder(json5.NewTrimNodeReader(f)).Decode(p) } diff --git a/conf/conf_test.go b/conf/conf_test.go index 4349b92..531395c 100644 --- a/conf/conf_test.go +++ b/conf/conf_test.go @@ -6,7 +6,7 @@ import ( func TestConf_LoadFromPath(t *testing.T) { c := New() - t.Log(c.LoadFromPath("./config.json"), c.NodeConfig) + t.Log(c.LoadFromPath("../example/config.json"), c.NodeConfig) } func TestConf_Watch(t *testing.T) { diff --git a/example/config.json b/example/config.json index c29d2e1..3d087f7 100644 --- a/example/config.json +++ b/example/config.json @@ -1,65 +1,93 @@ { "Log": { - "Level": "error" + // V2bX 的日志配置,独立于各 Core 的 log 配置 + + // 日志等级,info, warn, error, none + "Level": "error", + // 日志输出路径,默认输出到标准输出 + "Output": "" }, "Cores": [ { + // Core类型 "Type": "sing", "Log": { + // 同 SingBox log 部分配置 + "Level": "error", "Timestamp": true } - },{ + // More + }, + { "Type": "xray", "Log": { + // 同 Xray-core log 部分配置 + "Level": "error" }, - "DnsConfigPath": "", - ... + "DnsConfigPath": "" + // More } ], "Nodes": [ + // Node配置有两种写法 { + // 写法1 + + // Core类型 "Core": "sing", + // API接口地址 "ApiHost": "http://127.0.0.1", + // API密钥,即Token "ApiKey": "test", + // 节点ID "NodeID": 33, + // 节点类型 "NodeType": "shadowsocks", + // 请求超时时间 "Timeout": 30, + // 本地审计规则 "RuleListPath": "", + // 监听IP "ListenIP": "0.0.0.0", + // 发送IP "SendIP": "0.0.0.0", - "EnableProxyProtocol": true, - "EnableTFO": true, - ... - },{ - "ApiConfig": { - "ApiHost": "http://127.0.0.1", - "ApiKey": "test", - "NodeID": 33, - "Timeout": 30, - "RuleListPath": "" - }, - "Options": { - "Core": "sing", - "EnableProxyProtocol": true, - "EnableTFO": true, - ... - } - }, - { - "Core": "xray", - "ApiHost": "http://127.0.0.1", - "ApiKey": "test", - "NodeID": 33, - "NodeType": "shadowsocks", - "Timeout": 30, - "RuleListPath": "", - "ListenIP": "0.0.0.0", - "SendIP": "0.0.0.0", - "EnableProxyProtocol": true, - "EnableTFO": true, - ... + // 开启 Proxy Protocol,参见 https://github.com/haproxy/haproxy/blob/master/doc/proxy-protocol.txt + "EnableProxyProtocol": false, + // 开启 TCP Fast Open + "EnableTFO": true + // More } + /*, + { + // 写法2 + + "ApiConfig": { + "ApiHost": "http://127.0.0.1", + "ApiKey": "test", + "NodeID": 33, + "Timeout": 30, + "RuleListPath": "" + }, + "Options": { + "Core": "sing", + "EnableProxyProtocol": true, + "EnableTFO": true + } + }, + { + "Core": "xray", + "ApiHost": "http://127.0.0.1", + "ApiKey": "test", + "NodeID": 33, + "NodeType": "shadowsocks", + "Timeout": 30, + "RuleListPath": "", + "ListenIP": "0.0.0.0", + "SendIP": "0.0.0.0", + "EnableProxyProtocol": true, + "EnableTFO": true + }*/ ] } \ No newline at end of file