mirror of
https://github.com/v2fly/v2ray-core.git
synced 2024-11-12 07:19:01 -05:00
go style commands, merge v2ctl commands, v5 oriented (#369)
* go style commands merge v2ctl commandsw * migrate go style commands to v2ctl * fixes & code optimize * sort the commands * update commands description * restore old proto golang.org proto has removed UnmarshalText, without alternative * add test command * remove unused code * code optimize and fix * The commit simplifies the run and test commands code, * Fixes a hidden issue that the format flag not applied in command "v2ray test -format=pb ..." * fix default loader logic
This commit is contained in:
parent
bb74ef99e2
commit
521120d196
@ -1,9 +1,8 @@
|
|||||||
package control
|
package all
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -13,71 +12,66 @@ import (
|
|||||||
|
|
||||||
logService "v2ray.com/core/app/log/command"
|
logService "v2ray.com/core/app/log/command"
|
||||||
statsService "v2ray.com/core/app/stats/command"
|
statsService "v2ray.com/core/app/stats/command"
|
||||||
"v2ray.com/core/common"
|
"v2ray.com/core/commands/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
type APICommand struct{}
|
// 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.
|
||||||
|
|
||||||
func (c *APICommand) Name() string {
|
The following methods are currently supported:
|
||||||
return "api"
|
|
||||||
|
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 (c *APICommand) Description() Description {
|
func init() {
|
||||||
return Description{
|
cmdAPI.Run = executeAPI // break init loop
|
||||||
Short: "Call V2Ray API",
|
|
||||||
Usage: []string{
|
|
||||||
"v2ctl api [--server=127.0.0.1:8080] Service.Method Request",
|
|
||||||
"Call an API in an V2Ray process.",
|
|
||||||
"The following methods are currently supported:",
|
|
||||||
"\tLoggerService.RestartLogger",
|
|
||||||
"\tStatsService.GetStats",
|
|
||||||
"\tStatsService.QueryStats",
|
|
||||||
"API calls in this command have a timeout to the server of 3 seconds.",
|
|
||||||
"Examples:",
|
|
||||||
"v2ctl api --server=127.0.0.1:8080 LoggerService.RestartLogger '' ",
|
|
||||||
"v2ctl api --server=127.0.0.1:8080 StatsService.QueryStats 'pattern: \"\" reset: false'",
|
|
||||||
"v2ctl api --server=127.0.0.1:8080 StatsService.GetStats 'name: \"inbound>>>statin>>>traffic>>>downlink\" reset: false'",
|
|
||||||
"v2ctl api --server=127.0.0.1:8080 StatsService.GetSysStats ''",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *APICommand) Execute(args []string) error {
|
var (
|
||||||
fs := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
apiServerAddrPtr = cmdAPI.Flag.String("server", "127.0.0.1:8080", "")
|
||||||
|
)
|
||||||
|
|
||||||
serverAddrPtr := fs.String("server", "127.0.0.1:8080", "Server address")
|
func executeAPI(cmd *base.Command, args []string) {
|
||||||
|
unnamedArgs := cmdAPI.Flag.Args()
|
||||||
if err := fs.Parse(args); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
unnamedArgs := fs.Args()
|
|
||||||
if len(unnamedArgs) < 2 {
|
if len(unnamedArgs) < 2 {
|
||||||
return newError("service name or request not specified.")
|
base.Fatalf("service name or request not specified.")
|
||||||
}
|
}
|
||||||
|
|
||||||
service, method := getServiceMethod(unnamedArgs[0])
|
service, method := getServiceMethod(unnamedArgs[0])
|
||||||
handler, found := serivceHandlerMap[strings.ToLower(service)]
|
handler, found := serivceHandlerMap[strings.ToLower(service)]
|
||||||
if !found {
|
if !found {
|
||||||
return newError("unknown service: ", service)
|
base.Fatalf("unknown service: %s", service)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
conn, err := grpc.DialContext(ctx, *serverAddrPtr, grpc.WithInsecure(), grpc.WithBlock())
|
conn, err := grpc.DialContext(ctx, *apiServerAddrPtr, grpc.WithInsecure(), grpc.WithBlock())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newError("failed to dial ", *serverAddrPtr).Base(err)
|
base.Fatalf("failed to dial %s", *apiServerAddrPtr)
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
response, err := handler(ctx, conn, method, unnamedArgs[1])
|
response, err := handler(ctx, conn, method, unnamedArgs[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newError("failed to call service ", unnamedArgs[0]).Base(err)
|
base.Fatalf("failed to call service %s: %s", unnamedArgs[0], err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(response)
|
fmt.Println(response)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getServiceMethod(s string) (string, string) {
|
func getServiceMethod(s string) (string, string) {
|
||||||
@ -103,9 +97,6 @@ func callLogService(ctx context.Context, conn *grpc.ClientConn, method string, r
|
|||||||
switch strings.ToLower(method) {
|
switch strings.ToLower(method) {
|
||||||
case "restartlogger":
|
case "restartlogger":
|
||||||
r := &logService.RestartLoggerRequest{}
|
r := &logService.RestartLoggerRequest{}
|
||||||
if err := proto.UnmarshalText(request, r); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
resp, err := client.RestartLogger(ctx, r)
|
resp, err := client.RestartLogger(ctx, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -152,7 +143,3 @@ func callStatsService(ctx context.Context, conn *grpc.ClientConn, method string,
|
|||||||
return "", errors.New("Unknown method: " + method)
|
return "", errors.New("Unknown method: " + method)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
common.Must(RegisterCommand(&APICommand{}))
|
|
||||||
}
|
|
17
commands/all/commands.go
Normal file
17
commands/all/commands.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package all
|
||||||
|
|
||||||
|
import "v2ray.com/core/commands/base"
|
||||||
|
|
||||||
|
// go:generate go run v2ray.com/core/common/errors/errorgen
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
base.RootCommand.Commands = append(
|
||||||
|
base.RootCommand.Commands,
|
||||||
|
cmdAPI,
|
||||||
|
cmdConvert,
|
||||||
|
cmdLove,
|
||||||
|
cmdTLS,
|
||||||
|
cmdUUID,
|
||||||
|
cmdVerify,
|
||||||
|
)
|
||||||
|
}
|
126
commands/all/convert.go
Normal file
126
commands/all/convert.go
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package all
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
"v2ray.com/core/commands/base"
|
||||||
|
"v2ray.com/core/common"
|
||||||
|
"v2ray.com/core/common/buf"
|
||||||
|
"v2ray.com/core/infra/conf"
|
||||||
|
"v2ray.com/core/infra/conf/serial"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdConvert = &base.Command{
|
||||||
|
UsageLine: "{{.Exec}} convert [json file] [json file] ...",
|
||||||
|
Short: "Convert multiple json config to protobuf",
|
||||||
|
Long: `
|
||||||
|
Convert multiple json config to protobuf.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
{{.Exec}} {{.LongName}} config.json c1.json c2.json <url>.json
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cmdConvert.Run = executeConvert // break init loop
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeConvert(cmd *base.Command, args []string) {
|
||||||
|
unnamedArgs := cmdConvert.Flag.Args()
|
||||||
|
if len(unnamedArgs) < 1 {
|
||||||
|
base.Fatalf("empty config list")
|
||||||
|
}
|
||||||
|
|
||||||
|
conf := &conf.Config{}
|
||||||
|
for _, arg := range unnamedArgs {
|
||||||
|
fmt.Fprintf(os.Stderr, "Read config: %s", arg)
|
||||||
|
r, err := loadArg(arg)
|
||||||
|
common.Must(err)
|
||||||
|
c, err := serial.DecodeJSONConfig(r)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
conf.Override(c, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
pbConfig, err := conf.Build()
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesConfig, err := proto.Marshal(pbConfig)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to marshal proto config: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stdout.Write(bytesConfig); err != nil {
|
||||||
|
base.Fatalf("failed to write proto config: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, 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 nil, newError("failed to dial to ", target).Base(err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return nil, newError("unexpected HTTP status code: ", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := buf.ReadAllToBytes(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to read HTTP response").Base(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return content, nil
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package control
|
package all
|
||||||
|
|
||||||
import "v2ray.com/core/common/errors"
|
import "v2ray.com/core/common/errors"
|
||||||
|
|
37
commands/all/love.go
Normal file
37
commands/all/love.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package all
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"v2ray.com/core/commands/base"
|
||||||
|
"v2ray.com/core/common"
|
||||||
|
"v2ray.com/core/common/platform"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdLove = &base.Command{
|
||||||
|
UsageLine: "{{.Exec}} lovevictoria",
|
||||||
|
Short: "", // set Short to "" hides the command
|
||||||
|
Long: "",
|
||||||
|
Run: executeLove,
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeLove(cmd *base.Command, args []string) {
|
||||||
|
const content = "H4sIAAAAAAAC/4SVMaskNwzH+/kUW6izcSthMGrcqLhVk0rdQS5cSMg7Xu4S0vizB8meZd57M3ta2GHX/ukvyZZmY2ZKDMzCzJyY5yOlxKII1omsf+qkBiiC6WhbYsbkjDAfySQsJqD3jtrD0EBM3sBHzG3kUsrglIQREXonpd47kYIi4AHmgI9Wcq2jlJITC6JZJ+v3ECYzBMAHyYm392yuY4zWsjACmHZSh6l3A0JETzGlWZqDsnArpTg62mhJONhOdO90p97V1BAnteoaOcuummtrrtuERQwUiJwP8a4KGKcyxdOCw1spOY+WHueFqmakAIgUSSuhwKNgobxKXSLbtg6r5cFmBiAeF6yCkYycmv+BiCIiW8ScHa3DgxAuZQbRhFNrLTFo96RBmx9jKWWG5nBsjyJzuIkftUblonppZU5t5LzwIks5L1a4lijagQxLokbIYwxfytNDC+XQqrWW9fzAunhqh5/Tg8PuaMw0d/Tcw3iDO81bHfWM/AnutMh2xqSUntMzd3wHDy9iHMQz8bmUZYvqedTJ5GgOnrNt7FIbSlwXE3wDI19n/KA38MsLaP4l89b5F8AV3ESOMIEhIBgezHBc0H6xV9KbaXwMvPcNvIHcC0C7UPZQx4JVTb35/AneSQq+bAYXsBmY7TCRupF2NTdVm/+ch22xa0pvRERKqt1oxj9DUbXzU84Gvj5hc5a81SlAUwMwgEs4T9+7sg9lb9h+908MWiKV8xtWciVTmnB3tivRjNerfXdxpfEBbq2NUvLMM5R9NLuyQg8nXT0PIh1xPd/wrcV49oJ6zbZaPlj2V87IY9T3F2XCOcW2MbZyZd49H+9m81E1N9SxlU+ff/1y+/f3719vf7788+Ugv/ffbMIH7ZNj0dsT4WMHHwLPu/Rp2O75uh99AK+N2xn7ZHq1OK6gczkN+9ngdOl1Qvki5xwSR8vFX6D+9vXA97B/+fr5rz9u/738uP328urP19vfP759e3n9Xs6jamvqlfJ/AAAA//+YAMZjDgkAAA=="
|
||||||
|
c, err := base64.StdEncoding.DecodeString(content)
|
||||||
|
common.Must(err)
|
||||||
|
reader, err := gzip.NewReader(bytes.NewBuffer(c))
|
||||||
|
common.Must(err)
|
||||||
|
b := make([]byte, 4096)
|
||||||
|
nBytes, _ := reader.Read(b)
|
||||||
|
|
||||||
|
bb := bytes.NewBuffer(b[:nBytes])
|
||||||
|
scanner := bufio.NewScanner(bb)
|
||||||
|
for scanner.Scan() {
|
||||||
|
s := scanner.Text()
|
||||||
|
fmt.Print(s + platform.LineSeparator())
|
||||||
|
}
|
||||||
|
}
|
18
commands/all/tls.go
Normal file
18
commands/all/tls.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package all
|
||||||
|
|
||||||
|
import (
|
||||||
|
"v2ray.com/core/commands/all/tlscmd"
|
||||||
|
"v2ray.com/core/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,
|
||||||
|
},
|
||||||
|
}
|
143
commands/all/tlscmd/cert.go
Normal file
143
commands/all/tlscmd/cert.go
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package tlscmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"v2ray.com/core/commands/base"
|
||||||
|
"v2ray.com/core/common"
|
||||||
|
"v2ray.com/core/common/protocol/tls/cert"
|
||||||
|
"v2ray.com/core/common/task"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdCert is the tls cert command
|
||||||
|
var CmdCert = &base.Command{
|
||||||
|
UsageLine: "{{.Exec}} tls cert [--ca] [--domain=v2ray.com] [--expire=240h]",
|
||||||
|
Short: "Generate TLS certificates",
|
||||||
|
Long: `
|
||||||
|
Generate TLS certificates.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
-domain=domain_name
|
||||||
|
The domain name for the certificate.
|
||||||
|
|
||||||
|
-org=organization
|
||||||
|
The organization name for the certificate.
|
||||||
|
|
||||||
|
-ca
|
||||||
|
Whether this certificate is a CA
|
||||||
|
|
||||||
|
-json
|
||||||
|
The output of certificate to JSON
|
||||||
|
|
||||||
|
-file
|
||||||
|
The certificate path to save.
|
||||||
|
|
||||||
|
-expire
|
||||||
|
Expire time of the certificate. Default value 3 months.
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
CmdCert.Run = executeCert // break init loop
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
certDomainNames stringList
|
||||||
|
_ = func() bool {
|
||||||
|
CmdCert.Flag.Var(&certDomainNames, "domain", "Domain name for the certificate")
|
||||||
|
return true
|
||||||
|
}()
|
||||||
|
|
||||||
|
certCommonName = CmdCert.Flag.String("name", "V2Ray Inc", "The common name of this certificate")
|
||||||
|
certOrganization = CmdCert.Flag.String("org", "V2Ray Inc", "Organization of the certificate")
|
||||||
|
certIsCA = CmdCert.Flag.Bool("ca", false, "Whether this certificate is a CA")
|
||||||
|
certJSONOutput = CmdCert.Flag.Bool("json", true, "Print certificate in JSON format")
|
||||||
|
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.")
|
||||||
|
)
|
||||||
|
|
||||||
|
func executeCert(cmd *base.Command, args []string) {
|
||||||
|
var opts []cert.Option
|
||||||
|
if *certIsCA {
|
||||||
|
opts = append(opts, cert.Authority(*certIsCA))
|
||||||
|
opts = append(opts, cert.KeyUsage(x509.KeyUsageCertSign|x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature))
|
||||||
|
}
|
||||||
|
|
||||||
|
opts = append(opts, cert.NotAfter(time.Now().Add(*certExpire)))
|
||||||
|
opts = append(opts, cert.CommonName(*certCommonName))
|
||||||
|
if len(certDomainNames) > 0 {
|
||||||
|
opts = append(opts, cert.DNSNames(certDomainNames...))
|
||||||
|
}
|
||||||
|
opts = append(opts, cert.Organization(*certOrganization))
|
||||||
|
|
||||||
|
cert, err := cert.Generate(nil, opts...)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to generate TLS certificate: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *certJSONOutput {
|
||||||
|
printJSON(cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(*certFileOutput) > 0 {
|
||||||
|
if err := printFile(cert, *certFileOutput); err != nil {
|
||||||
|
base.Fatalf("failed to save file: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printJSON(certificate *cert.Certificate) {
|
||||||
|
certPEM, keyPEM := certificate.ToPEM()
|
||||||
|
jCert := &jsonCert{
|
||||||
|
Certificate: strings.Split(strings.TrimSpace(string(certPEM)), "\n"),
|
||||||
|
Key: strings.Split(strings.TrimSpace(string(keyPEM)), "\n"),
|
||||||
|
}
|
||||||
|
content, err := json.MarshalIndent(jCert, "", " ")
|
||||||
|
common.Must(err)
|
||||||
|
os.Stdout.Write(content)
|
||||||
|
os.Stdout.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFile(content []byte, name string) error {
|
||||||
|
f, err := os.Create(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
return common.Error2(f.Write(content))
|
||||||
|
}
|
||||||
|
|
||||||
|
func printFile(certificate *cert.Certificate, name string) error {
|
||||||
|
certPEM, keyPEM := certificate.ToPEM()
|
||||||
|
return task.Run(context.Background(), func() error {
|
||||||
|
return writeFile(certPEM, name+"_cert.pem")
|
||||||
|
}, func() error {
|
||||||
|
return writeFile(keyPEM, name+"_key.pem")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type stringList []string
|
||||||
|
|
||||||
|
func (l *stringList) String() string {
|
||||||
|
return "String list"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *stringList) Set(v string) error {
|
||||||
|
if v == "" {
|
||||||
|
base.Fatalf("empty value")
|
||||||
|
}
|
||||||
|
*l = append(*l, v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonCert struct {
|
||||||
|
Certificate []string `json:"certificate"`
|
||||||
|
Key []string `json:"key"`
|
||||||
|
}
|
@ -1,63 +1,55 @@
|
|||||||
package control
|
package tlscmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"v2ray.com/core/common"
|
"v2ray.com/core/commands/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TLSPingCommand struct{}
|
// CmdPing is the tls ping command
|
||||||
|
var CmdPing = &base.Command{
|
||||||
|
UsageLine: "{{.Exec}} tls ping [-ip <ip>] <domain>",
|
||||||
|
Short: "Ping the domain with TLS handshake",
|
||||||
|
Long: `
|
||||||
|
Ping the domain with TLS handshake.
|
||||||
|
|
||||||
func (c *TLSPingCommand) Name() string {
|
Arguments:
|
||||||
return "tlsping"
|
|
||||||
|
-ip
|
||||||
|
The IP address of the domain.
|
||||||
|
`,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TLSPingCommand) Description() Description {
|
func init() {
|
||||||
return Description{
|
CmdPing.Run = executePing // break init loop
|
||||||
Short: "Ping the domain with TLS handshake",
|
|
||||||
Usage: []string{"v2ctl tlsping <domain> --ip <ip>"},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func printCertificates(certs []*x509.Certificate) {
|
var (
|
||||||
for _, cert := range certs {
|
pingIPStr = CmdPing.Flag.String("ip", "", "")
|
||||||
if len(cert.DNSNames) == 0 {
|
)
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Println("Allowed domains: ", cert.DNSNames)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TLSPingCommand) Execute(args []string) error {
|
func executePing(cmd *base.Command, args []string) {
|
||||||
fs := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
if CmdPing.Flag.NArg() < 1 {
|
||||||
ipStr := fs.String("ip", "", "IP address of the domain")
|
base.Fatalf("domain not specified")
|
||||||
|
|
||||||
if err := fs.Parse(args); err != nil {
|
|
||||||
return newError("flag parsing").Base(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if fs.NArg() < 1 {
|
domain := CmdPing.Flag.Arg(0)
|
||||||
return newError("domain not specified")
|
|
||||||
}
|
|
||||||
|
|
||||||
domain := fs.Arg(0)
|
|
||||||
fmt.Println("Tls ping: ", domain)
|
fmt.Println("Tls ping: ", domain)
|
||||||
|
|
||||||
var ip net.IP
|
var ip net.IP
|
||||||
if len(*ipStr) > 0 {
|
if len(*pingIPStr) > 0 {
|
||||||
v := net.ParseIP(*ipStr)
|
v := net.ParseIP(*pingIPStr)
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return newError("invalid IP: ", *ipStr)
|
base.Fatalf("invalid IP: %s", *pingIPStr)
|
||||||
}
|
}
|
||||||
ip = v
|
ip = v
|
||||||
} else {
|
} else {
|
||||||
v, err := net.ResolveIPAddr("ip", domain)
|
v, err := net.ResolveIPAddr("ip", domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newError("resolve IP").Base(err)
|
base.Fatalf("Failed to resolve IP: %s", err)
|
||||||
}
|
}
|
||||||
ip = v.IP
|
ip = v.IP
|
||||||
}
|
}
|
||||||
@ -68,7 +60,7 @@ func (c *TLSPingCommand) Execute(args []string) error {
|
|||||||
{
|
{
|
||||||
tcpConn, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: ip, Port: 443})
|
tcpConn, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: ip, Port: 443})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newError("dial tcp").Base(err)
|
base.Fatalf("Failed to dial tcp: %s", err)
|
||||||
}
|
}
|
||||||
tlsConn := tls.Client(tcpConn, &tls.Config{
|
tlsConn := tls.Client(tcpConn, &tls.Config{
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
@ -91,7 +83,7 @@ func (c *TLSPingCommand) Execute(args []string) error {
|
|||||||
{
|
{
|
||||||
tcpConn, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: ip, Port: 443})
|
tcpConn, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: ip, Port: 443})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newError("dial tcp").Base(err)
|
base.Fatalf("Failed to dial tcp: %s", err)
|
||||||
}
|
}
|
||||||
tlsConn := tls.Client(tcpConn, &tls.Config{
|
tlsConn := tls.Client(tcpConn, &tls.Config{
|
||||||
ServerName: domain,
|
ServerName: domain,
|
||||||
@ -110,10 +102,13 @@ func (c *TLSPingCommand) Execute(args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Tls ping finished")
|
fmt.Println("Tls ping finished")
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func printCertificates(certs []*x509.Certificate) {
|
||||||
common.Must(RegisterCommand(&TLSPingCommand{}))
|
for _, cert := range certs {
|
||||||
|
if len(cert.DNSNames) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Println("Allowed domains: ", cert.DNSNames)
|
||||||
|
}
|
||||||
}
|
}
|
21
commands/all/uuid.go
Normal file
21
commands/all/uuid.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package all
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"v2ray.com/core/commands/base"
|
||||||
|
"v2ray.com/core/common/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdUUID = &base.Command{
|
||||||
|
UsageLine: "{{.Exec}} uuid",
|
||||||
|
Short: "Generate new UUIDs",
|
||||||
|
Long: `Generate new UUIDs.
|
||||||
|
`,
|
||||||
|
Run: executeUUID,
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeUUID(cmd *base.Command, args []string) {
|
||||||
|
u := uuid.New()
|
||||||
|
fmt.Println(u.String())
|
||||||
|
}
|
53
commands/all/verify.go
Normal file
53
commands/all/verify.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package all
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/v2fly/VSign/signerVerify"
|
||||||
|
"v2ray.com/core/commands/base"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdVerify = &base.Command{
|
||||||
|
UsageLine: "{{.Exec}} verify [--sig=sig-file] file",
|
||||||
|
Short: "Verify if a binary is officially signed",
|
||||||
|
Long: `
|
||||||
|
Verify if a binary is officially signed.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
-sig
|
||||||
|
The path to the signature file
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cmdVerify.Run = executeVerify // break init loop
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
verifySigFile = cmdVerify.Flag.String("sig", "", "Path to the signature file")
|
||||||
|
)
|
||||||
|
|
||||||
|
func executeVerify(cmd *base.Command, args []string) {
|
||||||
|
target := cmdVerify.Flag.Arg(0)
|
||||||
|
if target == "" {
|
||||||
|
base.Fatalf("empty file path.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if *verifySigFile == "" {
|
||||||
|
base.Fatalf("empty signature path.")
|
||||||
|
}
|
||||||
|
|
||||||
|
sigReader, err := os.Open(os.ExpandEnv(*verifySigFile))
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to open file %s: %s ", *verifySigFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
files := cmdVerify.Flag.Args()
|
||||||
|
|
||||||
|
err = signerVerify.OutputAndJudge(signerVerify.CheckSignaturesV2Fly(sigReader, files))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("file is not officially signed by V2Ray: %s", err)
|
||||||
|
}
|
||||||
|
}
|
122
commands/base/command.go
Normal file
122
commands/base/command.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
// Copyright 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package base defines shared basic pieces of the commands,
|
||||||
|
// in particular logging and the Command structure.
|
||||||
|
package base
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Command is an implementation of a v2ray command
|
||||||
|
// like v2ray run or v2ray version.
|
||||||
|
type Command struct {
|
||||||
|
// Run runs the command.
|
||||||
|
// The args are the arguments after the command name.
|
||||||
|
Run func(cmd *Command, args []string)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
UsageLine string
|
||||||
|
|
||||||
|
// Short is the short description shown in the 'go help' output.
|
||||||
|
Short string
|
||||||
|
|
||||||
|
// Long is the long message shown in the 'go help <this-command>' output.
|
||||||
|
Long string
|
||||||
|
|
||||||
|
// Flag is a set of flags specific to this command.
|
||||||
|
Flag flag.FlagSet
|
||||||
|
|
||||||
|
// CustomFlags indicates that the command will do its own
|
||||||
|
// flag parsing.
|
||||||
|
CustomFlags bool
|
||||||
|
|
||||||
|
// Commands lists the available commands and help topics.
|
||||||
|
// The order here is the order in which they are printed by 'go help'.
|
||||||
|
// Note that subcommands are in general best avoided.
|
||||||
|
Commands []*Command
|
||||||
|
}
|
||||||
|
|
||||||
|
// LongName returns the command's long name: all the words in the usage line between "go" and a flag or argument,
|
||||||
|
func (c *Command) LongName() string {
|
||||||
|
name := c.UsageLine
|
||||||
|
if i := strings.Index(name, " ["); i >= 0 {
|
||||||
|
name = name[:i]
|
||||||
|
}
|
||||||
|
if name == CommandEnv.Exec {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.TrimPrefix(name, CommandEnv.Exec+" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the command's short name: the last word in the usage line before a flag or argument.
|
||||||
|
func (c *Command) Name() string {
|
||||||
|
name := c.LongName()
|
||||||
|
if i := strings.LastIndex(name, " "); i >= 0 {
|
||||||
|
name = name[i+1:]
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage prints usage of the Command
|
||||||
|
func (c *Command) Usage() {
|
||||||
|
fmt.Fprintf(os.Stderr, "usage: %s\n", c.UsageLine)
|
||||||
|
fmt.Fprintf(os.Stderr, "Run '%s help %s' for details.\n", CommandEnv.Exec, c.LongName())
|
||||||
|
SetExitStatus(2)
|
||||||
|
Exit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runnable reports whether the command can be run; otherwise
|
||||||
|
// it is a documentation pseudo-command such as importpath.
|
||||||
|
func (c *Command) Runnable() bool {
|
||||||
|
return c.Run != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit exits with code set with SetExitStatus()
|
||||||
|
func Exit() {
|
||||||
|
os.Exit(exitStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalf logs error and exit with code 1
|
||||||
|
func Fatalf(format string, args ...interface{}) {
|
||||||
|
Errorf(format, args...)
|
||||||
|
Exit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf logs error and set exit status to 1, but not exit
|
||||||
|
func Errorf(format string, args ...interface{}) {
|
||||||
|
fmt.Fprintf(os.Stderr, format, args...)
|
||||||
|
fmt.Fprintln(os.Stderr)
|
||||||
|
SetExitStatus(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitIfErrors exits if current status is not zero
|
||||||
|
func ExitIfErrors() {
|
||||||
|
if exitStatus != 0 {
|
||||||
|
Exit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var exitStatus = 0
|
||||||
|
var exitMu sync.Mutex
|
||||||
|
|
||||||
|
// SetExitStatus set exit status code
|
||||||
|
func SetExitStatus(n int) {
|
||||||
|
exitMu.Lock()
|
||||||
|
if exitStatus < n {
|
||||||
|
exitStatus = n
|
||||||
|
}
|
||||||
|
exitMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExitStatus get exit status code
|
||||||
|
func GetExitStatus() int {
|
||||||
|
return exitStatus
|
||||||
|
}
|
22
commands/base/env.go
Normal file
22
commands/base/env.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package base
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CommandEnvHolder is a struct holds the environment info of commands
|
||||||
|
type CommandEnvHolder struct {
|
||||||
|
Exec string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommandEnv holds the environment info of commands
|
||||||
|
var CommandEnv CommandEnvHolder
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
exec, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
CommandEnv.Exec = path.Base(exec)
|
||||||
|
}
|
88
commands/base/execute.go
Normal file
88
commands/base/execute.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package base
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// copied from "github.com/golang/go/main.go"
|
||||||
|
|
||||||
|
// Execute excute the commands
|
||||||
|
func Execute() {
|
||||||
|
buildCommandsText(RootCommand)
|
||||||
|
flag.Parse()
|
||||||
|
args := flag.Args()
|
||||||
|
if len(args) < 1 {
|
||||||
|
PrintUsage(os.Stderr, RootCommand)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cmdName := args[0] // for error messages
|
||||||
|
if args[0] == "help" {
|
||||||
|
Help(os.Stdout, args[1:])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
BigCmdLoop:
|
||||||
|
for bigCmd := RootCommand; ; {
|
||||||
|
for _, cmd := range bigCmd.Commands {
|
||||||
|
if cmd.Name() != args[0] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(cmd.Commands) > 0 {
|
||||||
|
// test sub commands
|
||||||
|
bigCmd = cmd
|
||||||
|
args = args[1:]
|
||||||
|
if len(args) == 0 {
|
||||||
|
PrintUsage(os.Stderr, bigCmd)
|
||||||
|
SetExitStatus(2)
|
||||||
|
Exit()
|
||||||
|
}
|
||||||
|
if args[0] == "help" {
|
||||||
|
// Accept 'go mod help' and 'go mod help foo' for 'go help mod' and 'go help mod foo'.
|
||||||
|
Help(os.Stdout, append(strings.Split(cmdName, " "), args[1:]...))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cmdName += " " + args[0]
|
||||||
|
continue BigCmdLoop
|
||||||
|
}
|
||||||
|
if !cmd.Runnable() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cmd.Flag.Usage = func() { cmd.Usage() }
|
||||||
|
if cmd.CustomFlags {
|
||||||
|
args = args[1:]
|
||||||
|
} else {
|
||||||
|
cmd.Flag.Parse(args[1:])
|
||||||
|
args = cmd.Flag.Args()
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Run(cmd, args)
|
||||||
|
Exit()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helpArg := ""
|
||||||
|
if i := strings.LastIndex(cmdName, " "); i >= 0 {
|
||||||
|
helpArg = " " + cmdName[:i]
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "%s %s: unknown command\nRun '%s help%s' for usage.\n", CommandEnv.Exec, cmdName, CommandEnv.Exec, helpArg)
|
||||||
|
SetExitStatus(2)
|
||||||
|
Exit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortCommands sorts the first level sub commands
|
||||||
|
func SortCommands() {
|
||||||
|
sort.Slice(RootCommand.Commands, func(i, j int) bool {
|
||||||
|
return SortLessFunc(RootCommand.Commands[i], RootCommand.Commands[j])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortLessFunc used for sort commands list, can be override from outside
|
||||||
|
var SortLessFunc = func(i, j *Command) bool {
|
||||||
|
return i.Name() < j.Name()
|
||||||
|
}
|
158
commands/base/help.go
Normal file
158
commands/base/help.go
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
// Copyright 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package base
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Help implements the 'help' command.
|
||||||
|
func Help(w io.Writer, args []string) {
|
||||||
|
cmd := RootCommand
|
||||||
|
Args:
|
||||||
|
for i, arg := range args {
|
||||||
|
for _, sub := range cmd.Commands {
|
||||||
|
if sub.Name() == arg {
|
||||||
|
cmd = sub
|
||||||
|
continue Args
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// helpSuccess is the help command using as many args as possible that would succeed.
|
||||||
|
helpSuccess := CommandEnv.Exec + " help"
|
||||||
|
if i > 0 {
|
||||||
|
helpSuccess += " " + strings.Join(args[:i], " ")
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "%s help %s: unknown help topic. Run '%s'.\n", CommandEnv.Exec, strings.Join(args, " "), helpSuccess)
|
||||||
|
SetExitStatus(2) // failed at 'v2ray help cmd'
|
||||||
|
Exit()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cmd.Commands) > 0 {
|
||||||
|
PrintUsage(os.Stdout, cmd)
|
||||||
|
} else {
|
||||||
|
tmpl(os.Stdout, helpTemplate, makeTmplData(cmd))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var usageTemplate = `{{.Long | trim}}
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
{{.UsageLine}} <command> [arguments]
|
||||||
|
|
||||||
|
The commands are:
|
||||||
|
{{range .Commands}}{{if and (ne .Short "") (or (.Runnable) .Commands)}}
|
||||||
|
{{.Name | printf "%-12s"}} {{.Short}}{{end}}{{end}}
|
||||||
|
|
||||||
|
Use "{{.Exec}} help{{with .LongName}} {{.}}{{end}} <command>" for more information about a command.
|
||||||
|
`
|
||||||
|
|
||||||
|
// APPEND FOLLOWING TO 'usageTemplate' IF YOU WANT DOC,
|
||||||
|
// A DOC TOPIC IS JUST A COMMAND NOT RUNNABLE:
|
||||||
|
//
|
||||||
|
// {{if eq (.UsageLine) (.Exec)}}
|
||||||
|
// Additional help topics:
|
||||||
|
// {{range .Commands}}{{if and (not .Runnable) (not .Commands)}}
|
||||||
|
// {{.Name | printf "%-15s"}} {{.Short}}{{end}}{{end}}
|
||||||
|
//
|
||||||
|
// Use "{{.Exec}} help{{with .LongName}} {{.}}{{end}} <topic>" for more information about that topic.
|
||||||
|
// {{end}}
|
||||||
|
|
||||||
|
var helpTemplate = `{{if .Runnable}}usage: {{.UsageLine}}
|
||||||
|
|
||||||
|
{{end}}{{.Long | trim}}
|
||||||
|
`
|
||||||
|
|
||||||
|
// An errWriter wraps a writer, recording whether a write error occurred.
|
||||||
|
type errWriter struct {
|
||||||
|
w io.Writer
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *errWriter) Write(b []byte) (int, error) {
|
||||||
|
n, err := w.w.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
w.err = err
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// tmpl executes the given template text on data, writing the result to w.
|
||||||
|
func tmpl(w io.Writer, text string, data interface{}) {
|
||||||
|
t := template.New("top")
|
||||||
|
t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize})
|
||||||
|
template.Must(t.Parse(text))
|
||||||
|
ew := &errWriter{w: w}
|
||||||
|
err := t.Execute(ew, data)
|
||||||
|
if ew.err != nil {
|
||||||
|
// I/O error writing. Ignore write on closed pipe.
|
||||||
|
if strings.Contains(ew.err.Error(), "pipe") {
|
||||||
|
SetExitStatus(1)
|
||||||
|
Exit()
|
||||||
|
}
|
||||||
|
Fatalf("writing output: %v", ew.err)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func capitalize(s string) string {
|
||||||
|
if s == "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
r, n := utf8.DecodeRuneInString(s)
|
||||||
|
return string(unicode.ToTitle(r)) + s[n:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintUsage prints usage of cmd to w
|
||||||
|
func PrintUsage(w io.Writer, cmd *Command) {
|
||||||
|
bw := bufio.NewWriter(w)
|
||||||
|
tmpl(bw, usageTemplate, makeTmplData(cmd))
|
||||||
|
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
|
||||||
|
func buildCommandText(cmd *Command) {
|
||||||
|
cmd.UsageLine = buildText(cmd.UsageLine, makeTmplData(cmd))
|
||||||
|
cmd.Short = buildText(cmd.Short, makeTmplData(cmd))
|
||||||
|
cmd.Long = buildText(cmd.Long, makeTmplData(cmd))
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildText(text string, data interface{}) string {
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
text = strings.ReplaceAll(text, "\t", " ")
|
||||||
|
tmpl(buf, text, data)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type tmplData struct {
|
||||||
|
*Command
|
||||||
|
*CommandEnvHolder
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeTmplData(cmd *Command) tmplData {
|
||||||
|
return tmplData{
|
||||||
|
Command: cmd,
|
||||||
|
CommandEnvHolder: &CommandEnv,
|
||||||
|
}
|
||||||
|
}
|
16
commands/base/root.go
Normal file
16
commands/base/root.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package base
|
||||||
|
|
||||||
|
// RootCommand is the root command of all commands
|
||||||
|
var RootCommand *Command
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RootCommand = &Command{
|
||||||
|
UsageLine: CommandEnv.Exec,
|
||||||
|
Long: "The root command",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterCommand register a command to RootCommand
|
||||||
|
func RegisterCommand(cmd *Command) {
|
||||||
|
RootCommand.Commands = append(RootCommand.Commands, cmd)
|
||||||
|
}
|
23
config.go
23
config.go
@ -60,15 +60,24 @@ func getExtension(filename string) string {
|
|||||||
// * []string slice of multiple filename/url(s) to open to read
|
// * []string slice of multiple filename/url(s) to open to read
|
||||||
// * io.Reader that reads a config content (the original way)
|
// * io.Reader that reads a config content (the original way)
|
||||||
func LoadConfig(formatName string, filename string, input interface{}) (*Config, error) {
|
func LoadConfig(formatName string, filename string, input interface{}) (*Config, error) {
|
||||||
ext := getExtension(filename)
|
if formatName != "" {
|
||||||
if len(ext) > 0 {
|
// if clearly specified, we can safely assume that user knows what they are
|
||||||
if f, found := configLoaderByExt[ext]; found {
|
if f, found := configLoaderByName[formatName]; found {
|
||||||
|
return f.Loader(input)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// no explicitly specified loader, extenstion detect first
|
||||||
|
ext := getExtension(filename)
|
||||||
|
if len(ext) > 0 {
|
||||||
|
if f, found := configLoaderByExt[ext]; found {
|
||||||
|
return f.Loader(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// try default loader
|
||||||
|
formatName = "json"
|
||||||
|
if f, found := configLoaderByName[formatName]; found {
|
||||||
return f.Loader(input)
|
return f.Loader(input)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if f, found := configLoaderByName[formatName]; found {
|
|
||||||
return f.Loader(input)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, newError("Unable to load config in ", formatName).AtWarning()
|
return nil, newError("Unable to load config in ", formatName).AtWarning()
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
package command
|
|
||||||
|
|
||||||
//go:generate go run v2ray.com/core/common/errors/errorgen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"v2ray.com/core/common"
|
|
||||||
"v2ray.com/core/infra/conf/serial"
|
|
||||||
"v2ray.com/core/infra/control"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConfigCommand struct{}
|
|
||||||
|
|
||||||
func (c *ConfigCommand) Name() string {
|
|
||||||
return "config"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConfigCommand) Description() control.Description {
|
|
||||||
return control.Description{
|
|
||||||
Short: "Convert config among different formats.",
|
|
||||||
Usage: []string{
|
|
||||||
"v2ctl config",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConfigCommand) Execute(args []string) error {
|
|
||||||
pbConfig, err := serial.LoadJSONConfig(os.Stdin)
|
|
||||||
if err != nil {
|
|
||||||
return newError("failed to parse json config").Base(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 init() {
|
|
||||||
common.Must(control.RegisterCommand(&ConfigCommand{}))
|
|
||||||
}
|
|
@ -1,139 +0,0 @@
|
|||||||
package control
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"v2ray.com/core/common"
|
|
||||||
"v2ray.com/core/common/protocol/tls/cert"
|
|
||||||
"v2ray.com/core/common/task"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stringList []string
|
|
||||||
|
|
||||||
func (l *stringList) String() string {
|
|
||||||
return "String list"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *stringList) Set(v string) error {
|
|
||||||
if v == "" {
|
|
||||||
return newError("empty value")
|
|
||||||
}
|
|
||||||
*l = append(*l, v)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type jsonCert struct {
|
|
||||||
Certificate []string `json:"certificate"`
|
|
||||||
Key []string `json:"key"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CertificateCommand struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CertificateCommand) Name() string {
|
|
||||||
return "cert"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CertificateCommand) Description() Description {
|
|
||||||
return Description{
|
|
||||||
Short: "Generate TLS certificates.",
|
|
||||||
Usage: []string{
|
|
||||||
"v2ctl cert [--ca] [--domain=v2ray.com] [--expire=240h]",
|
|
||||||
"Generate new TLS certificate",
|
|
||||||
"--ca The new certificate is a CA certificate",
|
|
||||||
"--domain Common name for the certificate",
|
|
||||||
"--expire Time until certificate expires. 240h = 10 days.",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CertificateCommand) printJSON(certificate *cert.Certificate) {
|
|
||||||
certPEM, keyPEM := certificate.ToPEM()
|
|
||||||
jCert := &jsonCert{
|
|
||||||
Certificate: strings.Split(strings.TrimSpace(string(certPEM)), "\n"),
|
|
||||||
Key: strings.Split(strings.TrimSpace(string(keyPEM)), "\n"),
|
|
||||||
}
|
|
||||||
content, err := json.MarshalIndent(jCert, "", " ")
|
|
||||||
common.Must(err)
|
|
||||||
os.Stdout.Write(content)
|
|
||||||
os.Stdout.WriteString("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CertificateCommand) writeFile(content []byte, name string) error {
|
|
||||||
f, err := os.Create(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
return common.Error2(f.Write(content))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CertificateCommand) printFile(certificate *cert.Certificate, name string) error {
|
|
||||||
certPEM, keyPEM := certificate.ToPEM()
|
|
||||||
return task.Run(context.Background(), func() error {
|
|
||||||
return c.writeFile(certPEM, name+"_cert.pem")
|
|
||||||
}, func() error {
|
|
||||||
return c.writeFile(keyPEM, name+"_key.pem")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CertificateCommand) Execute(args []string) error {
|
|
||||||
fs := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
|
||||||
|
|
||||||
var domainNames stringList
|
|
||||||
fs.Var(&domainNames, "domain", "Domain name for the certificate")
|
|
||||||
|
|
||||||
commonName := fs.String("name", "V2Ray Inc", "The common name of this certificate")
|
|
||||||
organization := fs.String("org", "V2Ray Inc", "Organization of the certificate")
|
|
||||||
|
|
||||||
isCA := fs.Bool("ca", false, "Whether this certificate is a CA")
|
|
||||||
jsonOutput := fs.Bool("json", true, "Print certificate in JSON format")
|
|
||||||
fileOutput := fs.String("file", "", "Save certificate in file.")
|
|
||||||
|
|
||||||
expire := fs.Duration("expire", time.Hour*24*90 /* 90 days */, "Time until the certificate expires. Default value 3 months.")
|
|
||||||
|
|
||||||
if err := fs.Parse(args); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var opts []cert.Option
|
|
||||||
if *isCA {
|
|
||||||
opts = append(opts, cert.Authority(*isCA))
|
|
||||||
opts = append(opts, cert.KeyUsage(x509.KeyUsageCertSign|x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature))
|
|
||||||
}
|
|
||||||
|
|
||||||
opts = append(opts, cert.NotAfter(time.Now().Add(*expire)))
|
|
||||||
opts = append(opts, cert.CommonName(*commonName))
|
|
||||||
if len(domainNames) > 0 {
|
|
||||||
opts = append(opts, cert.DNSNames(domainNames...))
|
|
||||||
}
|
|
||||||
opts = append(opts, cert.Organization(*organization))
|
|
||||||
|
|
||||||
cert, err := cert.Generate(nil, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return newError("failed to generate TLS certificate").Base(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *jsonOutput {
|
|
||||||
c.printJSON(cert)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(*fileOutput) > 0 {
|
|
||||||
if err := c.printFile(cert, *fileOutput); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
common.Must(RegisterCommand(&CertificateCommand{}))
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
package control
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Description struct {
|
|
||||||
Short string
|
|
||||||
Usage []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Command interface {
|
|
||||||
Name() string
|
|
||||||
Description() Description
|
|
||||||
Execute(args []string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
commandRegistry = make(map[string]Command)
|
|
||||||
ctllog = log.New(os.Stderr, "v2ctl> ", 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
func RegisterCommand(cmd Command) error {
|
|
||||||
entry := strings.ToLower(cmd.Name())
|
|
||||||
if entry == "" {
|
|
||||||
return newError("empty command name")
|
|
||||||
}
|
|
||||||
commandRegistry[entry] = cmd
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetCommand(name string) Command {
|
|
||||||
cmd, found := commandRegistry[name]
|
|
||||||
if !found {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
type hiddenCommand interface {
|
|
||||||
Hidden() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func PrintUsage() {
|
|
||||||
for name, cmd := range commandRegistry {
|
|
||||||
if _, ok := cmd.(hiddenCommand); ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Println(" ", name, "\t\t\t", cmd.Description())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ConfigCommand is the json to pb convert struct
|
|
||||||
type ConfigCommand struct{}
|
|
||||||
|
|
||||||
// Name for cmd usage
|
|
||||||
func (c *ConfigCommand) Name() string {
|
|
||||||
return "config"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Description for help usage
|
|
||||||
func (c *ConfigCommand) Description() Description {
|
|
||||||
return Description{
|
|
||||||
Short: "merge multiple json config",
|
|
||||||
Usage: []string{"v2ctl config config.json c1.json c2.json <url>.json"},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute real work here.
|
|
||||||
func (c *ConfigCommand) Execute(args []string) error {
|
|
||||||
if len(args) < 1 {
|
|
||||||
return newError("empty config list")
|
|
||||||
}
|
|
||||||
|
|
||||||
conf := &conf.Config{}
|
|
||||||
for _, arg := range args {
|
|
||||||
ctllog.Println("Read config: ", arg)
|
|
||||||
r, err := c.LoadArg(arg)
|
|
||||||
common.Must(err)
|
|
||||||
c, err := serial.DecodeJSONConfig(r)
|
|
||||||
if err != nil {
|
|
||||||
ctllog.Fatalln(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
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadArg loads one arg, maybe an remote url, or local file path
|
|
||||||
func (c *ConfigCommand) 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
common.Must(RegisterCommand(&ConfigCommand{}))
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
package control
|
|
||||||
|
|
||||||
//go:generate go run v2ray.com/core/common/errors/errorgen
|
|
@ -1,78 +0,0 @@
|
|||||||
package control
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"v2ray.com/core/common"
|
|
||||||
"v2ray.com/core/common/buf"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FetchCommand struct{}
|
|
||||||
|
|
||||||
func (c *FetchCommand) Name() string {
|
|
||||||
return "fetch"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *FetchCommand) Description() Description {
|
|
||||||
return Description{
|
|
||||||
Short: "Fetch resources",
|
|
||||||
Usage: []string{"v2ctl fetch <url>"},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *FetchCommand) Execute(args []string) error {
|
|
||||||
if len(args) < 1 {
|
|
||||||
return newError("empty url")
|
|
||||||
}
|
|
||||||
content, err := FetchHTTPContent(args[0])
|
|
||||||
if err != nil {
|
|
||||||
return newError("failed to read HTTP response").Base(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Stdout.Write(content)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FetchHTTPContent dials https for remote content
|
|
||||||
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 nil, newError("failed to dial to ", target).Base(err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
return nil, newError("unexpected HTTP status code: ", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
content, err := buf.ReadAllToBytes(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, newError("failed to read HTTP response").Base(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return content, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
common.Must(RegisterCommand(&FetchCommand{}))
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
package control
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"compress/gzip"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"v2ray.com/core/common"
|
|
||||||
"v2ray.com/core/common/platform"
|
|
||||||
)
|
|
||||||
|
|
||||||
const content = "H4sIAAAAAAAC/4SVMaskNwzH+/kUW6izcSthMGrcqLhVk0rdQS5cSMg7Xu4S0vizB8meZd57M3ta2GHX/ukvyZZmY2ZKDMzCzJyY5yOlxKII1omsf+qkBiiC6WhbYsbkjDAfySQsJqD3jtrD0EBM3sBHzG3kUsrglIQREXonpd47kYIi4AHmgI9Wcq2jlJITC6JZJ+v3ECYzBMAHyYm392yuY4zWsjACmHZSh6l3A0JETzGlWZqDsnArpTg62mhJONhOdO90p97V1BAnteoaOcuummtrrtuERQwUiJwP8a4KGKcyxdOCw1spOY+WHueFqmakAIgUSSuhwKNgobxKXSLbtg6r5cFmBiAeF6yCkYycmv+BiCIiW8ScHa3DgxAuZQbRhFNrLTFo96RBmx9jKWWG5nBsjyJzuIkftUblonppZU5t5LzwIks5L1a4lijagQxLokbIYwxfytNDC+XQqrWW9fzAunhqh5/Tg8PuaMw0d/Tcw3iDO81bHfWM/AnutMh2xqSUntMzd3wHDy9iHMQz8bmUZYvqedTJ5GgOnrNt7FIbSlwXE3wDI19n/KA38MsLaP4l89b5F8AV3ESOMIEhIBgezHBc0H6xV9KbaXwMvPcNvIHcC0C7UPZQx4JVTb35/AneSQq+bAYXsBmY7TCRupF2NTdVm/+ch22xa0pvRERKqt1oxj9DUbXzU84Gvj5hc5a81SlAUwMwgEs4T9+7sg9lb9h+908MWiKV8xtWciVTmnB3tivRjNerfXdxpfEBbq2NUvLMM5R9NLuyQg8nXT0PIh1xPd/wrcV49oJ6zbZaPlj2V87IY9T3F2XCOcW2MbZyZd49H+9m81E1N9SxlU+ff/1y+/f3719vf7788+Ugv/ffbMIH7ZNj0dsT4WMHHwLPu/Rp2O75uh99AK+N2xn7ZHq1OK6gczkN+9ngdOl1Qvki5xwSR8vFX6D+9vXA97B/+fr5rz9u/738uP328urP19vfP759e3n9Xs6jamvqlfJ/AAAA//+YAMZjDgkAAA=="
|
|
||||||
|
|
||||||
type LoveCommand struct{}
|
|
||||||
|
|
||||||
func (*LoveCommand) Name() string {
|
|
||||||
return "lovevictoria"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*LoveCommand) Hidden() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *LoveCommand) Description() Description {
|
|
||||||
return Description{
|
|
||||||
Short: "",
|
|
||||||
Usage: []string{""},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*LoveCommand) Execute([]string) error {
|
|
||||||
c, err := base64.StdEncoding.DecodeString(content)
|
|
||||||
common.Must(err)
|
|
||||||
reader, err := gzip.NewReader(bytes.NewBuffer(c))
|
|
||||||
common.Must(err)
|
|
||||||
b := make([]byte, 4096)
|
|
||||||
nBytes, _ := reader.Read(b)
|
|
||||||
|
|
||||||
bb := bytes.NewBuffer(b[:nBytes])
|
|
||||||
scanner := bufio.NewScanner(bb)
|
|
||||||
for scanner.Scan() {
|
|
||||||
s := scanner.Text()
|
|
||||||
fmt.Print(s + platform.LineSeparator())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
common.Must(RegisterCommand(&LoveCommand{}))
|
|
||||||
}
|
|
@ -1,51 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
_ "v2ray.com/core/commands/all"
|
||||||
"fmt"
|
"v2ray.com/core/commands/base"
|
||||||
"os"
|
|
||||||
|
|
||||||
commlog "v2ray.com/core/common/log"
|
|
||||||
// _ "v2ray.com/core/infra/conf/command"
|
|
||||||
"v2ray.com/core/infra/control"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func getCommandName() string {
|
|
||||||
if len(os.Args) > 1 {
|
|
||||||
return os.Args[1]
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// let the v2ctl prints log at stderr
|
base.RootCommand.Long = "A tool set for V2Ray."
|
||||||
commlog.RegisterHandler(commlog.NewLogger(commlog.CreateStderrLogWriter()))
|
base.Execute()
|
||||||
name := getCommandName()
|
|
||||||
cmd := control.GetCommand(name)
|
|
||||||
if cmd == nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "Unknown command:", name)
|
|
||||||
fmt.Fprintln(os.Stderr)
|
|
||||||
|
|
||||||
fmt.Println("v2ctl <command>")
|
|
||||||
fmt.Println("Available commands:")
|
|
||||||
control.PrintUsage()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cmd.Execute(os.Args[2:]); err != nil {
|
|
||||||
hasError := false
|
|
||||||
if err != flag.ErrHelp {
|
|
||||||
fmt.Fprintln(os.Stderr, err.Error())
|
|
||||||
fmt.Fprintln(os.Stderr)
|
|
||||||
hasError = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, line := range cmd.Description().Usage {
|
|
||||||
fmt.Println(line)
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasError {
|
|
||||||
os.Exit(-1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
package control
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"v2ray.com/core/common"
|
|
||||||
"v2ray.com/core/common/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UUIDCommand struct{}
|
|
||||||
|
|
||||||
func (c *UUIDCommand) Name() string {
|
|
||||||
return "uuid"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *UUIDCommand) Description() Description {
|
|
||||||
return Description{
|
|
||||||
Short: "Generate new UUIDs",
|
|
||||||
Usage: []string{"v2ctl uuid"},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *UUIDCommand) Execute([]string) error {
|
|
||||||
u := uuid.New()
|
|
||||||
fmt.Println(u.String())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
common.Must(RegisterCommand(&UUIDCommand{}))
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
package control
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/v2fly/VSign/signerVerify"
|
|
||||||
"v2ray.com/core/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
type VerifyCommand struct{}
|
|
||||||
|
|
||||||
func (c *VerifyCommand) Name() string {
|
|
||||||
return "verify"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *VerifyCommand) Description() Description {
|
|
||||||
return Description{
|
|
||||||
Short: "Verify if a binary is officially signed.",
|
|
||||||
Usage: []string{
|
|
||||||
"v2ctl verify --sig=<sig-file> file...",
|
|
||||||
"Verify the file officially signed by V2Ray.",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *VerifyCommand) Execute(args []string) error {
|
|
||||||
fs := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
|
||||||
|
|
||||||
sigFile := fs.String("sig", "", "Path to the signature file")
|
|
||||||
|
|
||||||
if err := fs.Parse(args); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
target := fs.Arg(0)
|
|
||||||
if target == "" {
|
|
||||||
return newError("empty file path.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if *sigFile == "" {
|
|
||||||
return newError("empty signature path.")
|
|
||||||
}
|
|
||||||
|
|
||||||
sigReader, err := os.Open(os.ExpandEnv(*sigFile))
|
|
||||||
if err != nil {
|
|
||||||
return newError("failed to open file ", *sigFile).Base(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
files := fs.Args()
|
|
||||||
|
|
||||||
err = signerVerify.OutputAndJudge(signerVerify.CheckSignaturesV2Fly(sigReader, files))
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return newError("file is not officially signed by V2Ray").Base(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
common.Must(RegisterCommand(&VerifyCommand{}))
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package command
|
package commands
|
||||||
|
|
||||||
import "v2ray.com/core/common/errors"
|
import "v2ray.com/core/common/errors"
|
||||||
|
|
169
main/commands/run.go
Normal file
169
main/commands/run.go
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"v2ray.com/core"
|
||||||
|
"v2ray.com/core/commands/base"
|
||||||
|
"v2ray.com/core/common/cmdarg"
|
||||||
|
"v2ray.com/core/common/platform"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdRun runs V2Ray with config
|
||||||
|
var CmdRun = &base.Command{
|
||||||
|
CustomFlags: true,
|
||||||
|
UsageLine: "{{.Exec}} run [-c config.json] [-confdir dir]",
|
||||||
|
Short: "Run V2Ray with config",
|
||||||
|
Long: `
|
||||||
|
Run V2Ray with config.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{.Exec}} {{.LongName}} -c config.json
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
-c value
|
||||||
|
Short alias of -config
|
||||||
|
|
||||||
|
-config value
|
||||||
|
Config file for V2Ray. Multiple assign is accepted (only
|
||||||
|
json). Latter ones overrides the former ones.
|
||||||
|
|
||||||
|
-confdir string
|
||||||
|
A dir with multiple json config
|
||||||
|
|
||||||
|
-format string
|
||||||
|
Format of input files. (default "json")
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
CmdRun.Run = executeRun //break init loop
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
configFiles cmdarg.Arg // "Config file for V2Ray.", the option is customed type
|
||||||
|
configDir string
|
||||||
|
configFormat *string
|
||||||
|
)
|
||||||
|
|
||||||
|
func setConfigFlags(cmd *base.Command) {
|
||||||
|
configFormat = cmd.Flag.String("format", "", "")
|
||||||
|
|
||||||
|
cmd.Flag.Var(&configFiles, "config", "")
|
||||||
|
cmd.Flag.Var(&configFiles, "c", "")
|
||||||
|
cmd.Flag.StringVar(&configDir, "confdir", "", "")
|
||||||
|
}
|
||||||
|
func executeRun(cmd *base.Command, args []string) {
|
||||||
|
setConfigFlags(cmd)
|
||||||
|
cmd.Flag.Parse(args)
|
||||||
|
printVersion()
|
||||||
|
server, err := startV2Ray()
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("Failed to start: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := server.Start(); err != nil {
|
||||||
|
base.Fatalf("Failed to start: %s", err)
|
||||||
|
}
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
// Explicitly triggering GC to remove garbage from config loading.
|
||||||
|
runtime.GC()
|
||||||
|
|
||||||
|
{
|
||||||
|
osSignals := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM)
|
||||||
|
<-osSignals
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileExists(file string) bool {
|
||||||
|
info, err := os.Stat(file)
|
||||||
|
return err == nil && !info.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
func dirExists(file string) bool {
|
||||||
|
if file == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
info, err := os.Stat(file)
|
||||||
|
return err == nil && info.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
func readConfDir(dirPath string) cmdarg.Arg {
|
||||||
|
confs, err := ioutil.ReadDir(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
files := make(cmdarg.Arg, 0)
|
||||||
|
for _, f := range confs {
|
||||||
|
if strings.HasSuffix(f.Name(), ".json") {
|
||||||
|
files.Set(path.Join(dirPath, f.Name()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConfigFilePath() cmdarg.Arg {
|
||||||
|
if dirExists(configDir) {
|
||||||
|
log.Println("Using confdir from arg:", configDir)
|
||||||
|
configFiles = append(configFiles, readConfDir(configDir)...)
|
||||||
|
} else if envConfDir := platform.GetConfDirPath(); dirExists(envConfDir) {
|
||||||
|
log.Println("Using confdir from env:", envConfDir)
|
||||||
|
configFiles = append(configFiles, readConfDir(envConfDir)...)
|
||||||
|
}
|
||||||
|
if len(configFiles) > 0 {
|
||||||
|
return configFiles
|
||||||
|
}
|
||||||
|
|
||||||
|
if workingDir, err := os.Getwd(); err == nil {
|
||||||
|
configFile := filepath.Join(workingDir, "config.json")
|
||||||
|
if fileExists(configFile) {
|
||||||
|
log.Println("Using default config: ", configFile)
|
||||||
|
return cmdarg.Arg{configFile}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if configFile := platform.GetConfigurationPath(); fileExists(configFile) {
|
||||||
|
log.Println("Using config from env: ", configFile)
|
||||||
|
return cmdarg.Arg{configFile}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Using config from STDIN")
|
||||||
|
return cmdarg.Arg{"stdin:"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFormatFromAlias() string {
|
||||||
|
switch strings.ToLower(*configFormat) {
|
||||||
|
case "pb":
|
||||||
|
return "protobuf"
|
||||||
|
default:
|
||||||
|
return *configFormat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startV2Ray() (core.Server, error) {
|
||||||
|
configFiles := getConfigFilePath()
|
||||||
|
|
||||||
|
config, err := core.LoadConfig(getFormatFromAlias(), configFiles[0], configFiles)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to read config files: [", configFiles.String(), "]").Base(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
server, err := core.New(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to create server").Base(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return server, nil
|
||||||
|
}
|
76
main/commands/test.go
Normal file
76
main/commands/test.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"v2ray.com/core"
|
||||||
|
"v2ray.com/core/commands/base"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdTest tests config files
|
||||||
|
var CmdTest = &base.Command{
|
||||||
|
CustomFlags: true,
|
||||||
|
UsageLine: "{{.Exec}} test [-format=json] [-c config.json] [-confdir dir]",
|
||||||
|
Short: "Test config files",
|
||||||
|
Long: `
|
||||||
|
Test config files, without launching V2Ray server.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{.Exec}} {{.LongName}} -c config.json
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
-c value
|
||||||
|
Short alias of -config
|
||||||
|
|
||||||
|
-config value
|
||||||
|
Config file for V2Ray. Multiple assign is accepted (only
|
||||||
|
json). Latter ones overrides the former ones.
|
||||||
|
|
||||||
|
-confdir string
|
||||||
|
A dir with multiple json config
|
||||||
|
|
||||||
|
-format string
|
||||||
|
Format of input files. (default "json")
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
CmdTest.Run = executeTest //break init loop
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeTest(cmd *base.Command, args []string) {
|
||||||
|
setConfigFlags(cmd)
|
||||||
|
cmd.Flag.Parse(args)
|
||||||
|
if dirExists(configDir) {
|
||||||
|
log.Println("Using confdir from arg:", configDir)
|
||||||
|
configFiles = append(configFiles, readConfDir(configDir)...)
|
||||||
|
}
|
||||||
|
if len(configFiles) == 0 {
|
||||||
|
cmd.Flag.Usage()
|
||||||
|
base.SetExitStatus(1)
|
||||||
|
base.Exit()
|
||||||
|
}
|
||||||
|
printVersion()
|
||||||
|
_, err := startV2RayTesting()
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("Test failed: %s", err)
|
||||||
|
}
|
||||||
|
fmt.Println("Configuration OK.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func startV2RayTesting() (core.Server, error) {
|
||||||
|
config, err := core.LoadConfig(getFormatFromAlias(), configFiles[0], configFiles)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to read config files: [", configFiles.String(), "]").Base(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
server, err := core.New(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newError("failed to create server").Base(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return server, nil
|
||||||
|
}
|
28
main/commands/version.go
Normal file
28
main/commands/version.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"v2ray.com/core"
|
||||||
|
"v2ray.com/core/commands/base"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdVersion prints V2Ray Versions
|
||||||
|
var CmdVersion = &base.Command{
|
||||||
|
UsageLine: "{{.Exec}} version",
|
||||||
|
Short: "Print V2Ray Versions",
|
||||||
|
Long: `Version prints the build information for V2Ray executables.
|
||||||
|
`,
|
||||||
|
Run: executeVersion,
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeVersion(cmd *base.Command, args []string) {
|
||||||
|
printVersion()
|
||||||
|
}
|
||||||
|
|
||||||
|
func printVersion() {
|
||||||
|
version := core.VersionStatement()
|
||||||
|
for _, s := range version {
|
||||||
|
fmt.Println(s)
|
||||||
|
}
|
||||||
|
}
|
2
main/confloader/external/external.go
vendored
2
main/confloader/external/external.go
vendored
@ -73,7 +73,7 @@ func FetchHTTPContent(target string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ExtConfigLoader(files []string, reader io.Reader) (io.Reader, error) {
|
func ExtConfigLoader(files []string, reader io.Reader) (io.Reader, error) {
|
||||||
buf, err := ctlcmd.Run(append([]string{"config"}, files...), reader)
|
buf, err := ctlcmd.Run(append([]string{"convert"}, files...), reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -64,4 +64,7 @@ import (
|
|||||||
|
|
||||||
// Load config from file or http(s)
|
// Load config from file or http(s)
|
||||||
_ "v2ray.com/core/main/confloader/external"
|
_ "v2ray.com/core/main/confloader/external"
|
||||||
|
|
||||||
|
// commands
|
||||||
|
_ "v2ray.com/core/commands/all"
|
||||||
)
|
)
|
||||||
|
172
main/main.go
172
main/main.go
@ -1,165 +1,29 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
//go:generate go run v2ray.com/core/common/errors/errorgen
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"v2ray.com/core/commands/base"
|
||||||
"fmt"
|
"v2ray.com/core/main/commands"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"v2ray.com/core"
|
|
||||||
"v2ray.com/core/common/cmdarg"
|
|
||||||
"v2ray.com/core/common/platform"
|
|
||||||
_ "v2ray.com/core/main/distro/all"
|
_ "v2ray.com/core/main/distro/all"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func main() {
|
||||||
configFiles cmdarg.Arg // "Config file for V2Ray.", the option is customed type, parse in main
|
base.RootCommand.Long = "A unified platform for anti-censorship."
|
||||||
configDir string
|
base.RegisterCommand(commands.CmdRun)
|
||||||
version = flag.Bool("version", false, "Show current version of V2Ray.")
|
base.RegisterCommand(commands.CmdVersion)
|
||||||
test = flag.Bool("test", false, "Test config file only, without launching V2Ray server.")
|
base.RegisterCommand(commands.CmdTest)
|
||||||
format = flag.String("format", "json", "Format of input file.")
|
base.SortLessFunc = runIsTheFirst
|
||||||
|
base.SortCommands()
|
||||||
/* We have to do this here because Golang's Test will also need to parse flag, before
|
base.Execute()
|
||||||
* main func in this file is run.
|
|
||||||
*/
|
|
||||||
_ = func() error { // nolint: unparam
|
|
||||||
flag.Var(&configFiles, "config", "Config file for V2Ray. Multiple assign is accepted (only json). Latter ones overrides the former ones.")
|
|
||||||
flag.Var(&configFiles, "c", "Short alias of -config")
|
|
||||||
flag.StringVar(&configDir, "confdir", "", "A dir with multiple json config")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}()
|
|
||||||
)
|
|
||||||
|
|
||||||
func fileExists(file string) bool {
|
|
||||||
info, err := os.Stat(file)
|
|
||||||
return err == nil && !info.IsDir()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func dirExists(file string) bool {
|
func runIsTheFirst(i, j *base.Command) bool {
|
||||||
if file == "" {
|
left := i.Name()
|
||||||
|
right := j.Name()
|
||||||
|
if left == "run" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if right == "run" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
info, err := os.Stat(file)
|
return left < right
|
||||||
return err == nil && info.IsDir()
|
|
||||||
}
|
|
||||||
|
|
||||||
func readConfDir(dirPath string) {
|
|
||||||
confs, err := ioutil.ReadDir(dirPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
for _, f := range confs {
|
|
||||||
if strings.HasSuffix(f.Name(), ".json") {
|
|
||||||
configFiles.Set(path.Join(dirPath, f.Name()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getConfigFilePath() cmdarg.Arg {
|
|
||||||
if dirExists(configDir) {
|
|
||||||
log.Println("Using confdir from arg:", configDir)
|
|
||||||
readConfDir(configDir)
|
|
||||||
} else if envConfDir := platform.GetConfDirPath(); dirExists(envConfDir) {
|
|
||||||
log.Println("Using confdir from env:", envConfDir)
|
|
||||||
readConfDir(envConfDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(configFiles) > 0 {
|
|
||||||
return configFiles
|
|
||||||
}
|
|
||||||
|
|
||||||
if workingDir, err := os.Getwd(); err == nil {
|
|
||||||
configFile := filepath.Join(workingDir, "config.json")
|
|
||||||
if fileExists(configFile) {
|
|
||||||
log.Println("Using default config: ", configFile)
|
|
||||||
return cmdarg.Arg{configFile}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if configFile := platform.GetConfigurationPath(); fileExists(configFile) {
|
|
||||||
log.Println("Using config from env: ", configFile)
|
|
||||||
return cmdarg.Arg{configFile}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Using config from STDIN")
|
|
||||||
return cmdarg.Arg{"stdin:"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetConfigFormat() string {
|
|
||||||
switch strings.ToLower(*format) {
|
|
||||||
case "pb", "protobuf":
|
|
||||||
return "protobuf"
|
|
||||||
default:
|
|
||||||
return "json"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func startV2Ray() (core.Server, error) {
|
|
||||||
configFiles := getConfigFilePath()
|
|
||||||
|
|
||||||
config, err := core.LoadConfig(GetConfigFormat(), configFiles[0], configFiles)
|
|
||||||
if err != nil {
|
|
||||||
return nil, newError("failed to read config files: [", configFiles.String(), "]").Base(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
server, err := core.New(config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, newError("failed to create server").Base(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return server, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func printVersion() {
|
|
||||||
version := core.VersionStatement()
|
|
||||||
for _, s := range version {
|
|
||||||
fmt.Println(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
printVersion()
|
|
||||||
|
|
||||||
if *version {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
server, err := startV2Ray()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
// Configuration error. Exit with a special value to prevent systemd from restarting.
|
|
||||||
os.Exit(23)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *test {
|
|
||||||
fmt.Println("Configuration OK.")
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := server.Start(); err != nil {
|
|
||||||
fmt.Println("Failed to start", err)
|
|
||||||
os.Exit(-1)
|
|
||||||
}
|
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
// Explicitly triggering GC to remove garbage from config loading.
|
|
||||||
runtime.GC()
|
|
||||||
|
|
||||||
{
|
|
||||||
osSignals := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM)
|
|
||||||
<-osSignals
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user