refine api, run command and merger (#766)

* mergers code optimize

* api log work with pipe

* remove inbounds/outbounds by tags
*fix rmo flag parse

* cmds description and message optimize
This commit is contained in:
Jebbs 2021-03-11 05:03:26 +08:00 committed by Shelikhoo
parent 2523d77919
commit 7a0af318df
No known key found for this signature in database
GPG Key ID: C4D5E79D22B25316
16 changed files with 194 additions and 125 deletions

View File

@ -8,7 +8,7 @@ func GetExtensions(formatName string) ([]string, error) {
if lowerName == "auto" { if lowerName == "auto" {
return GetAllExtensions(), nil return GetAllExtensions(), nil
} }
f, found := mergeLoaderByName[lowerName] f, found := mergersByName[lowerName]
if !found { if !found {
return nil, newError(formatName+" not found", formatName).AtWarning() return nil, newError(formatName+" not found", formatName).AtWarning()
} }
@ -18,7 +18,7 @@ func GetExtensions(formatName string) ([]string, error) {
// GetAllExtensions get all extensions supported // GetAllExtensions get all extensions supported
func GetAllExtensions() []string { func GetAllExtensions() []string {
extensions := make([]string, 0) extensions := make([]string, 0)
for _, f := range mergeLoaderByName { for _, f := range mergersByName {
extensions = append(extensions, f.Extensions...) extensions = append(extensions, f.Extensions...)
} }
return extensions return extensions

View File

@ -1,40 +0,0 @@
package mergers
//go:generate go run github.com/v2fly/v2ray-core/v4/common/errors/errorgen
import (
"strings"
)
// MergeableFormat is a configurable mergeable format of V2Ray config file.
type MergeableFormat struct {
Name string
Extensions []string
Loader MergeLoader
}
// MergeLoader is a utility to merge V2Ray config from external source into a map and returns it.
type MergeLoader func(input interface{}, m map[string]interface{}) error
var (
mergeLoaderByName = make(map[string]*MergeableFormat)
mergeLoaderByExt = make(map[string]*MergeableFormat)
)
// RegisterMergeLoader add a new MergeLoader.
func RegisterMergeLoader(format *MergeableFormat) error {
if _, found := mergeLoaderByName[format.Name]; found {
return newError(format.Name, " already registered.")
}
mergeLoaderByName[format.Name] = format
for _, ext := range format.Extensions {
lext := strings.ToLower(ext)
if f, found := mergeLoaderByExt[lext]; found {
return newError(ext, " already registered to ", f.Name)
}
mergeLoaderByExt[lext] = format
}
return nil
}

View File

@ -12,15 +12,15 @@ import (
// MergeAs load input and merge as specified format into m // MergeAs load input and merge as specified format into m
func MergeAs(formatName string, input interface{}, m map[string]interface{}) error { func MergeAs(formatName string, input interface{}, m map[string]interface{}) error {
f, found := mergeLoaderByName[formatName] f, found := mergersByName[formatName]
if !found { if !found {
return newError("format loader not found for: ", formatName) return newError("format merger not found for: ", formatName)
} }
return f.Loader(input, m) return f.Merge(input, m)
} }
// Merge loads inputs and merges them into m // Merge loads inputs and merges them into m
// it detects extension for loader selecting, or try all loaders // it detects extension for merger selecting, or try all mergers
// if no extension found // if no extension found
func Merge(input interface{}, m map[string]interface{}) error { func Merge(input interface{}, m map[string]interface{}) error {
switch v := input.(type) { switch v := input.(type) {
@ -49,7 +49,7 @@ func Merge(input interface{}, m map[string]interface{}) error {
return err return err
} }
case io.Reader: case io.Reader:
// read to []byte incase it tries different loaders // read to []byte incase it tries different mergers
bs, err := ioutil.ReadAll(v) bs, err := ioutil.ReadAll(v)
if err != nil { if err != nil {
return err return err
@ -69,24 +69,24 @@ func mergeSingleFile(input interface{}, m map[string]interface{}) error {
ext := getExtension(file) ext := getExtension(file)
if ext != "" { if ext != "" {
lext := strings.ToLower(ext) lext := strings.ToLower(ext)
f, found := mergeLoaderByExt[lext] f, found := mergersByExt[lext]
if !found { if !found {
return newError("unmergeable format extension: ", ext) return newError("unmergeable format extension: ", ext)
} }
return f.Loader(file, m) return f.Merge(file, m)
} }
} }
// no extension, try all loaders // no extension, try all mergers
for _, f := range mergeLoaderByName { for _, f := range mergersByName {
if f.Name == core.FormatAuto { if f.Name == core.FormatAuto {
continue continue
} }
err := f.Loader(input, m) err := f.Merge(input, m)
if err == nil { if err == nil {
return nil return nil
} }
} }
return newError("tried all loaders but failed for: ", input).AtWarning() return newError("tried all mergers but failed for: ", input).AtWarning()
} }
func getExtension(filename string) string { func getExtension(filename string) string {

View File

@ -11,15 +11,17 @@ import (
type jsonConverter func(v []byte) ([]byte, error) type jsonConverter func(v []byte) ([]byte, error)
func makeLoader(name string, extensions []string, converter jsonConverter) *MergeableFormat { // makeMerger makes a merger who merge the format by converting it to JSON
return &MergeableFormat{ func makeMerger(name string, extensions []string, converter jsonConverter) *Merger {
return &Merger{
Name: name, Name: name,
Extensions: extensions, Extensions: extensions,
Loader: makeConvertToJSONLoader(converter), Merge: makeToJSONMergeFunc(converter),
} }
} }
func makeConvertToJSONLoader(converter func(v []byte) ([]byte, error)) MergeLoader { // makeToJSONMergeFunc makes a merge func who merge the format by converting it to JSON
func makeToJSONMergeFunc(converter func(v []byte) ([]byte, error)) MergeFunc {
return func(input interface{}, target map[string]interface{}) error { return func(input interface{}, target map[string]interface{}) error {
if target == nil { if target == nil {
panic("merge target is nil") panic("merge target is nil")

View File

@ -1,32 +1,69 @@
package mergers package mergers
//go:generate go run github.com/v2fly/v2ray-core/v4/common/errors/errorgen
import ( import (
"strings"
core "github.com/v2fly/v2ray-core/v4" core "github.com/v2fly/v2ray-core/v4"
"github.com/v2fly/v2ray-core/v4/common" "github.com/v2fly/v2ray-core/v4/common"
"github.com/v2fly/v2ray-core/v4/infra/conf/json" "github.com/v2fly/v2ray-core/v4/infra/conf/json"
) )
func init() { func init() {
common.Must(RegisterMergeLoader(makeLoader( common.Must(RegisterMerger(makeMerger(
core.FormatJSON, core.FormatJSON,
[]string{".json", ".jsonc"}, []string{".json", ".jsonc"},
nil, nil,
))) )))
common.Must(RegisterMergeLoader(makeLoader( common.Must(RegisterMerger(makeMerger(
core.FormatTOML, core.FormatTOML,
[]string{".toml"}, []string{".toml"},
json.FromTOML, json.FromTOML,
))) )))
common.Must(RegisterMergeLoader(makeLoader( common.Must(RegisterMerger(makeMerger(
core.FormatYAML, core.FormatYAML,
[]string{".yml", ".yaml"}, []string{".yml", ".yaml"},
json.FromYAML, json.FromYAML,
))) )))
common.Must(RegisterMergeLoader( common.Must(RegisterMerger(
&MergeableFormat{ &Merger{
Name: core.FormatAuto, Name: core.FormatAuto,
Extensions: nil, Extensions: nil,
Loader: Merge, Merge: Merge,
}), }),
) )
} }
// Merger is a configurable format merger for V2Ray config files.
type Merger struct {
Name string
Extensions []string
Merge MergeFunc
}
// MergeFunc is a utility to merge V2Ray config from external source into a map and returns it.
type MergeFunc func(input interface{}, m map[string]interface{}) error
var (
mergersByName = make(map[string]*Merger)
mergersByExt = make(map[string]*Merger)
)
// RegisterMerger add a new Merger.
func RegisterMerger(format *Merger) error {
if _, found := mergersByName[format.Name]; found {
return newError(format.Name, " already registered.")
}
mergersByName[format.Name] = format
for _, ext := range format.Extensions {
lext := strings.ToLower(ext)
if f, found := mergersByExt[lext]; found {
return newError(ext, " already registered to ", f.Name)
}
mergersByExt[lext] = format
}
return nil
}

View File

@ -3,7 +3,7 @@ package mergers
// GetAllNames get names of all formats // GetAllNames get names of all formats
func GetAllNames() []string { func GetAllNames() []string {
names := make([]string, 0) names := make([]string, 0)
for _, f := range mergeLoaderByName { for _, f := range mergersByName {
names = append(names, f.Name) names = append(names, f.Name)
} }
return names return names

View File

@ -10,15 +10,18 @@ import (
var cmdAddInbounds = &base.Command{ var cmdAddInbounds = &base.Command{
CustomFlags: true, CustomFlags: true,
UsageLine: "{{.Exec}} api adi [--server=127.0.0.1:8080] <c1.json> [c2.json]...", UsageLine: "{{.Exec}} api adi [--server=127.0.0.1:8080] [c1.json] [dir1]...",
Short: "add inbounds", Short: "add inbounds",
Long: ` Long: `
Add inbounds to V2Ray. Add inbounds to V2Ray.
> Make sure you have "HandlerService" set in "config.api.services"
of server config.
Arguments: Arguments:
-format <format> -format <format>
Specify the input format. The input format.
Available values: "auto", "json", "toml", "yaml" Available values: "auto", "json", "toml", "yaml"
Default: "auto" Default: "auto"
@ -33,7 +36,8 @@ Arguments:
Example: Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json {{.Exec}} {{.LongName}} dir
{{.Exec}} {{.LongName}} c1.json c2.yaml
`, `,
Run: executeAddInbounds, Run: executeAddInbounds,
} }
@ -44,7 +48,7 @@ func executeAddInbounds(cmd *base.Command, args []string) {
cmd.Flag.Parse(args) cmd.Flag.Parse(args)
c, err := helpers.LoadConfig(cmd.Flag.Args(), apiConfigFormat, apiConfigRecursively) c, err := helpers.LoadConfig(cmd.Flag.Args(), apiConfigFormat, apiConfigRecursively)
if err != nil { if err != nil {
base.Fatalf("%s", err) base.Fatalf("failed to load: %s", err)
} }
if len(c.InboundConfigs) == 0 { if len(c.InboundConfigs) == 0 {
base.Fatalf("no valid inbound found") base.Fatalf("no valid inbound found")

View File

@ -10,21 +10,27 @@ import (
var cmdRemoveInbounds = &base.Command{ var cmdRemoveInbounds = &base.Command{
CustomFlags: true, CustomFlags: true,
UsageLine: "{{.Exec}} api rmi [--server=127.0.0.1:8080] <json_file|tag> [json_file] [tag]...", UsageLine: "{{.Exec}} api rmi [--server=127.0.0.1:8080] [c1.json] [dir1]...",
Short: "remove inbounds", Short: "remove inbounds",
Long: ` Long: `
Remove inbounds from V2Ray. Remove inbounds from V2Ray.
> Make sure you have "HandlerService" set in "config.api.services"
of server config.
Arguments: Arguments:
-format <format> -format <format>
Specify the input format. The input format.
Available values: "auto", "json", "toml", "yaml" Available values: "auto", "json", "toml", "yaml"
Default: "auto" Default: "auto"
-r -r
Load folders recursively. Load folders recursively.
-tags
The input are tags instead of config files
-s, -server <server:port> -s, -server <server:port>
The API server address. Default 127.0.0.1:8080 The API server address. Default 127.0.0.1:8080
@ -33,7 +39,9 @@ Arguments:
Example: Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json "tag name" {{.Exec}} {{.LongName}} dir
{{.Exec}} {{.LongName}} c1.json c2.yaml
{{.Exec}} {{.LongName}} -tags tag1 tag2
`, `,
Run: executeRemoveInbounds, Run: executeRemoveInbounds,
} }
@ -41,12 +49,23 @@ Example:
func executeRemoveInbounds(cmd *base.Command, args []string) { func executeRemoveInbounds(cmd *base.Command, args []string) {
setSharedFlags(cmd) setSharedFlags(cmd)
setSharedConfigFlags(cmd) setSharedConfigFlags(cmd)
isTags := cmd.Flag.Bool("tags", false, "")
cmd.Flag.Parse(args) cmd.Flag.Parse(args)
c, err := helpers.LoadConfig(cmd.Flag.Args(), apiConfigFormat, apiConfigRecursively)
if err != nil { var tags []string
base.Fatalf("%s", err) if *isTags {
tags = cmd.Flag.Args()
} else {
c, err := helpers.LoadConfig(cmd.Flag.Args(), apiConfigFormat, apiConfigRecursively)
if err != nil {
base.Fatalf("failed to load: %s", err)
}
tags = make([]string, 0)
for _, c := range c.InboundConfigs {
tags = append(tags, c.Tag)
}
} }
if len(c.InboundConfigs) == 0 { if len(tags) == 0 {
base.Fatalf("no inbound to remove") base.Fatalf("no inbound to remove")
} }
@ -54,10 +73,10 @@ func executeRemoveInbounds(cmd *base.Command, args []string) {
defer close() defer close()
client := handlerService.NewHandlerServiceClient(conn) client := handlerService.NewHandlerServiceClient(conn)
for _, c := range c.InboundConfigs { for _, tag := range tags {
fmt.Println("removing:", c.Tag) fmt.Println("removing:", tag)
r := &handlerService.RemoveInboundRequest{ r := &handlerService.RemoveInboundRequest{
Tag: c.Tag, Tag: tag,
} }
_, err := client.RemoveInbound(ctx, r) _, err := client.RemoveInbound(ctx, r)
if err != nil { if err != nil {

View File

@ -3,6 +3,7 @@ package api
import ( import (
"io" "io"
"log" "log"
"os"
logService "github.com/v2fly/v2ray-core/v4/app/log/command" logService "github.com/v2fly/v2ray-core/v4/app/log/command"
"github.com/v2fly/v2ray-core/v4/main/commands/base" "github.com/v2fly/v2ray-core/v4/main/commands/base"
@ -15,6 +16,9 @@ var cmdLog = &base.Command{
Long: ` Long: `
Follow and print logs from v2ray. Follow and print logs from v2ray.
> Make sure you have "LoggerService" set in "config.api.services"
of server config.
> It ignores -timeout flag while following logs > It ignores -timeout flag while following logs
Arguments: Arguments:
@ -33,10 +37,10 @@ Example:
{{.Exec}} {{.LongName}} {{.Exec}} {{.LongName}}
{{.Exec}} {{.LongName}} --restart {{.Exec}} {{.LongName}} --restart
`, `,
Run: executeRestartLogger, Run: executeLog,
} }
func executeRestartLogger(cmd *base.Command, args []string) { func executeLog(cmd *base.Command, args []string) {
var restart bool var restart bool
cmd.Flag.BoolVar(&restart, "restart", false, "") cmd.Flag.BoolVar(&restart, "restart", false, "")
setSharedFlags(cmd) setSharedFlags(cmd)
@ -69,7 +73,8 @@ func followLogger() {
if err != nil { if err != nil {
base.Fatalf("failed to follow logger: %s", err) base.Fatalf("failed to follow logger: %s", err)
} }
// work with `v2ray api log | grep expr`
log.SetOutput(os.Stdout)
for { for {
resp, err := stream.Recv() resp, err := stream.Recv()
if err == io.EOF { if err == io.EOF {
@ -78,6 +83,6 @@ func followLogger() {
if err != nil { if err != nil {
base.Fatalf("failed to fetch log: %s", err) base.Fatalf("failed to fetch log: %s", err)
} }
log.Print(resp.Message) log.Println(resp.Message)
} }
} }

View File

@ -10,15 +10,18 @@ import (
var cmdAddOutbounds = &base.Command{ var cmdAddOutbounds = &base.Command{
CustomFlags: true, CustomFlags: true,
UsageLine: "{{.Exec}} api ado [--server=127.0.0.1:8080] <c1.json> [c2.json]...", UsageLine: "{{.Exec}} api ado [--server=127.0.0.1:8080] [c1.json] [dir1]...",
Short: "add outbounds", Short: "add outbounds",
Long: ` Long: `
Add outbounds to V2Ray. Add outbounds to V2Ray.
> Make sure you have "HandlerService" set in "config.api.services"
of server config.
Arguments: Arguments:
-format <format> -format <format>
Specify the input format. The input format.
Available values: "auto", "json", "toml", "yaml" Available values: "auto", "json", "toml", "yaml"
Default: "auto" Default: "auto"
@ -33,7 +36,8 @@ Arguments:
Example: Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json {{.Exec}} {{.LongName}} dir
{{.Exec}} {{.LongName}} c1.json c2.yaml
`, `,
Run: executeAddOutbounds, Run: executeAddOutbounds,
} }
@ -44,7 +48,7 @@ func executeAddOutbounds(cmd *base.Command, args []string) {
cmd.Flag.Parse(args) cmd.Flag.Parse(args)
c, err := helpers.LoadConfig(cmd.Flag.Args(), apiConfigFormat, apiConfigRecursively) c, err := helpers.LoadConfig(cmd.Flag.Args(), apiConfigFormat, apiConfigRecursively)
if err != nil { if err != nil {
base.Fatalf("%s", err) base.Fatalf("failed to load: %s", err)
} }
if len(c.OutboundConfigs) == 0 { if len(c.OutboundConfigs) == 0 {
base.Fatalf("no valid outbound found") base.Fatalf("no valid outbound found")

View File

@ -10,21 +10,27 @@ import (
var cmdRemoveOutbounds = &base.Command{ var cmdRemoveOutbounds = &base.Command{
CustomFlags: true, CustomFlags: true,
UsageLine: "{{.Exec}} api rmo [--server=127.0.0.1:8080] <json_file|tag> [json_file] [tag]...", UsageLine: "{{.Exec}} api rmo [--server=127.0.0.1:8080] [c1.json] [dir1]...",
Short: "remove outbounds", Short: "remove outbounds",
Long: ` Long: `
Remove outbounds from V2Ray. Remove outbounds from V2Ray.
> Make sure you have "HandlerService" set in "config.api.services"
of server config.
Arguments: Arguments:
-format <format> -format <format>
Specify the input format. The input format.
Available values: "auto", "json", "toml", "yaml" Available values: "auto", "json", "toml", "yaml"
Default: "auto" Default: "auto"
-r -r
Load folders recursively. Load folders recursively.
-tags
The input are tags instead of config files
-s, -server <server:port> -s, -server <server:port>
The API server address. Default 127.0.0.1:8080 The API server address. Default 127.0.0.1:8080
@ -33,20 +39,33 @@ Arguments:
Example: Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json "tag name" {{.Exec}} {{.LongName}} dir
{{.Exec}} {{.LongName}} c1.json c2.yaml
{{.Exec}} {{.LongName}} -tags tag1 tag2
`, `,
Run: executeRemoveOutbounds, Run: executeRemoveOutbounds,
} }
func executeRemoveOutbounds(cmd *base.Command, args []string) { func executeRemoveOutbounds(cmd *base.Command, args []string) {
setSharedFlags(cmd) setSharedFlags(cmd)
cmd.Flag.Parse(args)
setSharedConfigFlags(cmd) setSharedConfigFlags(cmd)
c, err := helpers.LoadConfig(cmd.Flag.Args(), apiConfigFormat, apiConfigRecursively) isTags := cmd.Flag.Bool("tags", false, "")
if err != nil { cmd.Flag.Parse(args)
base.Fatalf("%s", err)
var tags []string
if *isTags {
tags = cmd.Flag.Args()
} else {
c, err := helpers.LoadConfig(cmd.Flag.Args(), apiConfigFormat, apiConfigRecursively)
if err != nil {
base.Fatalf("failed to load: %s", err)
}
tags = make([]string, 0)
for _, c := range c.OutboundConfigs {
tags = append(tags, c.Tag)
}
} }
if len(c.OutboundConfigs) == 0 { if len(tags) == 0 {
base.Fatalf("no outbound to remove") base.Fatalf("no outbound to remove")
} }
@ -54,10 +73,10 @@ func executeRemoveOutbounds(cmd *base.Command, args []string) {
defer close() defer close()
client := handlerService.NewHandlerServiceClient(conn) client := handlerService.NewHandlerServiceClient(conn)
for _, c := range c.OutboundConfigs { for _, tag := range tags {
fmt.Println("removing:", c.Tag) fmt.Println("removing:", tag)
r := &handlerService.RemoveOutboundRequest{ r := &handlerService.RemoveOutboundRequest{
Tag: c.Tag, Tag: tag,
} }
_, err := client.RemoveOutbound(ctx, r) _, err := client.RemoveOutbound(ctx, r)
if err != nil { if err != nil {

View File

@ -19,13 +19,16 @@ var cmdStats = &base.Command{
Long: ` Long: `
Query statistics from V2Ray. Query statistics from V2Ray.
> Make sure you have "StatsService" set in "config.api.services"
of server config.
Arguments: Arguments:
-regexp -regexp
The patterns are using regexp. The patterns are using regexp.
-reset -reset
Fetch values then reset statistics counters to 0. Reset counters to 0 after fetching their values.
-runtime -runtime
Get runtime statistics. Get runtime statistics.

View File

@ -23,17 +23,17 @@ var cmdConvert = &base.Command{
Short: "convert config files", Short: "convert config files",
Long: ` Long: `
Convert config files between different formats. Files are merged Convert config files between different formats. Files are merged
before convert if multiple assigned. before convert.
Arguments: Arguments:
-i, -input <format> -i, -input <format>
Specify the input format. The input format.
Available values: "auto", "json", "toml", "yaml" Available values: "auto", "json", "toml", "yaml"
Default: "auto" Default: "auto"
-o, -output <format> -o, -output <format>
Specify the output format The output format
Available values: "json", "toml", "yaml", "protobuf" / "pb" Available values: "json", "toml", "yaml", "protobuf" / "pb"
Default: "json" Default: "json"
@ -42,15 +42,15 @@ Arguments:
Examples: Examples:
{{.Exec}} {{.LongName}} -output=protobuf config.json (1) {{.Exec}} {{.LongName}} -output=protobuf "path/to/dir" (1)
{{.Exec}} {{.LongName}} -input=toml config.toml (2) {{.Exec}} {{.LongName}} -o=yaml config.toml (2)
{{.Exec}} {{.LongName}} "path/to/dir" (3) {{.Exec}} {{.LongName}} c1.json c2.json (3)
{{.Exec}} {{.LongName}} -i yaml -o protobuf c1.yaml <url>.yaml (4) {{.Exec}} {{.LongName}} -output=yaml c1.yaml <url>.yaml (4)
(1) Convert json to protobuf (1) Merge all supported files in dir and convert to protobuf
(2) Convert toml to json (2) Convert toml to yaml
(3) Merge json files in dir (3) Merge json files
(4) Merge yaml files and convert to protobuf (4) Merge yaml files
Use "{{.Exec}} help config-merge" for more information about merge. Use "{{.Exec}} help config-merge" for more information about merge.
`, `,
@ -81,11 +81,11 @@ func executeConvert(cmd *base.Command, args []string) {
m, err := helpers.LoadConfigToMap(cmd.Flag.Args(), inputFormat, confDirRecursively) m, err := helpers.LoadConfigToMap(cmd.Flag.Args(), inputFormat, confDirRecursively)
if err != nil { if err != nil {
base.Fatalf(err.Error()) base.Fatalf("failed to merge: %s", err)
} }
err = merge.ApplyRules(m) err = merge.ApplyRules(m)
if err != nil { if err != nil {
base.Fatalf(err.Error()) base.Fatalf("failed to apply merge rules: %s", err)
} }
var out []byte var out []byte
@ -93,17 +93,17 @@ func executeConvert(cmd *base.Command, args []string) {
case core.FormatJSON: case core.FormatJSON:
out, err = json.Marshal(m) out, err = json.Marshal(m)
if err != nil { if err != nil {
base.Fatalf("failed to marshal json: %s", err) base.Fatalf("failed to convert to json: %s", err)
} }
case core.FormatTOML: case core.FormatTOML:
out, err = toml.Marshal(m) out, err = toml.Marshal(m)
if err != nil { if err != nil {
base.Fatalf("failed to marshal json: %s", err) base.Fatalf("failed to convert to toml: %s", err)
} }
case core.FormatYAML: case core.FormatYAML:
out, err = yaml.Marshal(m) out, err = yaml.Marshal(m)
if err != nil { if err != nil {
base.Fatalf("failed to marshal json: %s", err) base.Fatalf("failed to convert to yaml: %s", err)
} }
case core.FormatProtobuf, core.FormatProtobufShort: case core.FormatProtobuf, core.FormatProtobufShort:
data, err := json.Marshal(m) data, err := json.Marshal(m)
@ -121,7 +121,7 @@ func executeConvert(cmd *base.Command, args []string) {
} }
out, err = proto.Marshal(pbConfig) out, err = proto.Marshal(pbConfig)
if err != nil { if err != nil {
base.Fatalf("failed to marshal proto config: %s", err) base.Fatalf("failed to convert to protobuf: %s", err)
} }
default: default:
base.Errorf("invalid output format: %s", outputFormat) base.Errorf("invalid output format: %s", outputFormat)

View File

@ -8,12 +8,12 @@ var docFormat = &base.Command{
UsageLine: "{{.Exec}} format-loader", UsageLine: "{{.Exec}} format-loader",
Short: "config formats and loading", Short: "config formats and loading",
Long: ` Long: `
{{.Exec}} supports different config formats: {{.Exec}} is equipped with multiple loaders to support different
config formats:
* auto * auto
The default loader, supports all extensions below. The default loader, supports all formats listed below, with
It loads config by format detecting, with mixed format detecting, and mixed fomats support.
formats support.
* json (.json, .jsonc) * json (.json, .jsonc)
The json loader, multiple files support, mergeable. The json loader, multiple files support, mergeable.
@ -21,14 +21,14 @@ var docFormat = &base.Command{
* toml (.toml) * toml (.toml)
The toml loader, multiple files support, mergeable. The toml loader, multiple files support, mergeable.
* yaml (.yml) * yaml (.yml, .yaml)
The yaml loader, multiple files support, mergeable. The yaml loader, multiple files support, mergeable.
* protobuf / pb (.pb) * protobuf / pb (.pb)
Single file support, unmergeable. Single file support, unmergeable.
The following explains how format loaders behave with examples. The following explains how format loaders behaves.
Examples: Examples:

View File

@ -25,19 +25,27 @@ var CmdRun = &base.Command{
Long: ` Long: `
Run V2Ray with config. Run V2Ray with config.
{{.Exec}} will also use the config directory specified by environment
variable "v2ray.location.confdir". If no config found, it tries
to load config from one of below:
1. The default "config.json" in the current directory
2. The config file from ENV "v2ray.location.config"
3. The stdin if all failed above
Arguments: Arguments:
-c, -config <file> -c, -config <file>
Config file for V2Ray. Multiple assign is accepted. Config file for V2Ray. Multiple assign is accepted.
-d, -confdir <dir> -d, -confdir <dir>
A dir with config files. Multiple assign is accepted. A directory with config files. Multiple assign is accepted.
-r -r
Load confdir recursively. Load confdir recursively.
-format <format> -format <format>
Format of input files. (default "json") Format of config input. (default "auto")
Examples: Examples:

View File

@ -16,19 +16,27 @@ var CmdTest = &base.Command{
Long: ` Long: `
Test config files, without launching V2Ray server. Test config files, without launching V2Ray server.
{{.Exec}} will also use the config directory specified by environment
variable "v2ray.location.confdir". If no config found, it tries
to load config from one of below:
1. The default "config.json" in the current directory
2. The config file from ENV "v2ray.location.config"
3. The stdin if all failed above
Arguments: Arguments:
-c, -config <file> -c, -config <file>
Config file for V2Ray. Multiple assign is accepted. Config file for V2Ray. Multiple assign is accepted.
-d, -confdir <dir> -d, -confdir <dir>
A dir with config files. Multiple assign is accepted. A directory with config files. Multiple assign is accepted.
-r -r
Load confdir recursively. Load confdir recursively.
-format <format> -format <format>
Format of input files. (default "json") Format of config input. (default "auto")
Examples: Examples: