mirror of
https://github.com/v2fly/v2ray-core.git
synced 2024-12-22 01:57:12 -05:00
v5: API commands (rebased from a17fc40651
)
This commit is contained in:
parent
54a627c443
commit
0506757caf
@ -1,145 +0,0 @@
|
|||||||
package all
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
|
|
||||||
logService "github.com/v2fly/v2ray-core/v4/app/log/command"
|
|
||||||
statsService "github.com/v2fly/v2ray-core/v4/app/stats/command"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/commands/base"
|
|
||||||
)
|
|
||||||
|
|
||||||
// cmdAPI calls an API in an V2Ray process
|
|
||||||
var cmdAPI = &base.Command{
|
|
||||||
UsageLine: "{{.Exec}} api [-server 127.0.0.1:8080] <action> <parameter>",
|
|
||||||
Short: "Call V2Ray API",
|
|
||||||
Long: `
|
|
||||||
Call V2Ray API, API calls in this command have a timeout to the server of 3 seconds.
|
|
||||||
|
|
||||||
The following methods are currently supported:
|
|
||||||
|
|
||||||
LoggerService.RestartLogger
|
|
||||||
StatsService.GetStats
|
|
||||||
StatsService.QueryStats
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 LoggerService.RestartLogger ''
|
|
||||||
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 StatsService.QueryStats 'pattern: "" reset: false'
|
|
||||||
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 StatsService.GetStats 'name: "inbound>>>statin>>>traffic>>>downlink" reset: false'
|
|
||||||
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 StatsService.GetSysStats ''
|
|
||||||
`,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
cmdAPI.Run = executeAPI // break init loop
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
apiServerAddrPtr = cmdAPI.Flag.String("server", "127.0.0.1:8080", "")
|
|
||||||
)
|
|
||||||
|
|
||||||
func executeAPI(cmd *base.Command, args []string) {
|
|
||||||
unnamedArgs := cmdAPI.Flag.Args()
|
|
||||||
if len(unnamedArgs) < 2 {
|
|
||||||
base.Fatalf("service name or request not specified.")
|
|
||||||
}
|
|
||||||
|
|
||||||
service, method := getServiceMethod(unnamedArgs[0])
|
|
||||||
handler, found := serivceHandlerMap[strings.ToLower(service)]
|
|
||||||
if !found {
|
|
||||||
base.Fatalf("unknown service: %s", service)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
conn, err := grpc.DialContext(ctx, *apiServerAddrPtr, grpc.WithInsecure(), grpc.WithBlock())
|
|
||||||
if err != nil {
|
|
||||||
base.Fatalf("failed to dial %s", *apiServerAddrPtr)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
response, err := handler(ctx, conn, method, unnamedArgs[1])
|
|
||||||
if err != nil {
|
|
||||||
base.Fatalf("failed to call service %s: %s", unnamedArgs[0], err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(response)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getServiceMethod(s string) (string, string) {
|
|
||||||
ss := strings.Split(s, ".")
|
|
||||||
service := ss[0]
|
|
||||||
var method string
|
|
||||||
if len(ss) > 1 {
|
|
||||||
method = ss[1]
|
|
||||||
}
|
|
||||||
return service, method
|
|
||||||
}
|
|
||||||
|
|
||||||
type serviceHandler func(ctx context.Context, conn *grpc.ClientConn, method string, request string) (string, error)
|
|
||||||
|
|
||||||
var serivceHandlerMap = map[string]serviceHandler{
|
|
||||||
"statsservice": callStatsService,
|
|
||||||
"loggerservice": callLogService,
|
|
||||||
}
|
|
||||||
|
|
||||||
func callLogService(ctx context.Context, conn *grpc.ClientConn, method string, request string) (string, error) {
|
|
||||||
client := logService.NewLoggerServiceClient(conn)
|
|
||||||
|
|
||||||
switch strings.ToLower(method) {
|
|
||||||
case "restartlogger":
|
|
||||||
r := &logService.RestartLoggerRequest{}
|
|
||||||
resp, err := client.RestartLogger(ctx, r)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return proto.MarshalTextString(resp), nil
|
|
||||||
default:
|
|
||||||
return "", errors.New("Unknown method: " + method)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func callStatsService(ctx context.Context, conn *grpc.ClientConn, method string, request string) (string, error) {
|
|
||||||
client := statsService.NewStatsServiceClient(conn)
|
|
||||||
|
|
||||||
switch strings.ToLower(method) {
|
|
||||||
case "getstats":
|
|
||||||
r := &statsService.GetStatsRequest{}
|
|
||||||
if err := proto.UnmarshalText(request, r); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
resp, err := client.GetStats(ctx, r)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return proto.MarshalTextString(resp), nil
|
|
||||||
case "querystats":
|
|
||||||
r := &statsService.QueryStatsRequest{}
|
|
||||||
if err := proto.UnmarshalText(request, r); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
resp, err := client.QueryStats(ctx, r)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return proto.MarshalTextString(resp), nil
|
|
||||||
case "getsysstats":
|
|
||||||
// SysStatsRequest is an empty message
|
|
||||||
r := &statsService.SysStatsRequest{}
|
|
||||||
resp, err := client.GetSysStats(ctx, r)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return proto.MarshalTextString(resp), nil
|
|
||||||
default:
|
|
||||||
return "", errors.New("Unknown method: " + method)
|
|
||||||
}
|
|
||||||
}
|
|
23
commands/all/api/api.go
Normal file
23
commands/all/api/api.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"v2ray.com/core/commands/base"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdAPI calls an API in an V2Ray process
|
||||||
|
var CmdAPI = &base.Command{
|
||||||
|
UsageLine: "{{.Exec}} api",
|
||||||
|
Short: "Call V2Ray API",
|
||||||
|
Long: `{{.Exec}} {{.LongName}} provides tools to manipulate V2Ray via its API.
|
||||||
|
`,
|
||||||
|
Commands: []*base.Command{
|
||||||
|
cmdRestartLogger,
|
||||||
|
cmdGetStats,
|
||||||
|
cmdQueryStats,
|
||||||
|
cmdSysStats,
|
||||||
|
cmdAddInbounds,
|
||||||
|
cmdAddOutbounds,
|
||||||
|
cmdRemoveInbounds,
|
||||||
|
cmdRemoveOutbounds,
|
||||||
|
},
|
||||||
|
}
|
78
commands/all/api/inbounds_add.go
Normal file
78
commands/all/api/inbounds_add.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
handlerService "v2ray.com/core/app/proxyman/command"
|
||||||
|
"v2ray.com/core/commands/base"
|
||||||
|
"v2ray.com/core/infra/conf"
|
||||||
|
"v2ray.com/core/infra/conf/serial"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdAddInbounds = &base.Command{
|
||||||
|
CustomFlags: true,
|
||||||
|
UsageLine: "{{.Exec}} api adi [--server=127.0.0.1:8080] <c1.json> [c2.json]...",
|
||||||
|
Short: "Add inbounds",
|
||||||
|
Long: `
|
||||||
|
Add inbounds to V2Ray.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
-s, -server
|
||||||
|
The API server address. Default 127.0.0.1:8080
|
||||||
|
|
||||||
|
-t, -timeout
|
||||||
|
Timeout seconds to call API. Default 3
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json
|
||||||
|
`,
|
||||||
|
Run: executeAddInbounds,
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeAddInbounds(cmd *base.Command, args []string) {
|
||||||
|
setSharedFlags(cmd)
|
||||||
|
cmd.Flag.Parse(args)
|
||||||
|
unnamedArgs := cmd.Flag.Args()
|
||||||
|
if len(unnamedArgs) == 0 {
|
||||||
|
fmt.Println("reading from stdin:")
|
||||||
|
unnamedArgs = []string{"stdin:"}
|
||||||
|
}
|
||||||
|
|
||||||
|
ins := make([]conf.InboundDetourConfig, 0)
|
||||||
|
for _, arg := range unnamedArgs {
|
||||||
|
r, err := loadArg(arg)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to load %s: %s", arg, err)
|
||||||
|
}
|
||||||
|
conf, err := serial.DecodeJSONConfig(r)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to decode %s: %s", arg, err)
|
||||||
|
}
|
||||||
|
ins = append(ins, conf.InboundConfigs...)
|
||||||
|
}
|
||||||
|
if len(ins) == 0 {
|
||||||
|
base.Fatalf("no valid inbound found")
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, ctx, close := dialAPIServer()
|
||||||
|
defer close()
|
||||||
|
|
||||||
|
client := handlerService.NewHandlerServiceClient(conn)
|
||||||
|
for _, in := range ins {
|
||||||
|
fmt.Println("adding:", in.Tag)
|
||||||
|
i, err := in.Build()
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to build conf: %s", err)
|
||||||
|
}
|
||||||
|
r := &handlerService.AddInboundRequest{
|
||||||
|
Inbound: i,
|
||||||
|
}
|
||||||
|
resp, err := client.AddInbound(ctx, r)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to add inbound: %s", err)
|
||||||
|
}
|
||||||
|
showResponese(resp)
|
||||||
|
}
|
||||||
|
}
|
79
commands/all/api/inbounds_remove.go
Normal file
79
commands/all/api/inbounds_remove.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
handlerService "v2ray.com/core/app/proxyman/command"
|
||||||
|
"v2ray.com/core/commands/base"
|
||||||
|
"v2ray.com/core/infra/conf/serial"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdRemoveInbounds = &base.Command{
|
||||||
|
CustomFlags: true,
|
||||||
|
UsageLine: "{{.Exec}} api rmi [--server=127.0.0.1:8080] <json_file|tag> [json_file] [tag]...",
|
||||||
|
Short: "Remove inbounds",
|
||||||
|
Long: `
|
||||||
|
Remove inbounds from V2Ray.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
-s, -server
|
||||||
|
The API server address. Default 127.0.0.1:8080
|
||||||
|
|
||||||
|
-t, -timeout
|
||||||
|
Timeout seconds to call API. Default 3
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json "tag name"
|
||||||
|
`,
|
||||||
|
Run: executeRemoveInbounds,
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeRemoveInbounds(cmd *base.Command, args []string) {
|
||||||
|
setSharedFlags(cmd)
|
||||||
|
cmd.Flag.Parse(args)
|
||||||
|
unnamedArgs := cmd.Flag.Args()
|
||||||
|
if len(unnamedArgs) == 0 {
|
||||||
|
fmt.Println("reading from stdin:")
|
||||||
|
unnamedArgs = []string{"stdin:"}
|
||||||
|
}
|
||||||
|
|
||||||
|
tags := make([]string, 0)
|
||||||
|
for _, arg := range unnamedArgs {
|
||||||
|
if r, err := loadArg(arg); err == nil {
|
||||||
|
conf, err := serial.DecodeJSONConfig(r)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to decode %s: %s", arg, err)
|
||||||
|
}
|
||||||
|
ins := conf.InboundConfigs
|
||||||
|
for _, i := range ins {
|
||||||
|
tags = append(tags, i.Tag)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// take request as tag
|
||||||
|
tags = append(tags, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tags) == 0 {
|
||||||
|
base.Fatalf("no inbound to remove")
|
||||||
|
}
|
||||||
|
fmt.Println("removing inbounds:", tags)
|
||||||
|
|
||||||
|
conn, ctx, close := dialAPIServer()
|
||||||
|
defer close()
|
||||||
|
|
||||||
|
client := handlerService.NewHandlerServiceClient(conn)
|
||||||
|
for _, tag := range tags {
|
||||||
|
fmt.Println("removing:", tag)
|
||||||
|
r := &handlerService.RemoveInboundRequest{
|
||||||
|
Tag: tag,
|
||||||
|
}
|
||||||
|
resp, err := client.RemoveInbound(ctx, r)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to remove inbound: %s", err)
|
||||||
|
}
|
||||||
|
showResponese(resp)
|
||||||
|
}
|
||||||
|
}
|
40
commands/all/api/logger_restart.go
Normal file
40
commands/all/api/logger_restart.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
logService "v2ray.com/core/app/log/command"
|
||||||
|
"v2ray.com/core/commands/base"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdRestartLogger = &base.Command{
|
||||||
|
CustomFlags: true,
|
||||||
|
UsageLine: "{{.Exec}} api restartlogger [--server=127.0.0.1:8080]",
|
||||||
|
Short: "Restart the logger",
|
||||||
|
Long: `
|
||||||
|
Restart the logger of V2Ray.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
-s, -server
|
||||||
|
The API server address. Default 127.0.0.1:8080
|
||||||
|
|
||||||
|
-t, -timeout
|
||||||
|
Timeout seconds to call API. Default 3
|
||||||
|
`,
|
||||||
|
Run: executeRestartLogger,
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeRestartLogger(cmd *base.Command, args []string) {
|
||||||
|
setSharedFlags(cmd)
|
||||||
|
cmd.Flag.Parse(args)
|
||||||
|
|
||||||
|
conn, ctx, close := dialAPIServer()
|
||||||
|
defer close()
|
||||||
|
|
||||||
|
client := logService.NewLoggerServiceClient(conn)
|
||||||
|
r := &logService.RestartLoggerRequest{}
|
||||||
|
resp, err := client.RestartLogger(ctx, r)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to restart logger: %s", err)
|
||||||
|
}
|
||||||
|
showResponese(resp)
|
||||||
|
}
|
78
commands/all/api/outbounds_add.go
Normal file
78
commands/all/api/outbounds_add.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
handlerService "v2ray.com/core/app/proxyman/command"
|
||||||
|
"v2ray.com/core/commands/base"
|
||||||
|
"v2ray.com/core/infra/conf"
|
||||||
|
"v2ray.com/core/infra/conf/serial"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdAddOutbounds = &base.Command{
|
||||||
|
CustomFlags: true,
|
||||||
|
UsageLine: "{{.Exec}} api ado [--server=127.0.0.1:8080] <c1.json> [c2.json]...",
|
||||||
|
Short: "Add outbounds",
|
||||||
|
Long: `
|
||||||
|
Add outbounds to V2Ray.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
-s, -server
|
||||||
|
The API server address. Default 127.0.0.1:8080
|
||||||
|
|
||||||
|
-t, -timeout
|
||||||
|
Timeout seconds to call API. Default 3
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json
|
||||||
|
`,
|
||||||
|
Run: executeAddOutbounds,
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeAddOutbounds(cmd *base.Command, args []string) {
|
||||||
|
setSharedFlags(cmd)
|
||||||
|
cmd.Flag.Parse(args)
|
||||||
|
unnamedArgs := cmd.Flag.Args()
|
||||||
|
if len(unnamedArgs) == 0 {
|
||||||
|
fmt.Println("Reading from STDIN")
|
||||||
|
unnamedArgs = []string{"stdin:"}
|
||||||
|
}
|
||||||
|
|
||||||
|
outs := make([]conf.OutboundDetourConfig, 0)
|
||||||
|
for _, arg := range unnamedArgs {
|
||||||
|
r, err := loadArg(arg)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to load %s: %s", arg, err)
|
||||||
|
}
|
||||||
|
conf, err := serial.DecodeJSONConfig(r)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to decode %s: %s", arg, err)
|
||||||
|
}
|
||||||
|
outs = append(outs, conf.OutboundConfigs...)
|
||||||
|
}
|
||||||
|
if len(outs) == 0 {
|
||||||
|
base.Fatalf("no valid outbound found")
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, ctx, close := dialAPIServer()
|
||||||
|
defer close()
|
||||||
|
|
||||||
|
client := handlerService.NewHandlerServiceClient(conn)
|
||||||
|
for _, out := range outs {
|
||||||
|
fmt.Println("adding:", out.Tag)
|
||||||
|
o, err := out.Build()
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to build conf: %s", err)
|
||||||
|
}
|
||||||
|
r := &handlerService.AddOutboundRequest{
|
||||||
|
Outbound: o,
|
||||||
|
}
|
||||||
|
resp, err := client.AddOutbound(ctx, r)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to add outbound: %s", err)
|
||||||
|
}
|
||||||
|
showResponese(resp)
|
||||||
|
}
|
||||||
|
}
|
78
commands/all/api/outbounds_remove.go
Normal file
78
commands/all/api/outbounds_remove.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
handlerService "v2ray.com/core/app/proxyman/command"
|
||||||
|
"v2ray.com/core/commands/base"
|
||||||
|
"v2ray.com/core/infra/conf/serial"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdRemoveOutbounds = &base.Command{
|
||||||
|
CustomFlags: true,
|
||||||
|
UsageLine: "{{.Exec}} api rmo [--server=127.0.0.1:8080] <json_file|tag> [json_file] [tag]...",
|
||||||
|
Short: "Remove outbounds",
|
||||||
|
Long: `
|
||||||
|
Remove outbounds from V2Ray.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
-s, -server
|
||||||
|
The API server address. Default 127.0.0.1:8080
|
||||||
|
|
||||||
|
-t, -timeout
|
||||||
|
Timeout seconds to call API. Default 3
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json "tag name"
|
||||||
|
`,
|
||||||
|
Run: executeRemoveOutbounds,
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeRemoveOutbounds(cmd *base.Command, args []string) {
|
||||||
|
setSharedFlags(cmd)
|
||||||
|
cmd.Flag.Parse(args)
|
||||||
|
unnamedArgs := cmd.Flag.Args()
|
||||||
|
if len(unnamedArgs) == 0 {
|
||||||
|
fmt.Println("reading from stdin:")
|
||||||
|
unnamedArgs = []string{"stdin:"}
|
||||||
|
}
|
||||||
|
|
||||||
|
tags := make([]string, 0)
|
||||||
|
for _, arg := range unnamedArgs {
|
||||||
|
if r, err := loadArg(arg); err == nil {
|
||||||
|
conf, err := serial.DecodeJSONConfig(r)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to decode %s: %s", arg, err)
|
||||||
|
}
|
||||||
|
outs := conf.OutboundConfigs
|
||||||
|
for _, o := range outs {
|
||||||
|
tags = append(tags, o.Tag)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// take request as tag
|
||||||
|
tags = append(tags, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tags) == 0 {
|
||||||
|
base.Fatalf("no outbound to remove")
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, ctx, close := dialAPIServer()
|
||||||
|
defer close()
|
||||||
|
|
||||||
|
client := handlerService.NewHandlerServiceClient(conn)
|
||||||
|
for _, tag := range tags {
|
||||||
|
fmt.Println("removing:", tag)
|
||||||
|
r := &handlerService.RemoveOutboundRequest{
|
||||||
|
Tag: tag,
|
||||||
|
}
|
||||||
|
resp, err := client.RemoveOutbound(ctx, r)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to remove outbound: %s", err)
|
||||||
|
}
|
||||||
|
showResponese(resp)
|
||||||
|
}
|
||||||
|
}
|
118
commands/all/api/shared.go
Normal file
118
commands/all/api/shared.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
"v2ray.com/core/commands/base"
|
||||||
|
"v2ray.com/core/common/buf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type serviceHandler func(ctx context.Context, conn *grpc.ClientConn, cmd *base.Command, args []string) string
|
||||||
|
|
||||||
|
var (
|
||||||
|
apiServerAddrPtr string
|
||||||
|
apiTimeout int
|
||||||
|
)
|
||||||
|
|
||||||
|
func setSharedFlags(cmd *base.Command) {
|
||||||
|
cmd.Flag.StringVar(&apiServerAddrPtr, "s", "127.0.0.1:8080", "")
|
||||||
|
cmd.Flag.StringVar(&apiServerAddrPtr, "server", "127.0.0.1:8080", "")
|
||||||
|
cmd.Flag.IntVar(&apiTimeout, "t", 3, "")
|
||||||
|
cmd.Flag.IntVar(&apiTimeout, "timeout", 3, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func dialAPIServer() (conn *grpc.ClientConn, ctx context.Context, close func()) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(apiTimeout)*time.Second)
|
||||||
|
conn, err := grpc.DialContext(ctx, apiServerAddrPtr, grpc.WithInsecure(), grpc.WithBlock())
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to dial %s", apiServerAddrPtr)
|
||||||
|
}
|
||||||
|
close = func() {
|
||||||
|
cancel()
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadArg loads one arg, maybe an remote url, or local file path
|
||||||
|
func loadArg(arg string) (out io.Reader, err error) {
|
||||||
|
var data []byte
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(arg, "http://"), strings.HasPrefix(arg, "https://"):
|
||||||
|
data, err = fetchHTTPContent(arg)
|
||||||
|
|
||||||
|
case arg == "stdin:":
|
||||||
|
data, err = ioutil.ReadAll(os.Stdin)
|
||||||
|
|
||||||
|
default:
|
||||||
|
data, err = ioutil.ReadFile(arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out = bytes.NewBuffer(data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchHTTPContent dials https for remote content
|
||||||
|
func fetchHTTPContent(target string) ([]byte, error) {
|
||||||
|
parsedTarget, err := url.Parse(target)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if s := strings.ToLower(parsedTarget.Scheme); s != "http" && s != "https" {
|
||||||
|
return nil, fmt.Errorf("invalid scheme: %s", 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 nil, fmt.Errorf("failed to dial to %s", target)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return nil, fmt.Errorf("unexpected HTTP status code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := buf.ReadAllToBytes(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read HTTP response")
|
||||||
|
}
|
||||||
|
|
||||||
|
return content, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func showResponese(m proto.Message) {
|
||||||
|
msg := ""
|
||||||
|
bs, err := proto.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
msg = err.Error()
|
||||||
|
} else {
|
||||||
|
msg = string(bs)
|
||||||
|
msg = strings.TrimSpace(msg)
|
||||||
|
}
|
||||||
|
if msg == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(msg)
|
||||||
|
}
|
55
commands/all/api/stats_get.go
Normal file
55
commands/all/api/stats_get.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
statsService "v2ray.com/core/app/stats/command"
|
||||||
|
"v2ray.com/core/commands/base"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdGetStats = &base.Command{
|
||||||
|
CustomFlags: true,
|
||||||
|
UsageLine: "{{.Exec}} api stats [--server=127.0.0.1:8080] [-name '']",
|
||||||
|
Short: "Get statistics",
|
||||||
|
Long: `
|
||||||
|
Get statistics from V2Ray.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
-s, -server
|
||||||
|
The API server address. Default 127.0.0.1:8080
|
||||||
|
|
||||||
|
-t, -timeout
|
||||||
|
Timeout seconds to call API. Default 3
|
||||||
|
|
||||||
|
-name
|
||||||
|
Name of the stat counter.
|
||||||
|
|
||||||
|
-reset
|
||||||
|
Reset the counter to fetching its value.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -name "inbound>>>statin>>>traffic>>>downlink"
|
||||||
|
`,
|
||||||
|
Run: executeGetStats,
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeGetStats(cmd *base.Command, args []string) {
|
||||||
|
setSharedFlags(cmd)
|
||||||
|
statName := cmd.Flag.String("name", "", "")
|
||||||
|
reset := cmd.Flag.Bool("reset", false, "")
|
||||||
|
cmd.Flag.Parse(args)
|
||||||
|
|
||||||
|
conn, ctx, close := dialAPIServer()
|
||||||
|
defer close()
|
||||||
|
|
||||||
|
client := statsService.NewStatsServiceClient(conn)
|
||||||
|
r := &statsService.GetStatsRequest{
|
||||||
|
Name: *statName,
|
||||||
|
Reset_: *reset,
|
||||||
|
}
|
||||||
|
resp, err := client.GetStats(ctx, r)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to get stats: %s", err)
|
||||||
|
}
|
||||||
|
showResponese(resp)
|
||||||
|
}
|
55
commands/all/api/stats_query.go
Normal file
55
commands/all/api/stats_query.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
statsService "v2ray.com/core/app/stats/command"
|
||||||
|
"v2ray.com/core/commands/base"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdQueryStats = &base.Command{
|
||||||
|
CustomFlags: true,
|
||||||
|
UsageLine: "{{.Exec}} api statsquery [--server=127.0.0.1:8080] [-pattern '']",
|
||||||
|
Short: "Query statistics",
|
||||||
|
Long: `
|
||||||
|
Query statistics from V2Ray.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
-s, -server
|
||||||
|
The API server address. Default 127.0.0.1:8080
|
||||||
|
|
||||||
|
-t, -timeout
|
||||||
|
Timeout seconds to call API. Default 3
|
||||||
|
|
||||||
|
-pattern
|
||||||
|
Pattern of the query.
|
||||||
|
|
||||||
|
-reset
|
||||||
|
Reset the counter to fetching its value.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -pattern "counter_"
|
||||||
|
`,
|
||||||
|
Run: executeQueryStats,
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeQueryStats(cmd *base.Command, args []string) {
|
||||||
|
setSharedFlags(cmd)
|
||||||
|
pattern := cmd.Flag.String("pattern", "", "")
|
||||||
|
reset := cmd.Flag.Bool("reset", false, "")
|
||||||
|
cmd.Flag.Parse(args)
|
||||||
|
|
||||||
|
conn, ctx, close := dialAPIServer()
|
||||||
|
defer close()
|
||||||
|
|
||||||
|
client := statsService.NewStatsServiceClient(conn)
|
||||||
|
r := &statsService.QueryStatsRequest{
|
||||||
|
Pattern: *pattern,
|
||||||
|
Reset_: *reset,
|
||||||
|
}
|
||||||
|
resp, err := client.QueryStats(ctx, r)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to query stats: %s", err)
|
||||||
|
}
|
||||||
|
showResponese(resp)
|
||||||
|
}
|
40
commands/all/api/stats_sys.go
Normal file
40
commands/all/api/stats_sys.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
statsService "v2ray.com/core/app/stats/command"
|
||||||
|
"v2ray.com/core/commands/base"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdSysStats = &base.Command{
|
||||||
|
CustomFlags: true,
|
||||||
|
UsageLine: "{{.Exec}} api statssys [--server=127.0.0.1:8080]",
|
||||||
|
Short: "Get system statistics",
|
||||||
|
Long: `
|
||||||
|
Get system statistics from V2Ray.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
-s, -server
|
||||||
|
The API server address. Default 127.0.0.1:8080
|
||||||
|
|
||||||
|
-t, -timeout
|
||||||
|
Timeout seconds to call API. Default 3
|
||||||
|
`,
|
||||||
|
Run: executeSysStats,
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeSysStats(cmd *base.Command, args []string) {
|
||||||
|
setSharedFlags(cmd)
|
||||||
|
cmd.Flag.Parse(args)
|
||||||
|
|
||||||
|
conn, ctx, close := dialAPIServer()
|
||||||
|
defer close()
|
||||||
|
|
||||||
|
client := statsService.NewStatsServiceClient(conn)
|
||||||
|
r := &statsService.SysStatsRequest{}
|
||||||
|
resp, err := client.GetSysStats(ctx, r)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to get sys stats: %s", err)
|
||||||
|
}
|
||||||
|
showResponese(resp)
|
||||||
|
}
|
@ -1,16 +1,20 @@
|
|||||||
package all
|
package all
|
||||||
|
|
||||||
import "github.com/v2fly/v2ray-core/v4/commands/base"
|
import (
|
||||||
|
"github.com/v2fly/v2ray-core/v4/commands/all/api"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/commands/all/tls"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/commands/base"
|
||||||
|
)
|
||||||
|
|
||||||
// go:generate go run v2ray.com/core/common/errors/errorgen
|
// go:generate go run v2ray.com/core/common/errors/errorgen
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
base.RootCommand.Commands = append(
|
base.RootCommand.Commands = append(
|
||||||
base.RootCommand.Commands,
|
base.RootCommand.Commands,
|
||||||
cmdAPI,
|
api.CmdAPI,
|
||||||
cmdConvert,
|
cmdConvert,
|
||||||
cmdLove,
|
cmdLove,
|
||||||
cmdTLS,
|
tls.CmdTLS,
|
||||||
cmdUUID,
|
cmdUUID,
|
||||||
cmdVerify,
|
cmdVerify,
|
||||||
cmdMerge,
|
cmdMerge,
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
package all
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/v2fly/v2ray-core/v4/commands/all/tlscmd"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/commands/base"
|
|
||||||
)
|
|
||||||
|
|
||||||
var cmdTLS = &base.Command{
|
|
||||||
UsageLine: "{{.Exec}} tls",
|
|
||||||
Short: "TLS tools",
|
|
||||||
Long: `{{.Exec}} tls provides tools for TLS.
|
|
||||||
`,
|
|
||||||
|
|
||||||
Commands: []*base.Command{
|
|
||||||
tlscmd.CmdCert,
|
|
||||||
tlscmd.CmdPing,
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package tlscmd
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -14,8 +14,8 @@ import (
|
|||||||
"github.com/v2fly/v2ray-core/v4/common/task"
|
"github.com/v2fly/v2ray-core/v4/common/task"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdCert is the tls cert command
|
// cmdCert is the tls cert command
|
||||||
var CmdCert = &base.Command{
|
var cmdCert = &base.Command{
|
||||||
UsageLine: "{{.Exec}} tls cert [--ca] [--domain=v2ray.com] [--expire=240h]",
|
UsageLine: "{{.Exec}} tls cert [--ca] [--domain=v2ray.com] [--expire=240h]",
|
||||||
Short: "Generate TLS certificates",
|
Short: "Generate TLS certificates",
|
||||||
Long: `
|
Long: `
|
||||||
@ -44,22 +44,22 @@ Arguments:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
CmdCert.Run = executeCert // break init loop
|
cmdCert.Run = executeCert // break init loop
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
certDomainNames stringList
|
certDomainNames stringList
|
||||||
_ = func() bool {
|
_ = func() bool {
|
||||||
CmdCert.Flag.Var(&certDomainNames, "domain", "Domain name for the certificate")
|
cmdCert.Flag.Var(&certDomainNames, "domain", "Domain name for the certificate")
|
||||||
return true
|
return true
|
||||||
}()
|
}()
|
||||||
|
|
||||||
certCommonName = CmdCert.Flag.String("name", "V2Ray Inc", "The common name of this certificate")
|
certCommonName = cmdCert.Flag.String("name", "V2Ray Inc", "The common name of this certificate")
|
||||||
certOrganization = CmdCert.Flag.String("org", "V2Ray Inc", "Organization of the certificate")
|
certOrganization = cmdCert.Flag.String("org", "V2Ray Inc", "Organization of the certificate")
|
||||||
certIsCA = CmdCert.Flag.Bool("ca", false, "Whether this certificate is a CA")
|
certIsCA = cmdCert.Flag.Bool("ca", false, "Whether this certificate is a CA")
|
||||||
certJSONOutput = CmdCert.Flag.Bool("json", true, "Print certificate in JSON format")
|
certJSONOutput = cmdCert.Flag.Bool("json", true, "Print certificate in JSON format")
|
||||||
certFileOutput = CmdCert.Flag.String("file", "", "Save certificate in file.")
|
certFileOutput = cmdCert.Flag.String("file", "", "Save certificate in file.")
|
||||||
certExpire = CmdCert.Flag.Duration("expire", time.Hour*24*90 /* 90 days */, "Time until the certificate expires. Default value 3 months.")
|
certExpire = cmdCert.Flag.Duration("expire", time.Hour*24*90 /* 90 days */, "Time until the certificate expires. Default value 3 months.")
|
||||||
)
|
)
|
||||||
|
|
||||||
func executeCert(cmd *base.Command, args []string) {
|
func executeCert(cmd *base.Command, args []string) {
|
@ -1,4 +1,4 @@
|
|||||||
package tlscmd
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
@ -11,8 +11,8 @@ import (
|
|||||||
v2tls "github.com/v2fly/v2ray-core/v4/transport/internet/tls"
|
v2tls "github.com/v2fly/v2ray-core/v4/transport/internet/tls"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdPing is the tls ping command
|
// cmdPing is the tls ping command
|
||||||
var CmdPing = &base.Command{
|
var cmdPing = &base.Command{
|
||||||
UsageLine: "{{.Exec}} tls ping [-ip <ip>] <domain>",
|
UsageLine: "{{.Exec}} tls ping [-ip <ip>] <domain>",
|
||||||
Short: "Ping the domain with TLS handshake",
|
Short: "Ping the domain with TLS handshake",
|
||||||
Long: `
|
Long: `
|
||||||
@ -26,19 +26,19 @@ Arguments:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
CmdPing.Run = executePing // break init loop
|
cmdPing.Run = executePing // break init loop
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
pingIPStr = CmdPing.Flag.String("ip", "", "")
|
pingIPStr = cmdPing.Flag.String("ip", "", "")
|
||||||
)
|
)
|
||||||
|
|
||||||
func executePing(cmd *base.Command, args []string) {
|
func executePing(cmd *base.Command, args []string) {
|
||||||
if CmdPing.Flag.NArg() < 1 {
|
if cmdPing.Flag.NArg() < 1 {
|
||||||
base.Fatalf("domain not specified")
|
base.Fatalf("domain not specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
domain := CmdPing.Flag.Arg(0)
|
domain := cmdPing.Flag.Arg(0)
|
||||||
fmt.Println("Tls ping: ", domain)
|
fmt.Println("Tls ping: ", domain)
|
||||||
|
|
||||||
var ip net.IP
|
var ip net.IP
|
18
commands/all/tls/tls.go
Normal file
18
commands/all/tls/tls.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"v2ray.com/core/commands/base"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdTLS holds all tls sub commands
|
||||||
|
var CmdTLS = &base.Command{
|
||||||
|
UsageLine: "{{.Exec}} tls",
|
||||||
|
Short: "TLS tools",
|
||||||
|
Long: `{{.Exec}} {{.LongName}} provides tools for TLS.
|
||||||
|
`,
|
||||||
|
|
||||||
|
Commands: []*base.Command{
|
||||||
|
cmdCert,
|
||||||
|
cmdPing,
|
||||||
|
},
|
||||||
|
}
|
@ -22,13 +22,19 @@ type Command struct {
|
|||||||
Run func(cmd *Command, args []string)
|
Run func(cmd *Command, args []string)
|
||||||
|
|
||||||
// UsageLine is the one-line usage message.
|
// UsageLine is the one-line usage message.
|
||||||
// The words between "go" and the first flag or argument in the line are taken to be the command name.
|
// The words between the first word (the "executable name") and the first flag or argument in the line are taken to be the command name.
|
||||||
|
//
|
||||||
|
// UsageLine supports go template syntax. It's recommended to use "{{.Exec}}" instead of hardcoding name
|
||||||
UsageLine string
|
UsageLine string
|
||||||
|
|
||||||
// Short is the short description shown in the 'go help' output.
|
// Short is the short description shown in the 'go help' output.
|
||||||
|
//
|
||||||
|
// Note: Short does not support go template syntax.
|
||||||
Short string
|
Short string
|
||||||
|
|
||||||
// Long is the long message shown in the 'go help <this-command>' output.
|
// Long is the long message shown in the 'go help <this-command>' output.
|
||||||
|
//
|
||||||
|
// Long supports go template syntax. It's recommended to use "{{.Exec}}", "{{.LongName}}" instead of hardcoding strings
|
||||||
Long string
|
Long string
|
||||||
|
|
||||||
// Flag is a set of flags specific to this command.
|
// Flag is a set of flags specific to this command.
|
||||||
@ -44,16 +50,18 @@ type Command struct {
|
|||||||
Commands []*Command
|
Commands []*Command
|
||||||
}
|
}
|
||||||
|
|
||||||
// LongName returns the command's long name: all the words in the usage line between "go" and a flag or argument,
|
// LongName returns the command's long name: all the words in the usage line between first word (e.g. "v2ray") and a flag or argument,
|
||||||
func (c *Command) LongName() string {
|
func (c *Command) LongName() string {
|
||||||
name := c.UsageLine
|
name := c.UsageLine
|
||||||
if i := strings.Index(name, " ["); i >= 0 {
|
if i := strings.Index(name, " ["); i >= 0 {
|
||||||
name = name[:i]
|
name = strings.TrimSpace(name[:i])
|
||||||
}
|
}
|
||||||
if name == CommandEnv.Exec {
|
if i := strings.Index(name, " "); i >= 0 {
|
||||||
return ""
|
name = name[i+1:]
|
||||||
|
} else {
|
||||||
|
name = ""
|
||||||
}
|
}
|
||||||
return strings.TrimPrefix(name, CommandEnv.Exec+" ")
|
return strings.TrimSpace(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the command's short name: the last word in the usage line before a flag or argument.
|
// Name returns the command's short name: the last word in the usage line before a flag or argument.
|
||||||
@ -62,11 +70,12 @@ func (c *Command) Name() string {
|
|||||||
if i := strings.LastIndex(name, " "); i >= 0 {
|
if i := strings.LastIndex(name, " "); i >= 0 {
|
||||||
name = name[i+1:]
|
name = name[i+1:]
|
||||||
}
|
}
|
||||||
return name
|
return strings.TrimSpace(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage prints usage of the Command
|
// Usage prints usage of the Command
|
||||||
func (c *Command) Usage() {
|
func (c *Command) Usage() {
|
||||||
|
buildCommandText(c)
|
||||||
fmt.Fprintf(os.Stderr, "usage: %s\n", c.UsageLine)
|
fmt.Fprintf(os.Stderr, "usage: %s\n", c.UsageLine)
|
||||||
fmt.Fprintf(os.Stderr, "Run '%s help %s' for details.\n", CommandEnv.Exec, c.LongName())
|
fmt.Fprintf(os.Stderr, "Run '%s help %s' for details.\n", CommandEnv.Exec, c.LongName())
|
||||||
SetExitStatus(2)
|
SetExitStatus(2)
|
||||||
|
@ -14,7 +14,6 @@ import (
|
|||||||
|
|
||||||
// Execute excute the commands
|
// Execute excute the commands
|
||||||
func Execute() {
|
func Execute() {
|
||||||
buildCommandsText(RootCommand)
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
args := flag.Args()
|
args := flag.Args()
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
@ -61,6 +60,7 @@ BigCmdLoop:
|
|||||||
args = cmd.Flag.Args()
|
args = cmd.Flag.Args()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildCommandText(cmd)
|
||||||
cmd.Run(cmd, args)
|
cmd.Run(cmd, args)
|
||||||
Exit()
|
Exit()
|
||||||
return
|
return
|
||||||
|
@ -41,6 +41,7 @@ Args:
|
|||||||
if len(cmd.Commands) > 0 {
|
if len(cmd.Commands) > 0 {
|
||||||
PrintUsage(os.Stdout, cmd)
|
PrintUsage(os.Stdout, cmd)
|
||||||
} else {
|
} else {
|
||||||
|
buildCommandText(cmd)
|
||||||
tmpl(os.Stdout, helpTemplate, makeTmplData(cmd))
|
tmpl(os.Stdout, helpTemplate, makeTmplData(cmd))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,24 +120,21 @@ func width(width int, value string) string {
|
|||||||
|
|
||||||
// PrintUsage prints usage of cmd to w
|
// PrintUsage prints usage of cmd to w
|
||||||
func PrintUsage(w io.Writer, cmd *Command) {
|
func PrintUsage(w io.Writer, cmd *Command) {
|
||||||
|
buildCommandText(cmd)
|
||||||
bw := bufio.NewWriter(w)
|
bw := bufio.NewWriter(w)
|
||||||
tmpl(bw, usageTemplate, makeTmplData(cmd))
|
tmpl(bw, usageTemplate, makeTmplData(cmd))
|
||||||
bw.Flush()
|
bw.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildCommandsText build text of command and its children as template
|
|
||||||
func buildCommandsText(cmd *Command) {
|
|
||||||
buildCommandText(cmd)
|
|
||||||
for _, cmd := range cmd.Commands {
|
|
||||||
buildCommandsText(cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildCommandText build command text as template
|
// buildCommandText build command text as template
|
||||||
func buildCommandText(cmd *Command) {
|
func buildCommandText(cmd *Command) {
|
||||||
cmd.UsageLine = buildText(cmd.UsageLine, makeTmplData(cmd))
|
data := makeTmplData(cmd)
|
||||||
cmd.Short = buildText(cmd.Short, makeTmplData(cmd))
|
cmd.UsageLine = buildText(cmd.UsageLine, data)
|
||||||
cmd.Long = buildText(cmd.Long, makeTmplData(cmd))
|
// DO NOT SUPPORT ".Short":
|
||||||
|
// - It's not necessary
|
||||||
|
// - Or, we have to build text for all sub commands of current command, like "v2ray help api"
|
||||||
|
// cmd.Short = buildText(cmd.Short, data)
|
||||||
|
cmd.Long = buildText(cmd.Long, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildText(text string, data interface{}) string {
|
func buildText(text string, data interface{}) string {
|
||||||
|
Loading…
Reference in New Issue
Block a user