mirror of
https://github.com/v2fly/v2ray-core.git
synced 2025-01-02 07:26:24 -05:00
improve commands (#648)
* stat show json refactor > will show {} since scripts should expect it, its the json style of blank value * combine statistics commands to one * code optimize * fix runtime flag * remove json indent * change overridden to override * api bi -json * convert stdin support code optimize * writeRow() code optimize add stats tittle revert back to restartlogger * api log -restart * follow log * codeql * move -json to shared flags * flags optimize * update flag descriptions * change "-v" of "api bo" to duration * change "-expire" of "tls cert" to days * cmds short description optimize * fix multiple log followers * Format loader refactor * "infra/conf/merge" refactor * "LoadConfig" refactor * add "infra/conf/mergers" * contribute to it will benifit `v2ray run`,`v2ray test`,`v2ray convert` * easily add new formats, by just adding a converter like json.FromTOML * default format auto, to all cmds above * auto detect input format * mixed formats support * better stdin behavior * don't wait if no content * don't use 'stdin:' placeholder * `v2ray test` now behaves exactly the same with `v2ray run`, including stdin reading * api ado, adi, rmo, rmi refactor * support folders to files resolving, mixed formats * remove remaining 'stdin:' placeholders * fix tests * os.Stdin.Stat() behaves different in platforms, removed * code optimize Co-authored-by: loyalsoldier <10487845+Loyalsoldier@users.noreply.github.com>
This commit is contained in:
parent
d581fb9c00
commit
ebbf31f07e
@ -4,14 +4,17 @@ package command
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
grpc "google.golang.org/grpc"
|
grpc "google.golang.org/grpc"
|
||||||
|
|
||||||
core "github.com/v2fly/v2ray-core/v4"
|
core "github.com/v2fly/v2ray-core/v4"
|
||||||
"github.com/v2fly/v2ray-core/v4/app/log"
|
"github.com/v2fly/v2ray-core/v4/app/log"
|
||||||
"github.com/v2fly/v2ray-core/v4/common"
|
"github.com/v2fly/v2ray-core/v4/common"
|
||||||
|
cmlog "github.com/v2fly/v2ray-core/v4/common/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// LoggerServer is the implemention of LoggerService
|
||||||
type LoggerServer struct {
|
type LoggerServer struct {
|
||||||
V *core.Instance
|
V *core.Instance
|
||||||
}
|
}
|
||||||
@ -31,6 +34,34 @@ func (s *LoggerServer) RestartLogger(ctx context.Context, request *RestartLogger
|
|||||||
return &RestartLoggerResponse{}, nil
|
return &RestartLoggerResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FollowLog implements LoggerService.
|
||||||
|
func (s *LoggerServer) FollowLog(_ *FollowLogRequest, stream LoggerService_FollowLogServer) error {
|
||||||
|
logger := s.V.GetFeature((*log.Instance)(nil))
|
||||||
|
if logger == nil {
|
||||||
|
return newError("unable to get logger instance")
|
||||||
|
}
|
||||||
|
follower, ok := logger.(cmlog.Follower)
|
||||||
|
if !ok {
|
||||||
|
return newError("logger not support following")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
f := func(msg cmlog.Message) {
|
||||||
|
err = stream.Send(&FollowLogResponse{
|
||||||
|
Message: msg.String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
follower.AddFollower(f)
|
||||||
|
defer follower.RemoveFollower(f)
|
||||||
|
ticker := time.NewTicker(time.Second)
|
||||||
|
for {
|
||||||
|
<-ticker.C
|
||||||
|
if err != nil {
|
||||||
|
ticker.Stop()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *LoggerServer) mustEmbedUnimplementedLoggerServiceServer() {}
|
func (s *LoggerServer) mustEmbedUnimplementedLoggerServiceServer() {}
|
||||||
|
|
||||||
type service struct {
|
type service struct {
|
||||||
|
@ -139,6 +139,91 @@ func (*RestartLoggerResponse) Descriptor() ([]byte, []int) {
|
|||||||
return file_app_log_command_config_proto_rawDescGZIP(), []int{2}
|
return file_app_log_command_config_proto_rawDescGZIP(), []int{2}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FollowLogRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FollowLogRequest) Reset() {
|
||||||
|
*x = FollowLogRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_app_log_command_config_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FollowLogRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*FollowLogRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *FollowLogRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_log_command_config_proto_msgTypes[3]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use FollowLogRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*FollowLogRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_log_command_config_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FollowLogResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FollowLogResponse) Reset() {
|
||||||
|
*x = FollowLogResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_app_log_command_config_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FollowLogResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*FollowLogResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *FollowLogResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_log_command_config_proto_msgTypes[4]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use FollowLogResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*FollowLogResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_log_command_config_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FollowLogResponse) GetMessage() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Message
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
var File_app_log_command_config_proto protoreflect.FileDescriptor
|
var File_app_log_command_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_app_log_command_config_proto_rawDesc = []byte{
|
var file_app_log_command_config_proto_rawDesc = []byte{
|
||||||
@ -149,23 +234,34 @@ var file_app_log_command_config_proto_rawDesc = []byte{
|
|||||||
0x6e, 0x66, 0x69, 0x67, 0x22, 0x16, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c,
|
0x6e, 0x66, 0x69, 0x67, 0x22, 0x16, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c,
|
||||||
0x6f, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x17, 0x0a, 0x15,
|
0x6f, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x17, 0x0a, 0x15,
|
||||||
0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x73,
|
0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x73,
|
||||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x87, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72,
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x4c,
|
||||||
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x76, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x74, 0x61,
|
0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2d, 0x0a, 0x11, 0x46, 0x6f, 0x6c,
|
||||||
0x72, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x12, 0x30, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79,
|
0x6c, 0x6f, 0x77, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18,
|
||||||
0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, 0x2e, 0x63, 0x6f,
|
0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x6f, 0x67,
|
0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0xf5, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67,
|
||||||
0x67, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x76, 0x32, 0x72,
|
0x67, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x76, 0x0a, 0x0d, 0x52, 0x65,
|
||||||
0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, 0x2e,
|
0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x12, 0x30, 0x2e, 0x76, 0x32,
|
||||||
0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c,
|
0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67,
|
||||||
0x6f, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42,
|
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74,
|
||||||
0x6f, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72,
|
0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e,
|
||||||
0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
|
0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c,
|
||||||
0x64, 0x50, 0x01, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
|
0x6f, 0x67, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61,
|
||||||
0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65,
|
0x72, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||||
0x2f, 0x76, 0x34, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x6c, 0x6f, 0x67, 0x2f, 0x63, 0x6f, 0x6d, 0x6d,
|
0x22, 0x00, 0x12, 0x6c, 0x0a, 0x09, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x4c, 0x6f, 0x67, 0x12,
|
||||||
0x61, 0x6e, 0x64, 0xaa, 0x02, 0x1a, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65,
|
0x2c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70,
|
||||||
0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4c, 0x6f, 0x67, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
|
0x2e, 0x6c, 0x6f, 0x67, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x46, 0x6f, 0x6c,
|
||||||
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x6c, 0x6f, 0x77, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e,
|
||||||
|
0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c,
|
||||||
|
0x6f, 0x67, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x46, 0x6f, 0x6c, 0x6c, 0x6f,
|
||||||
|
0x77, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01,
|
||||||
|
0x42, 0x6f, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f,
|
||||||
|
0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x67, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61,
|
||||||
|
0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
|
||||||
|
0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72,
|
||||||
|
0x65, 0x2f, 0x76, 0x34, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x6c, 0x6f, 0x67, 0x2f, 0x63, 0x6f, 0x6d,
|
||||||
|
0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, 0x1a, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72,
|
||||||
|
0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4c, 0x6f, 0x67, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
|
||||||
|
0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -180,17 +276,21 @@ func file_app_log_command_config_proto_rawDescGZIP() []byte {
|
|||||||
return file_app_log_command_config_proto_rawDescData
|
return file_app_log_command_config_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_app_log_command_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
var file_app_log_command_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||||
var file_app_log_command_config_proto_goTypes = []interface{}{
|
var file_app_log_command_config_proto_goTypes = []interface{}{
|
||||||
(*Config)(nil), // 0: v2ray.core.app.log.command.Config
|
(*Config)(nil), // 0: v2ray.core.app.log.command.Config
|
||||||
(*RestartLoggerRequest)(nil), // 1: v2ray.core.app.log.command.RestartLoggerRequest
|
(*RestartLoggerRequest)(nil), // 1: v2ray.core.app.log.command.RestartLoggerRequest
|
||||||
(*RestartLoggerResponse)(nil), // 2: v2ray.core.app.log.command.RestartLoggerResponse
|
(*RestartLoggerResponse)(nil), // 2: v2ray.core.app.log.command.RestartLoggerResponse
|
||||||
|
(*FollowLogRequest)(nil), // 3: v2ray.core.app.log.command.FollowLogRequest
|
||||||
|
(*FollowLogResponse)(nil), // 4: v2ray.core.app.log.command.FollowLogResponse
|
||||||
}
|
}
|
||||||
var file_app_log_command_config_proto_depIdxs = []int32{
|
var file_app_log_command_config_proto_depIdxs = []int32{
|
||||||
1, // 0: v2ray.core.app.log.command.LoggerService.RestartLogger:input_type -> v2ray.core.app.log.command.RestartLoggerRequest
|
1, // 0: v2ray.core.app.log.command.LoggerService.RestartLogger:input_type -> v2ray.core.app.log.command.RestartLoggerRequest
|
||||||
2, // 1: v2ray.core.app.log.command.LoggerService.RestartLogger:output_type -> v2ray.core.app.log.command.RestartLoggerResponse
|
3, // 1: v2ray.core.app.log.command.LoggerService.FollowLog:input_type -> v2ray.core.app.log.command.FollowLogRequest
|
||||||
1, // [1:2] is the sub-list for method output_type
|
2, // 2: v2ray.core.app.log.command.LoggerService.RestartLogger:output_type -> v2ray.core.app.log.command.RestartLoggerResponse
|
||||||
0, // [0:1] is the sub-list for method input_type
|
4, // 3: v2ray.core.app.log.command.LoggerService.FollowLog:output_type -> v2ray.core.app.log.command.FollowLogResponse
|
||||||
|
2, // [2:4] is the sub-list for method output_type
|
||||||
|
0, // [0:2] is the sub-list for method input_type
|
||||||
0, // [0:0] is the sub-list for extension type_name
|
0, // [0:0] is the sub-list for extension type_name
|
||||||
0, // [0:0] is the sub-list for extension extendee
|
0, // [0:0] is the sub-list for extension extendee
|
||||||
0, // [0:0] is the sub-list for field type_name
|
0, // [0:0] is the sub-list for field type_name
|
||||||
@ -238,6 +338,30 @@ func file_app_log_command_config_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
file_app_log_command_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*FollowLogRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_app_log_command_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*FollowLogResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
type x struct{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
@ -245,7 +369,7 @@ func file_app_log_command_config_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_app_log_command_config_proto_rawDesc,
|
RawDescriptor: file_app_log_command_config_proto_rawDesc,
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 3,
|
NumMessages: 5,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
@ -12,6 +12,13 @@ message RestartLoggerRequest {}
|
|||||||
|
|
||||||
message RestartLoggerResponse {}
|
message RestartLoggerResponse {}
|
||||||
|
|
||||||
|
message FollowLogRequest {}
|
||||||
|
|
||||||
|
message FollowLogResponse {
|
||||||
|
string message = 1;
|
||||||
|
}
|
||||||
|
|
||||||
service LoggerService {
|
service LoggerService {
|
||||||
rpc RestartLogger(RestartLoggerRequest) returns (RestartLoggerResponse) {}
|
rpc RestartLogger(RestartLoggerRequest) returns (RestartLoggerResponse) {}
|
||||||
|
rpc FollowLog(FollowLogRequest) returns (stream FollowLogResponse) {};
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ const _ = grpc.SupportPackageIsVersion7
|
|||||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
type LoggerServiceClient interface {
|
type LoggerServiceClient interface {
|
||||||
RestartLogger(ctx context.Context, in *RestartLoggerRequest, opts ...grpc.CallOption) (*RestartLoggerResponse, error)
|
RestartLogger(ctx context.Context, in *RestartLoggerRequest, opts ...grpc.CallOption) (*RestartLoggerResponse, error)
|
||||||
|
FollowLog(ctx context.Context, in *FollowLogRequest, opts ...grpc.CallOption) (LoggerService_FollowLogClient, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type loggerServiceClient struct {
|
type loggerServiceClient struct {
|
||||||
@ -38,11 +39,44 @@ func (c *loggerServiceClient) RestartLogger(ctx context.Context, in *RestartLogg
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *loggerServiceClient) FollowLog(ctx context.Context, in *FollowLogRequest, opts ...grpc.CallOption) (LoggerService_FollowLogClient, error) {
|
||||||
|
stream, err := c.cc.NewStream(ctx, &LoggerService_ServiceDesc.Streams[0], "/v2ray.core.app.log.command.LoggerService/FollowLog", opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
x := &loggerServiceFollowLogClient{stream}
|
||||||
|
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := x.ClientStream.CloseSend(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoggerService_FollowLogClient interface {
|
||||||
|
Recv() (*FollowLogResponse, error)
|
||||||
|
grpc.ClientStream
|
||||||
|
}
|
||||||
|
|
||||||
|
type loggerServiceFollowLogClient struct {
|
||||||
|
grpc.ClientStream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *loggerServiceFollowLogClient) Recv() (*FollowLogResponse, error) {
|
||||||
|
m := new(FollowLogResponse)
|
||||||
|
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
// LoggerServiceServer is the server API for LoggerService service.
|
// LoggerServiceServer is the server API for LoggerService service.
|
||||||
// All implementations must embed UnimplementedLoggerServiceServer
|
// All implementations must embed UnimplementedLoggerServiceServer
|
||||||
// for forward compatibility
|
// for forward compatibility
|
||||||
type LoggerServiceServer interface {
|
type LoggerServiceServer interface {
|
||||||
RestartLogger(context.Context, *RestartLoggerRequest) (*RestartLoggerResponse, error)
|
RestartLogger(context.Context, *RestartLoggerRequest) (*RestartLoggerResponse, error)
|
||||||
|
FollowLog(*FollowLogRequest, LoggerService_FollowLogServer) error
|
||||||
mustEmbedUnimplementedLoggerServiceServer()
|
mustEmbedUnimplementedLoggerServiceServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,6 +87,9 @@ type UnimplementedLoggerServiceServer struct {
|
|||||||
func (UnimplementedLoggerServiceServer) RestartLogger(context.Context, *RestartLoggerRequest) (*RestartLoggerResponse, error) {
|
func (UnimplementedLoggerServiceServer) RestartLogger(context.Context, *RestartLoggerRequest) (*RestartLoggerResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method RestartLogger not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method RestartLogger not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedLoggerServiceServer) FollowLog(*FollowLogRequest, LoggerService_FollowLogServer) error {
|
||||||
|
return status.Errorf(codes.Unimplemented, "method FollowLog not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedLoggerServiceServer) mustEmbedUnimplementedLoggerServiceServer() {}
|
func (UnimplementedLoggerServiceServer) mustEmbedUnimplementedLoggerServiceServer() {}
|
||||||
|
|
||||||
// UnsafeLoggerServiceServer may be embedded to opt out of forward compatibility for this service.
|
// UnsafeLoggerServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||||
@ -84,6 +121,27 @@ func _LoggerService_RestartLogger_Handler(srv interface{}, ctx context.Context,
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _LoggerService_FollowLog_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
|
m := new(FollowLogRequest)
|
||||||
|
if err := stream.RecvMsg(m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return srv.(LoggerServiceServer).FollowLog(m, &loggerServiceFollowLogServer{stream})
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoggerService_FollowLogServer interface {
|
||||||
|
Send(*FollowLogResponse) error
|
||||||
|
grpc.ServerStream
|
||||||
|
}
|
||||||
|
|
||||||
|
type loggerServiceFollowLogServer struct {
|
||||||
|
grpc.ServerStream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *loggerServiceFollowLogServer) Send(m *FollowLogResponse) error {
|
||||||
|
return x.ServerStream.SendMsg(m)
|
||||||
|
}
|
||||||
|
|
||||||
// LoggerService_ServiceDesc is the grpc.ServiceDesc for LoggerService service.
|
// LoggerService_ServiceDesc is the grpc.ServiceDesc for LoggerService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@ -96,6 +154,12 @@ var LoggerService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
Handler: _LoggerService_RestartLogger_Handler,
|
Handler: _LoggerService_RestartLogger_Handler,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{},
|
Streams: []grpc.StreamDesc{
|
||||||
|
{
|
||||||
|
StreamName: "FollowLog",
|
||||||
|
Handler: _LoggerService_FollowLog_Handler,
|
||||||
|
ServerStreams: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
Metadata: "app/log/command/config.proto",
|
Metadata: "app/log/command/config.proto",
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ package log
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/v2fly/v2ray-core/v4/common"
|
"github.com/v2fly/v2ray-core/v4/common"
|
||||||
@ -16,6 +17,7 @@ type Instance struct {
|
|||||||
config *Config
|
config *Config
|
||||||
accessLogger log.Handler
|
accessLogger log.Handler
|
||||||
errorLogger log.Handler
|
errorLogger log.Handler
|
||||||
|
followers map[reflect.Value]func(msg log.Message)
|
||||||
active bool
|
active bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,6 +91,23 @@ func (g *Instance) Start() error {
|
|||||||
return g.startInternal()
|
return g.startInternal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddFollower implements log.Follower.
|
||||||
|
func (g *Instance) AddFollower(f func(msg log.Message)) {
|
||||||
|
g.Lock()
|
||||||
|
defer g.Unlock()
|
||||||
|
if g.followers == nil {
|
||||||
|
g.followers = make(map[reflect.Value]func(msg log.Message))
|
||||||
|
}
|
||||||
|
g.followers[reflect.ValueOf(f)] = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveFollower implements log.Follower.
|
||||||
|
func (g *Instance) RemoveFollower(f func(msg log.Message)) {
|
||||||
|
g.Lock()
|
||||||
|
defer g.Unlock()
|
||||||
|
delete(g.followers, reflect.ValueOf(f))
|
||||||
|
}
|
||||||
|
|
||||||
// Handle implements log.Handler.
|
// Handle implements log.Handler.
|
||||||
func (g *Instance) Handle(msg log.Message) {
|
func (g *Instance) Handle(msg log.Message) {
|
||||||
g.RLock()
|
g.RLock()
|
||||||
@ -98,6 +117,10 @@ func (g *Instance) Handle(msg log.Message) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, f := range g.followers {
|
||||||
|
f(msg)
|
||||||
|
}
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case *log.AccessMessage:
|
case *log.AccessMessage:
|
||||||
if g.accessLogger != nil {
|
if g.accessLogger != nil {
|
||||||
|
@ -12,7 +12,7 @@ type Balancer struct {
|
|||||||
ohm outbound.Manager
|
ohm outbound.Manager
|
||||||
fallbackTag string
|
fallbackTag string
|
||||||
|
|
||||||
override overridden
|
override override
|
||||||
}
|
}
|
||||||
|
|
||||||
// PickOutbound picks the tag of a outbound
|
// PickOutbound picks the tag of a outbound
|
||||||
|
@ -43,39 +43,39 @@ func (r *Router) OverrideSelecting(balancer string, selects []string, validity t
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type overriddenSettings struct {
|
type overrideSettings struct {
|
||||||
selects []string
|
selects []string
|
||||||
until time.Time
|
until time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type overridden struct {
|
type override struct {
|
||||||
access sync.RWMutex
|
access sync.RWMutex
|
||||||
settings overriddenSettings
|
settings overrideSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get gets the overridden settings
|
// Get gets the override settings
|
||||||
func (o *overridden) Get() *overriddenSettings {
|
func (o *override) Get() *overrideSettings {
|
||||||
o.access.RLock()
|
o.access.RLock()
|
||||||
defer o.access.RUnlock()
|
defer o.access.RUnlock()
|
||||||
if len(o.settings.selects) == 0 || time.Now().After(o.settings.until) {
|
if len(o.settings.selects) == 0 || time.Now().After(o.settings.until) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return &overriddenSettings{
|
return &overrideSettings{
|
||||||
selects: o.settings.selects,
|
selects: o.settings.selects,
|
||||||
until: o.settings.until,
|
until: o.settings.until,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put updates the overridden settings
|
// Put updates the override settings
|
||||||
func (o *overridden) Put(selects []string, until time.Time) {
|
func (o *override) Put(selects []string, until time.Time) {
|
||||||
o.access.Lock()
|
o.access.Lock()
|
||||||
defer o.access.Unlock()
|
defer o.access.Unlock()
|
||||||
o.settings.selects = selects
|
o.settings.selects = selects
|
||||||
o.settings.until = until
|
o.settings.until = until
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear clears the overridden settings
|
// Clear clears the override settings
|
||||||
func (o *overridden) Clear() {
|
func (o *override) Clear() {
|
||||||
o.access.Lock()
|
o.access.Lock()
|
||||||
defer o.access.Unlock()
|
defer o.access.Unlock()
|
||||||
o.settings.selects = nil
|
o.settings.selects = nil
|
||||||
|
@ -49,9 +49,20 @@ func (s *statsServer) GetStats(ctx context.Context, request *GetStatsRequest) (*
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *statsServer) QueryStats(ctx context.Context, request *QueryStatsRequest) (*QueryStatsResponse, error) {
|
func (s *statsServer) QueryStats(ctx context.Context, request *QueryStatsRequest) (*QueryStatsResponse, error) {
|
||||||
matcher, err := strmatcher.Substr.New(request.Pattern)
|
mgroup := &strmatcher.MatcherGroup{}
|
||||||
if err != nil {
|
if request.Pattern != "" {
|
||||||
return nil, err
|
request.Patterns = append(request.Patterns, request.Pattern)
|
||||||
|
}
|
||||||
|
t := strmatcher.Substr
|
||||||
|
if request.Regexp {
|
||||||
|
t = strmatcher.Regex
|
||||||
|
}
|
||||||
|
for _, p := range request.Patterns {
|
||||||
|
m, err := t.New(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mgroup.Add(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
response := &QueryStatsResponse{}
|
response := &QueryStatsResponse{}
|
||||||
@ -62,7 +73,7 @@ func (s *statsServer) QueryStats(ctx context.Context, request *QueryStatsRequest
|
|||||||
}
|
}
|
||||||
|
|
||||||
manager.VisitCounters(func(name string, c feature_stats.Counter) bool {
|
manager.VisitCounters(func(name string, c feature_stats.Counter) bool {
|
||||||
if matcher.Match(name) {
|
if mgroup.Size() == 0 || len(mgroup.Match(name)) > 0 {
|
||||||
var value int64
|
var value int64
|
||||||
if request.Reset_ {
|
if request.Reset_ {
|
||||||
value = c.Set(0)
|
value = c.Set(0)
|
||||||
|
@ -189,8 +189,11 @@ type QueryStatsRequest struct {
|
|||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
Pattern string `protobuf:"bytes,1,opt,name=pattern,proto3" json:"pattern,omitempty"`
|
// Deprecated, use Patterns instead
|
||||||
Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"`
|
Pattern string `protobuf:"bytes,1,opt,name=pattern,proto3" json:"pattern,omitempty"`
|
||||||
|
Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"`
|
||||||
|
Patterns []string `protobuf:"bytes,3,rep,name=patterns,proto3" json:"patterns,omitempty"`
|
||||||
|
Regexp bool `protobuf:"varint,4,opt,name=regexp,proto3" json:"regexp,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *QueryStatsRequest) Reset() {
|
func (x *QueryStatsRequest) Reset() {
|
||||||
@ -239,6 +242,20 @@ func (x *QueryStatsRequest) GetReset_() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *QueryStatsRequest) GetPatterns() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Patterns
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *QueryStatsRequest) GetRegexp() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Regexp
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type QueryStatsResponse struct {
|
type QueryStatsResponse struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
@ -499,66 +516,70 @@ var file_app_stats_command_command_proto_rawDesc = []byte{
|
|||||||
0x73, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x73, 0x74, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
|
0x73, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x73, 0x74, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
|
||||||
0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70,
|
0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70,
|
||||||
0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e,
|
0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e,
|
||||||
0x53, 0x74, 0x61, 0x74, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74, 0x22, 0x43, 0x0a, 0x11, 0x51, 0x75,
|
0x53, 0x74, 0x61, 0x74, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74, 0x22, 0x77, 0x0a, 0x11, 0x51, 0x75,
|
||||||
0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
||||||
0x18, 0x0a, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
0x18, 0x0a, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||||
0x52, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x73,
|
0x52, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x73,
|
||||||
0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x22,
|
0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x12,
|
||||||
0x4c, 0x0a, 0x12, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73,
|
0x1a, 0x0a, 0x08, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
|
||||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x73, 0x74, 0x61, 0x74, 0x18, 0x01, 0x20,
|
0x09, 0x52, 0x08, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72,
|
||||||
0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65,
|
0x65, 0x67, 0x65, 0x78, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x72, 0x65, 0x67,
|
||||||
|
0x65, 0x78, 0x70, 0x22, 0x4c, 0x0a, 0x12, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74,
|
||||||
|
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x73, 0x74, 0x61,
|
||||||
|
0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e,
|
||||||
|
0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63,
|
||||||
|
0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x52, 0x04, 0x73, 0x74, 0x61,
|
||||||
|
0x74, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71,
|
||||||
|
0x75, 0x65, 0x73, 0x74, 0x22, 0xa2, 0x02, 0x0a, 0x10, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74,
|
||||||
|
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x4e, 0x75, 0x6d,
|
||||||
|
0x47, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52,
|
||||||
|
0x0c, 0x4e, 0x75, 0x6d, 0x47, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x12, 0x14, 0x0a,
|
||||||
|
0x05, 0x4e, 0x75, 0x6d, 0x47, 0x43, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x4e, 0x75,
|
||||||
|
0x6d, 0x47, 0x43, 0x12, 0x14, 0x0a, 0x05, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x18, 0x03, 0x20, 0x01,
|
||||||
|
0x28, 0x04, 0x52, 0x05, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x54, 0x6f, 0x74,
|
||||||
|
0x61, 0x6c, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x54,
|
||||||
|
0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x12, 0x10, 0x0a, 0x03, 0x53, 0x79, 0x73,
|
||||||
|
0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x53, 0x79, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x4d,
|
||||||
|
0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x4d, 0x61,
|
||||||
|
0x6c, 0x6c, 0x6f, 0x63, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x46, 0x72, 0x65, 0x65, 0x73, 0x18, 0x07,
|
||||||
|
0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x46, 0x72, 0x65, 0x65, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x4c,
|
||||||
|
0x69, 0x76, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04,
|
||||||
|
0x52, 0x0b, 0x4c, 0x69, 0x76, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x22, 0x0a,
|
||||||
|
0x0c, 0x50, 0x61, 0x75, 0x73, 0x65, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x18, 0x09, 0x20,
|
||||||
|
0x01, 0x28, 0x04, 0x52, 0x0c, 0x50, 0x61, 0x75, 0x73, 0x65, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e,
|
||||||
|
0x73, 0x12, 0x16, 0x0a, 0x06, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28,
|
||||||
|
0x0d, 0x52, 0x06, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e,
|
||||||
|
0x66, 0x69, 0x67, 0x32, 0xde, 0x02, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x73, 0x53, 0x65, 0x72,
|
||||||
|
0x76, 0x69, 0x63, 0x65, 0x12, 0x6b, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73,
|
||||||
|
0x12, 0x2d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70,
|
||||||
|
0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e,
|
||||||
|
0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||||
|
0x2e, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70,
|
||||||
|
0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47,
|
||||||
|
0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||||
|
0x00, 0x12, 0x71, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12,
|
||||||
|
0x2f, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70,
|
||||||
|
0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x51,
|
||||||
|
0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||||
|
0x1a, 0x30, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70,
|
||||||
|
0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e,
|
||||||
|
0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||||
|
0x73, 0x65, 0x22, 0x00, 0x12, 0x6e, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x53, 0x79, 0x73, 0x53, 0x74,
|
||||||
|
0x61, 0x74, 0x73, 0x12, 0x2d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65,
|
||||||
0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61,
|
0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61,
|
||||||
0x6e, 0x64, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74, 0x22, 0x11, 0x0a,
|
0x6e, 0x64, 0x2e, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||||
0x0f, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e,
|
||||||
0x22, 0xa2, 0x02, 0x0a, 0x10, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73,
|
0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
|
||||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x4e, 0x75, 0x6d, 0x47, 0x6f, 0x72, 0x6f,
|
0x64, 0x2e, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||||
0x75, 0x74, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x4e, 0x75, 0x6d,
|
0x73, 0x65, 0x22, 0x00, 0x42, 0x75, 0x0a, 0x20, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61,
|
||||||
0x47, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x75, 0x6d,
|
0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73,
|
||||||
0x47, 0x43, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x4e, 0x75, 0x6d, 0x47, 0x43, 0x12,
|
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68,
|
||||||
0x14, 0x0a, 0x05, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05,
|
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72,
|
||||||
0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6c,
|
0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x34, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x73,
|
||||||
0x6c, 0x6f, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x54, 0x6f, 0x74, 0x61, 0x6c,
|
0x74, 0x61, 0x74, 0x73, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, 0x1c, 0x56,
|
||||||
0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x12, 0x10, 0x0a, 0x03, 0x53, 0x79, 0x73, 0x18, 0x05, 0x20, 0x01,
|
0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x53, 0x74,
|
||||||
0x28, 0x04, 0x52, 0x03, 0x53, 0x79, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x4d, 0x61, 0x6c, 0x6c, 0x6f,
|
0x61, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f,
|
||||||
0x63, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x4d, 0x61, 0x6c, 0x6c, 0x6f, 0x63,
|
0x74, 0x6f, 0x33,
|
||||||
0x73, 0x12, 0x14, 0x0a, 0x05, 0x46, 0x72, 0x65, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04,
|
|
||||||
0x52, 0x05, 0x46, 0x72, 0x65, 0x65, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x4c, 0x69, 0x76, 0x65, 0x4f,
|
|
||||||
0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x4c, 0x69,
|
|
||||||
0x76, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x50, 0x61, 0x75,
|
|
||||||
0x73, 0x65, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52,
|
|
||||||
0x0c, 0x50, 0x61, 0x75, 0x73, 0x65, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x12, 0x16, 0x0a,
|
|
||||||
0x06, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x55,
|
|
||||||
0x70, 0x74, 0x69, 0x6d, 0x65, 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x32,
|
|
||||||
0xde, 0x02, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
|
|
||||||
0x12, 0x6b, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x2d, 0x2e, 0x76,
|
|
||||||
0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74,
|
|
||||||
0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53,
|
|
||||||
0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x76, 0x32,
|
|
||||||
0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61,
|
|
||||||
0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74,
|
|
||||||
0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x71, 0x0a,
|
|
||||||
0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x2f, 0x2e, 0x76, 0x32,
|
|
||||||
0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61,
|
|
||||||
0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79,
|
|
||||||
0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x76,
|
|
||||||
0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74,
|
|
||||||
0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x51, 0x75, 0x65, 0x72,
|
|
||||||
0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
|
|
||||||
0x12, 0x6e, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12,
|
|
||||||
0x2d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70,
|
|
||||||
0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x53,
|
|
||||||
0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e,
|
|
||||||
0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e,
|
|
||||||
0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x53, 0x79,
|
|
||||||
0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
|
|
||||||
0x42, 0x75, 0x0a, 0x20, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f,
|
|
||||||
0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d,
|
|
||||||
0x6d, 0x61, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
|
||||||
0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63,
|
|
||||||
0x6f, 0x72, 0x65, 0x2f, 0x76, 0x34, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x73,
|
|
||||||
0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, 0x1c, 0x56, 0x32, 0x52, 0x61, 0x79,
|
|
||||||
0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e,
|
|
||||||
0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -23,8 +23,11 @@ message GetStatsResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message QueryStatsRequest {
|
message QueryStatsRequest {
|
||||||
|
// Deprecated, use Patterns instead
|
||||||
string pattern = 1;
|
string pattern = 1;
|
||||||
bool reset = 2;
|
bool reset = 2;
|
||||||
|
repeated string patterns = 3;
|
||||||
|
bool regexp = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message QueryStatsResponse {
|
message QueryStatsResponse {
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -28,8 +27,6 @@ func LoadArgToBytes(arg string) (out []byte, err error) {
|
|||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(arg, "http://"), strings.HasPrefix(arg, "https://"):
|
case strings.HasPrefix(arg, "http://"), strings.HasPrefix(arg, "https://"):
|
||||||
out, err = FetchHTTPContent(arg)
|
out, err = FetchHTTPContent(arg)
|
||||||
case (arg == "stdin:"):
|
|
||||||
out, err = ioutil.ReadAll(os.Stdin)
|
|
||||||
default:
|
default:
|
||||||
out, err = ioutil.ReadFile(arg)
|
out, err = ioutil.ReadFile(arg)
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,12 @@ type Handler interface {
|
|||||||
Handle(msg Message)
|
Handle(msg Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Follower is the interface for following logs.
|
||||||
|
type Follower interface {
|
||||||
|
AddFollower(func(msg Message))
|
||||||
|
RemoveFollower(func(msg Message))
|
||||||
|
}
|
||||||
|
|
||||||
// GeneralMessage is a general log message that can contain all kind of content.
|
// GeneralMessage is a general log message that can contain all kind of content.
|
||||||
type GeneralMessage struct {
|
type GeneralMessage struct {
|
||||||
Severity Severity
|
Severity Severity
|
||||||
|
98
common/units/bytesize.go
Normal file
98
common/units/bytesize.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package units
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errInvalidSize = errors.New("invalid size")
|
||||||
|
var errInvalidUnit = errors.New("invalid or unsupported unit")
|
||||||
|
|
||||||
|
// ByteSize is the size of bytes
|
||||||
|
type ByteSize uint64
|
||||||
|
|
||||||
|
const (
|
||||||
|
_ = iota
|
||||||
|
// KB = 1KB
|
||||||
|
KB ByteSize = 1 << (10 * iota)
|
||||||
|
// MB = 1MB
|
||||||
|
MB
|
||||||
|
// GB = 1GB
|
||||||
|
GB
|
||||||
|
// TB = 1TB
|
||||||
|
TB
|
||||||
|
// PB = 1PB
|
||||||
|
PB
|
||||||
|
// EB = 1EB
|
||||||
|
EB
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b ByteSize) String() string {
|
||||||
|
unit := ""
|
||||||
|
value := float64(0)
|
||||||
|
switch {
|
||||||
|
case b == 0:
|
||||||
|
return "0"
|
||||||
|
case b < KB:
|
||||||
|
unit = "B"
|
||||||
|
value = float64(b)
|
||||||
|
case b < MB:
|
||||||
|
unit = "KB"
|
||||||
|
value = float64(b) / float64(KB)
|
||||||
|
case b < GB:
|
||||||
|
unit = "MB"
|
||||||
|
value = float64(b) / float64(MB)
|
||||||
|
case b < TB:
|
||||||
|
unit = "GB"
|
||||||
|
value = float64(b) / float64(GB)
|
||||||
|
case b < PB:
|
||||||
|
unit = "TB"
|
||||||
|
value = float64(b) / float64(TB)
|
||||||
|
case b < EB:
|
||||||
|
unit = "PB"
|
||||||
|
value = float64(b) / float64(PB)
|
||||||
|
default:
|
||||||
|
unit = "EB"
|
||||||
|
value = float64(b) / float64(EB)
|
||||||
|
}
|
||||||
|
result := strconv.FormatFloat(value, 'f', 2, 64)
|
||||||
|
result = strings.TrimSuffix(result, ".0")
|
||||||
|
return result + unit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses ByteSize from string
|
||||||
|
func (b *ByteSize) Parse(s string) error {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
s = strings.ToUpper(s)
|
||||||
|
i := strings.IndexFunc(s, unicode.IsLetter)
|
||||||
|
if i == -1 {
|
||||||
|
return errInvalidUnit
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesString, multiple := s[:i], s[i:]
|
||||||
|
bytes, err := strconv.ParseFloat(bytesString, 64)
|
||||||
|
if err != nil || bytes <= 0 {
|
||||||
|
return errInvalidSize
|
||||||
|
}
|
||||||
|
switch multiple {
|
||||||
|
case "B":
|
||||||
|
*b = ByteSize(bytes)
|
||||||
|
case "K", "KB", "KIB":
|
||||||
|
*b = ByteSize(bytes * float64(KB))
|
||||||
|
case "M", "MB", "MIB":
|
||||||
|
*b = ByteSize(bytes * float64(MB))
|
||||||
|
case "G", "GB", "GIB":
|
||||||
|
*b = ByteSize(bytes * float64(GB))
|
||||||
|
case "T", "TB", "TIB":
|
||||||
|
*b = ByteSize(bytes * float64(TB))
|
||||||
|
case "P", "PB", "PIB":
|
||||||
|
*b = ByteSize(bytes * float64(PB))
|
||||||
|
case "E", "EB", "EIB":
|
||||||
|
*b = ByteSize(bytes * float64(EB))
|
||||||
|
default:
|
||||||
|
return errInvalidUnit
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
66
common/units/bytesize_test.go
Normal file
66
common/units/bytesize_test.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package units_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/v2fly/v2ray-core/v4/common/units"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestByteSizes(t *testing.T) {
|
||||||
|
size := units.ByteSize(0)
|
||||||
|
assertSizeString(t, size, "0")
|
||||||
|
size++
|
||||||
|
assertSizeValue(t,
|
||||||
|
assertSizeString(t, size, "1.00B"),
|
||||||
|
size,
|
||||||
|
)
|
||||||
|
size <<= 10
|
||||||
|
assertSizeValue(t,
|
||||||
|
assertSizeString(t, size, "1.00KB"),
|
||||||
|
size,
|
||||||
|
)
|
||||||
|
size <<= 10
|
||||||
|
assertSizeValue(t,
|
||||||
|
assertSizeString(t, size, "1.00MB"),
|
||||||
|
size,
|
||||||
|
)
|
||||||
|
size <<= 10
|
||||||
|
assertSizeValue(t,
|
||||||
|
assertSizeString(t, size, "1.00GB"),
|
||||||
|
size,
|
||||||
|
)
|
||||||
|
size <<= 10
|
||||||
|
assertSizeValue(t,
|
||||||
|
assertSizeString(t, size, "1.00TB"),
|
||||||
|
size,
|
||||||
|
)
|
||||||
|
size <<= 10
|
||||||
|
assertSizeValue(t,
|
||||||
|
assertSizeString(t, size, "1.00PB"),
|
||||||
|
size,
|
||||||
|
)
|
||||||
|
size <<= 10
|
||||||
|
assertSizeValue(t,
|
||||||
|
assertSizeString(t, size, "1.00EB"),
|
||||||
|
size,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertSizeValue(t *testing.T, size string, expected units.ByteSize) {
|
||||||
|
actual := units.ByteSize(0)
|
||||||
|
err := actual.Parse(size)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if actual != expected {
|
||||||
|
t.Errorf("expect %s, but got %s", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertSizeString(t *testing.T, size units.ByteSize, expected string) string {
|
||||||
|
actual := size.String()
|
||||||
|
if actual != expected {
|
||||||
|
t.Errorf("expect %s, but got %s", expected, actual)
|
||||||
|
}
|
||||||
|
return expected
|
||||||
|
}
|
145
config.go
145
config.go
@ -1,8 +1,12 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
@ -12,6 +16,21 @@ import (
|
|||||||
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
|
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// FormatAuto represents all available formats by auto selecting
|
||||||
|
FormatAuto = "auto"
|
||||||
|
// FormatJSON represents json format
|
||||||
|
FormatJSON = "json"
|
||||||
|
// FormatTOML represents toml format
|
||||||
|
FormatTOML = "toml"
|
||||||
|
// FormatYAML represents yaml format
|
||||||
|
FormatYAML = "yaml"
|
||||||
|
// FormatProtobuf represents protobuf format
|
||||||
|
FormatProtobuf = "protobuf"
|
||||||
|
// FormatProtobufShort is the short of FormatProtobuf
|
||||||
|
FormatProtobufShort = "pb"
|
||||||
|
)
|
||||||
|
|
||||||
// ConfigFormat is a configurable format of V2Ray config file.
|
// ConfigFormat is a configurable format of V2Ray config file.
|
||||||
type ConfigFormat struct {
|
type ConfigFormat struct {
|
||||||
Name []string
|
Name []string
|
||||||
@ -23,6 +42,7 @@ type ConfigFormat struct {
|
|||||||
type ConfigLoader func(input interface{}) (*Config, error)
|
type ConfigLoader func(input interface{}) (*Config, error)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
configLoaders = make([]*ConfigFormat, 0)
|
||||||
configLoaderByName = make(map[string]*ConfigFormat)
|
configLoaderByName = make(map[string]*ConfigFormat)
|
||||||
configLoaderByExt = make(map[string]*ConfigFormat)
|
configLoaderByExt = make(map[string]*ConfigFormat)
|
||||||
)
|
)
|
||||||
@ -30,11 +50,10 @@ var (
|
|||||||
// RegisterConfigLoader add a new ConfigLoader.
|
// RegisterConfigLoader add a new ConfigLoader.
|
||||||
func RegisterConfigLoader(format *ConfigFormat) error {
|
func RegisterConfigLoader(format *ConfigFormat) error {
|
||||||
for _, name := range format.Name {
|
for _, name := range format.Name {
|
||||||
lname := strings.ToLower(name)
|
if _, found := configLoaderByName[name]; found {
|
||||||
if _, found := configLoaderByName[lname]; found {
|
|
||||||
return newError(name, " already registered.")
|
return newError(name, " already registered.")
|
||||||
}
|
}
|
||||||
configLoaderByName[lname] = format
|
configLoaderByName[name] = format
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ext := range format.Extension {
|
for _, ext := range format.Extension {
|
||||||
@ -44,7 +63,7 @@ func RegisterConfigLoader(format *ConfigFormat) error {
|
|||||||
}
|
}
|
||||||
configLoaderByExt[lext] = format
|
configLoaderByExt[lext] = format
|
||||||
}
|
}
|
||||||
|
configLoaders = append(configLoaders, format)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,43 +72,101 @@ func getExtension(filename string) string {
|
|||||||
return strings.ToLower(ext)
|
return strings.ToLower(ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetConfigLoader get config loader by name and filename.
|
// GetLoaderExtensions get config loader extensions.
|
||||||
// Specify formatName to explicitly select a loader.
|
func GetLoaderExtensions(formatName string) ([]string, error) {
|
||||||
// Specify filename to choose loader by detect its extension.
|
if formatName == FormatAuto {
|
||||||
// Leave formatName and filename blank for default loader
|
return GetAllExtensions(), nil
|
||||||
func GetConfigLoader(formatName string, filename string) (*ConfigFormat, error) {
|
|
||||||
if formatName != "" {
|
|
||||||
// if explicitly specified, we can safely assume that user knows what they are
|
|
||||||
if f, found := configLoaderByName[formatName]; found {
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
return nil, newError("Unable to load config in ", formatName).AtWarning()
|
|
||||||
}
|
}
|
||||||
// no explicitly specified loader, extenstion detect first
|
if f, found := configLoaderByName[formatName]; found {
|
||||||
if ext := getExtension(filename); len(ext) > 0 {
|
return f.Extension, nil
|
||||||
if f, found := configLoaderByExt[ext]; found {
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// default loader
|
return nil, newError("config loader not found: ", formatName).AtWarning()
|
||||||
if f, found := configLoaderByName["json"]; found {
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
panic("default loader not found")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadConfig loads config with given format from given source.
|
// GetAllExtensions get all extensions supported
|
||||||
// input accepts 2 different types:
|
func GetAllExtensions() []string {
|
||||||
|
extensions := make([]string, 0)
|
||||||
|
for _, f := range configLoaderByName {
|
||||||
|
extensions = append(extensions, f.Extension...)
|
||||||
|
}
|
||||||
|
return extensions
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadConfig loads multiple config with given format from given source.
|
||||||
|
// input accepts:
|
||||||
|
// * string of a single filename/url(s) to open to read
|
||||||
// * []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, input interface{}) (*Config, error) {
|
||||||
f, err := GetConfigLoader(formatName, filename)
|
cnt := getInputCount(input)
|
||||||
if err != nil {
|
if cnt == 0 {
|
||||||
return nil, err
|
log.Println("Using config from STDIN")
|
||||||
|
input = os.Stdin
|
||||||
|
cnt = 1
|
||||||
|
}
|
||||||
|
if formatName == FormatAuto && cnt == 1 {
|
||||||
|
// This ensures only to call auto loader for multiple files,
|
||||||
|
// so that it can only care about merging scenarios
|
||||||
|
return loadSingleConfigAutoFormat(input)
|
||||||
|
}
|
||||||
|
// if input is a slice with single element, extract it
|
||||||
|
// so that unmergeable loaders don't need to deal with
|
||||||
|
// slices
|
||||||
|
s := reflect.Indirect(reflect.ValueOf(input))
|
||||||
|
k := s.Kind()
|
||||||
|
if (k == reflect.Slice || k == reflect.Array) && s.Len() == 1 {
|
||||||
|
value := reflect.Indirect(s.Index(0))
|
||||||
|
if value.Kind() == reflect.String {
|
||||||
|
// string type alias
|
||||||
|
input = fmt.Sprint(value.Interface())
|
||||||
|
} else {
|
||||||
|
input = value.Interface()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f, found := configLoaderByName[formatName]
|
||||||
|
if !found {
|
||||||
|
return nil, newError("config loader not found: ", formatName).AtWarning()
|
||||||
}
|
}
|
||||||
return f.Loader(input)
|
return f.Loader(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loadSingleConfigAutoFormat loads a single config with from given source.
|
||||||
|
// input accepts:
|
||||||
|
// * string of a single filename/url(s) to open to read
|
||||||
|
// * io.Reader that reads a config content (the original way)
|
||||||
|
func loadSingleConfigAutoFormat(input interface{}) (*Config, error) {
|
||||||
|
if file, ok := input.(string); ok {
|
||||||
|
extension := getExtension(file)
|
||||||
|
if extension != "" {
|
||||||
|
lowerName := strings.ToLower(extension)
|
||||||
|
if f, found := configLoaderByExt[lowerName]; found {
|
||||||
|
return f.Loader(file)
|
||||||
|
}
|
||||||
|
return nil, newError("config loader not found for: ", extension).AtWarning()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// no extension, try all loaders
|
||||||
|
for _, f := range configLoaders {
|
||||||
|
if f.Name[0] == FormatAuto {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c, err := f.Loader(input)
|
||||||
|
if err == nil {
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, newError("tried all loaders but failed for: ", input).AtWarning()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInputCount(input interface{}) int {
|
||||||
|
s := reflect.Indirect(reflect.ValueOf(input))
|
||||||
|
k := s.Kind()
|
||||||
|
if k == reflect.Slice || k == reflect.Array {
|
||||||
|
return s.Len()
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
func loadProtobufConfig(data []byte) (*Config, error) {
|
func loadProtobufConfig(data []byte) (*Config, error) {
|
||||||
config := new(Config)
|
config := new(Config)
|
||||||
if err := proto.Unmarshal(data, config); err != nil {
|
if err := proto.Unmarshal(data, config); err != nil {
|
||||||
@ -100,12 +177,12 @@ func loadProtobufConfig(data []byte) (*Config, error) {
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
common.Must(RegisterConfigLoader(&ConfigFormat{
|
common.Must(RegisterConfigLoader(&ConfigFormat{
|
||||||
Name: []string{"Protobuf", "pb"},
|
Name: []string{FormatProtobuf, FormatProtobufShort},
|
||||||
Extension: []string{".pb"},
|
Extension: []string{".pb"},
|
||||||
Loader: func(input interface{}) (*Config, error) {
|
Loader: func(input interface{}) (*Config, error) {
|
||||||
switch v := input.(type) {
|
switch v := input.(type) {
|
||||||
case cmdarg.Arg:
|
case string:
|
||||||
r, err := cmdarg.LoadArg(v[0])
|
r, err := cmdarg.LoadArg(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ type StrategyInfo struct {
|
|||||||
Others []*OutboundInfo // Other outbounds
|
Others []*OutboundInfo // Other outbounds
|
||||||
}
|
}
|
||||||
|
|
||||||
// BalancingOverrideInfo holds balancing overridden information
|
// BalancingOverrideInfo holds balancing override information
|
||||||
type BalancingOverrideInfo struct {
|
type BalancingOverrideInfo struct {
|
||||||
Until time.Time
|
Until time.Time
|
||||||
Selects []string
|
Selects []string
|
||||||
|
@ -24,7 +24,7 @@ func CreateObject(v *Instance, config interface{}) (interface{}, error) {
|
|||||||
//
|
//
|
||||||
// v2ray:api:stable
|
// v2ray:api:stable
|
||||||
func StartInstance(configFormat string, configBytes []byte) (*Instance, error) {
|
func StartInstance(configFormat string, configBytes []byte) (*Instance, error) {
|
||||||
config, err := LoadConfig(configFormat, "", bytes.NewReader(configBytes))
|
config, err := LoadConfig(configFormat, bytes.NewReader(configBytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ func TestV2RayDial(t *testing.T) {
|
|||||||
cfgBytes, err := proto.Marshal(config)
|
cfgBytes, err := proto.Marshal(config)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
server, err := core.StartInstance("protobuf", cfgBytes)
|
server, err := core.StartInstance(core.FormatProtobuf, cfgBytes)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
@ -111,7 +111,7 @@ func TestV2RayDialUDPConn(t *testing.T) {
|
|||||||
cfgBytes, err := proto.Marshal(config)
|
cfgBytes, err := proto.Marshal(config)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
server, err := core.StartInstance("protobuf", cfgBytes)
|
server, err := core.StartInstance(core.FormatProtobuf, cfgBytes)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ func TestV2RayDialUDP(t *testing.T) {
|
|||||||
cfgBytes, err := proto.Marshal(config)
|
cfgBytes, err := proto.Marshal(config)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
server, err := core.StartInstance("protobuf", cfgBytes)
|
server, err := core.StartInstance(core.FormatProtobuf, cfgBytes)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// mergeMaps merges source map into target
|
// mergeMaps merges source map into target
|
||||||
|
// it supports only map[string]interface{} type for any children of the map tree
|
||||||
func mergeMaps(target map[string]interface{}, source map[string]interface{}) (err error) {
|
func mergeMaps(target map[string]interface{}, source map[string]interface{}) (err error) {
|
||||||
for key, value := range source {
|
for key, value := range source {
|
||||||
target[key], err = mergeField(target[key], value)
|
target[key], err = mergeField(target[key], value)
|
||||||
|
@ -18,95 +18,46 @@ package merge
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
|
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FilesToJSON merges multiple jsons files into one json, accepts remote url, or local file path
|
// JSONs merges multiple json contents into one json.
|
||||||
func FilesToJSON(args []string) ([]byte, error) {
|
func JSONs(args [][]byte) ([]byte, error) {
|
||||||
m, err := FilesToMap(args)
|
m := make(map[string]interface{})
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return json.Marshal(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BytesToJSON merges multiple json contents into one json.
|
|
||||||
func BytesToJSON(args [][]byte) ([]byte, error) {
|
|
||||||
m, err := BytesToMap(args)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return json.Marshal(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilesToMap merges multiple json files into one map, accepts remote url, or local file path
|
|
||||||
func FilesToMap(args []string) (m map[string]interface{}, err error) {
|
|
||||||
m, err = loadFiles(args)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = applyRules(m)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BytesToMap merges multiple json contents into one map.
|
|
||||||
func BytesToMap(args [][]byte) (m map[string]interface{}, err error) {
|
|
||||||
m, err = loadBytes(args)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = applyRules(m)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadFiles(args []string) (map[string]interface{}, error) {
|
|
||||||
c := make(map[string]interface{})
|
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
r, err := cmdarg.LoadArg(arg)
|
if _, err := ToMap(arg, m); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("fail to load %s: %s", arg, err)
|
|
||||||
}
|
|
||||||
m, err := decode(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("fail to decode %s: %s", arg, err)
|
|
||||||
}
|
|
||||||
if err = mergeMaps(c, m); err != nil {
|
|
||||||
return nil, fmt.Errorf("fail to merge %s: %s", arg, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadBytes(args [][]byte) (map[string]interface{}, error) {
|
|
||||||
conf := make(map[string]interface{})
|
|
||||||
for _, arg := range args {
|
|
||||||
r := bytes.NewReader(arg)
|
|
||||||
m, err := decode(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err = mergeMaps(conf, m); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return conf, nil
|
return FromMap(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func decode(r io.Reader) (map[string]interface{}, error) {
|
// ToMap merges json content to target map and returns it
|
||||||
c := make(map[string]interface{})
|
func ToMap(content []byte, target map[string]interface{}) (map[string]interface{}, error) {
|
||||||
err := serial.DecodeJSON(r, &c)
|
if target == nil {
|
||||||
|
target = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
r := bytes.NewReader(content)
|
||||||
|
n := make(map[string]interface{})
|
||||||
|
err := serial.DecodeJSON(r, &n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return c, nil
|
if err = mergeMaps(target, n); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return target, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromMap apply merge rules to map and convert it to json
|
||||||
|
func FromMap(target map[string]interface{}) ([]byte, error) {
|
||||||
|
if target == nil {
|
||||||
|
target = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
err := ApplyRules(target)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return json.Marshal(target)
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
package merge_test
|
package merge_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"bytes"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@ -50,7 +50,7 @@ func TestMergeV2Style(t *testing.T) {
|
|||||||
]}
|
]}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
m, err := merge.BytesToMap([][]byte{[]byte(json1), []byte(json2)})
|
m, err := merge.JSONs([][]byte{[]byte(json1), []byte(json2)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@ -91,7 +91,7 @@ func TestMergeTag(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
m, err := merge.BytesToMap([][]byte{[]byte(json1), []byte(json2)})
|
m, err := merge.JSONs([][]byte{[]byte(json1), []byte(json2)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@ -147,7 +147,7 @@ func TestMergeTagValueTypes(t *testing.T) {
|
|||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
m, err := merge.BytesToMap([][]byte{[]byte(json1), []byte(json2)})
|
m, err := merge.JSONs([][]byte{[]byte(json1), []byte(json2)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@ -187,20 +187,24 @@ func TestMergeTagDeep(t *testing.T) {
|
|||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
m, err := merge.BytesToMap([][]byte{[]byte(json1), []byte(json2)})
|
m, err := merge.JSONs([][]byte{[]byte(json1), []byte(json2)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
assertResult(t, m, expected)
|
assertResult(t, m, expected)
|
||||||
}
|
}
|
||||||
func assertResult(t *testing.T, value map[string]interface{}, expected string) {
|
func assertResult(t *testing.T, value []byte, expected string) {
|
||||||
e := make(map[string]interface{})
|
v := make(map[string]interface{})
|
||||||
err := serial.DecodeJSON(strings.NewReader(expected), &e)
|
err := serial.DecodeJSON(bytes.NewReader(value), &v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(value, e) {
|
e := make(map[string]interface{})
|
||||||
bs, _ := json.Marshal(value)
|
err = serial.DecodeJSON(strings.NewReader(expected), &e)
|
||||||
t.Fatalf("expected:\n%s\n\nactual:\n%s", expected, string(bs))
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(v, e) {
|
||||||
|
t.Fatalf("expected:\n%s\n\nactual:\n%s", expected, string(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,8 @@ package merge
|
|||||||
const priorityKey string = "_priority"
|
const priorityKey string = "_priority"
|
||||||
const tagKey string = "_tag"
|
const tagKey string = "_tag"
|
||||||
|
|
||||||
func applyRules(m map[string]interface{}) error {
|
// ApplyRules applies merge rules according to _tag, _priority fields, and remove them
|
||||||
|
func ApplyRules(m map[string]interface{}) error {
|
||||||
err := sortMergeSlices(m)
|
err := sortMergeSlices(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package json
|
package mergers
|
||||||
|
|
||||||
import "github.com/v2fly/v2ray-core/v4/common/errors"
|
import "github.com/v2fly/v2ray-core/v4/common/errors"
|
||||||
|
|
25
infra/conf/mergers/extensions.go
Normal file
25
infra/conf/mergers/extensions.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package mergers
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// GetExtensions get extensions of given format
|
||||||
|
func GetExtensions(formatName string) ([]string, error) {
|
||||||
|
lowerName := strings.ToLower(formatName)
|
||||||
|
if lowerName == "auto" {
|
||||||
|
return GetAllExtensions(), nil
|
||||||
|
}
|
||||||
|
f, found := mergeLoaderByName[lowerName]
|
||||||
|
if !found {
|
||||||
|
return nil, newError(formatName+" not found", formatName).AtWarning()
|
||||||
|
}
|
||||||
|
return f.Extensions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllExtensions get all extensions supported
|
||||||
|
func GetAllExtensions() []string {
|
||||||
|
extensions := make([]string, 0)
|
||||||
|
for _, f := range mergeLoaderByName {
|
||||||
|
extensions = append(extensions, f.Extensions...)
|
||||||
|
}
|
||||||
|
return extensions
|
||||||
|
}
|
40
infra/conf/mergers/formats.go
Normal file
40
infra/conf/mergers/formats.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package mergers
|
||||||
|
|
||||||
|
//go:generate go run github.com/v2fly/v2ray-core/v4/common/errors/errorgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MergeableFormat is a configurable mergeable format of V2Ray config file.
|
||||||
|
type MergeableFormat struct {
|
||||||
|
Name string
|
||||||
|
Extensions []string
|
||||||
|
Loader MergeLoader
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeLoader is a utility to merge V2Ray config from external source into a map and returns it.
|
||||||
|
type MergeLoader func(input interface{}, m map[string]interface{}) error
|
||||||
|
|
||||||
|
var (
|
||||||
|
mergeLoaderByName = make(map[string]*MergeableFormat)
|
||||||
|
mergeLoaderByExt = make(map[string]*MergeableFormat)
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterMergeLoader add a new MergeLoader.
|
||||||
|
func RegisterMergeLoader(format *MergeableFormat) error {
|
||||||
|
if _, found := mergeLoaderByName[format.Name]; found {
|
||||||
|
return newError(format.Name, " already registered.")
|
||||||
|
}
|
||||||
|
mergeLoaderByName[format.Name] = format
|
||||||
|
|
||||||
|
for _, ext := range format.Extensions {
|
||||||
|
lext := strings.ToLower(ext)
|
||||||
|
if f, found := mergeLoaderByExt[lext]; found {
|
||||||
|
return newError(ext, " already registered to ", f.Name)
|
||||||
|
}
|
||||||
|
mergeLoaderByExt[lext] = format
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
95
infra/conf/mergers/merge.go
Normal file
95
infra/conf/mergers/merge.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package mergers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
core "github.com/v2fly/v2ray-core/v4"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MergeAs load input and merge as specified format into m
|
||||||
|
func MergeAs(formatName string, input interface{}, m map[string]interface{}) error {
|
||||||
|
f, found := mergeLoaderByName[formatName]
|
||||||
|
if !found {
|
||||||
|
return newError("format loader not found for: ", formatName)
|
||||||
|
}
|
||||||
|
return f.Loader(input, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge loads inputs and merges them into m
|
||||||
|
// it detects extension for loader selecting, or try all loaders
|
||||||
|
// if no extension found
|
||||||
|
func Merge(input interface{}, m map[string]interface{}) error {
|
||||||
|
switch v := input.(type) {
|
||||||
|
case string:
|
||||||
|
err := mergeSingleFile(v, m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case []string:
|
||||||
|
for _, file := range v {
|
||||||
|
err := mergeSingleFile(file, m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case cmdarg.Arg:
|
||||||
|
for _, file := range v {
|
||||||
|
err := mergeSingleFile(file, m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case []byte:
|
||||||
|
err := mergeSingleFile(v, m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case io.Reader:
|
||||||
|
// read to []byte incase it tries different loaders
|
||||||
|
bs, err := ioutil.ReadAll(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = mergeSingleFile(bs, m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return newError("unknow merge input type")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeSingleFile(input interface{}, m map[string]interface{}) error {
|
||||||
|
if file, ok := input.(string); ok {
|
||||||
|
ext := getExtension(file)
|
||||||
|
if ext != "" {
|
||||||
|
lext := strings.ToLower(ext)
|
||||||
|
f, found := mergeLoaderByExt[lext]
|
||||||
|
if !found {
|
||||||
|
return newError("unmergeable format extension: ", ext)
|
||||||
|
}
|
||||||
|
return f.Loader(file, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// no extension, try all loaders
|
||||||
|
for _, f := range mergeLoaderByName {
|
||||||
|
if f.Name == core.FormatAuto {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := f.Loader(input, m)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newError("tried all loaders but failed for: ", input).AtWarning()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getExtension(filename string) string {
|
||||||
|
ext := filepath.Ext(filename)
|
||||||
|
return strings.ToLower(ext)
|
||||||
|
}
|
103
infra/conf/mergers/merger_base.go
Normal file
103
infra/conf/mergers/merger_base.go
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package mergers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/merge"
|
||||||
|
)
|
||||||
|
|
||||||
|
type jsonConverter func(v []byte) ([]byte, error)
|
||||||
|
|
||||||
|
func makeLoader(name string, extensions []string, converter jsonConverter) *MergeableFormat {
|
||||||
|
return &MergeableFormat{
|
||||||
|
Name: name,
|
||||||
|
Extensions: extensions,
|
||||||
|
Loader: makeConvertToJSONLoader(converter),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeConvertToJSONLoader(converter func(v []byte) ([]byte, error)) MergeLoader {
|
||||||
|
return func(input interface{}, target map[string]interface{}) error {
|
||||||
|
if target == nil {
|
||||||
|
panic("merge target is nil")
|
||||||
|
}
|
||||||
|
switch v := input.(type) {
|
||||||
|
case string:
|
||||||
|
err := loadFile(v, target, converter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case []string:
|
||||||
|
err := loadFiles(v, target, converter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case cmdarg.Arg:
|
||||||
|
err := loadFiles(v, target, converter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case []byte:
|
||||||
|
err := loadBytes(v, target, converter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case io.Reader:
|
||||||
|
err := loadReader(v, target, converter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return newError("unknow merge input type")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadFiles(files []string, target map[string]interface{}, converter func(v []byte) ([]byte, error)) error {
|
||||||
|
for _, file := range files {
|
||||||
|
err := loadFile(file, target, converter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadFile(file string, target map[string]interface{}, converter func(v []byte) ([]byte, error)) error {
|
||||||
|
bs, err := cmdarg.LoadArgToBytes(file)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("fail to load %s: %s", file, err)
|
||||||
|
}
|
||||||
|
if converter != nil {
|
||||||
|
bs, err = converter(bs)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error convert to json '%s': %s", file, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err = merge.ToMap(bs, target)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadReader(reader io.Reader, target map[string]interface{}, converter func(v []byte) ([]byte, error)) error {
|
||||||
|
bs, err := ioutil.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return loadBytes(bs, target, converter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadBytes(bs []byte, target map[string]interface{}, converter func(v []byte) ([]byte, error)) error {
|
||||||
|
var err error
|
||||||
|
if converter != nil {
|
||||||
|
bs, err = converter(bs)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("fail to convert to json: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err = merge.ToMap(bs, target)
|
||||||
|
return err
|
||||||
|
}
|
32
infra/conf/mergers/mergers.go
Normal file
32
infra/conf/mergers/mergers.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package mergers
|
||||||
|
|
||||||
|
import (
|
||||||
|
core "github.com/v2fly/v2ray-core/v4"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/common"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.Must(RegisterMergeLoader(makeLoader(
|
||||||
|
core.FormatJSON,
|
||||||
|
[]string{".json", ".jsonc"},
|
||||||
|
nil,
|
||||||
|
)))
|
||||||
|
common.Must(RegisterMergeLoader(makeLoader(
|
||||||
|
core.FormatTOML,
|
||||||
|
[]string{".toml"},
|
||||||
|
json.FromTOML,
|
||||||
|
)))
|
||||||
|
common.Must(RegisterMergeLoader(makeLoader(
|
||||||
|
core.FormatYAML,
|
||||||
|
[]string{".yml", ".yaml"},
|
||||||
|
json.FromYAML,
|
||||||
|
)))
|
||||||
|
common.Must(RegisterMergeLoader(
|
||||||
|
&MergeableFormat{
|
||||||
|
Name: core.FormatAuto,
|
||||||
|
Extensions: nil,
|
||||||
|
Loader: Merge,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
10
infra/conf/mergers/names.go
Normal file
10
infra/conf/mergers/names.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package mergers
|
||||||
|
|
||||||
|
// GetAllNames get names of all formats
|
||||||
|
func GetAllNames() []string {
|
||||||
|
names := make([]string, 0)
|
||||||
|
for _, f := range mergeLoaderByName {
|
||||||
|
names = append(names, f.Name)
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
@ -7,14 +7,12 @@ import (
|
|||||||
// CmdAPI calls an API in an V2Ray process
|
// CmdAPI calls an API in an V2Ray process
|
||||||
var CmdAPI = &base.Command{
|
var CmdAPI = &base.Command{
|
||||||
UsageLine: "{{.Exec}} api",
|
UsageLine: "{{.Exec}} api",
|
||||||
Short: "Call V2Ray API",
|
Short: "call V2Ray API",
|
||||||
Long: `{{.Exec}} {{.LongName}} provides tools to manipulate V2Ray via its API.
|
Long: `{{.Exec}} {{.LongName}} provides tools to manipulate V2Ray via its API.
|
||||||
`,
|
`,
|
||||||
Commands: []*base.Command{
|
Commands: []*base.Command{
|
||||||
cmdRestartLogger,
|
cmdLog,
|
||||||
cmdGetStats,
|
cmdStats,
|
||||||
cmdQueryStats,
|
|
||||||
cmdSysStats,
|
|
||||||
cmdBalancerCheck,
|
cmdBalancerCheck,
|
||||||
cmdBalancerInfo,
|
cmdBalancerInfo,
|
||||||
cmdBalancerOverride,
|
cmdBalancerOverride,
|
||||||
|
@ -18,10 +18,10 @@ of server config.
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
||||||
-s, -server
|
-s, -server <server:port>
|
||||||
The API server address. Default 127.0.0.1:8080
|
The API server address. Default 127.0.0.1:8080
|
||||||
|
|
||||||
-t, -timeout
|
-t, -timeout <seconds>
|
||||||
Timeout seconds to call API. Default 3
|
Timeout seconds to call API. Default 3
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/v2fly/v2ray-core/v4/main/commands/base"
|
"github.com/v2fly/v2ray-core/v4/main/commands/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: support "-json" flag for json output
|
||||||
var cmdBalancerInfo = &base.Command{
|
var cmdBalancerInfo = &base.Command{
|
||||||
CustomFlags: true,
|
CustomFlags: true,
|
||||||
UsageLine: "{{.Exec}} api bi [--server=127.0.0.1:8080] [balancer]...",
|
UsageLine: "{{.Exec}} api bi [--server=127.0.0.1:8080] [balancer]...",
|
||||||
@ -24,10 +25,13 @@ of server config.
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
||||||
-s, -server
|
-json
|
||||||
|
Use json output.
|
||||||
|
|
||||||
|
-s, -server <server:port>
|
||||||
The API server address. Default 127.0.0.1:8080
|
The API server address. Default 127.0.0.1:8080
|
||||||
|
|
||||||
-t, -timeout
|
-t, -timeout <seconds>
|
||||||
Timeout seconds to call API. Default 3
|
Timeout seconds to call API. Default 3
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
@ -53,12 +57,17 @@ func executeBalancerInfo(cmd *base.Command, args []string) {
|
|||||||
sort.Slice(resp.Balancers, func(i, j int) bool {
|
sort.Slice(resp.Balancers, func(i, j int) bool {
|
||||||
return resp.Balancers[i].Tag < resp.Balancers[j].Tag
|
return resp.Balancers[i].Tag < resp.Balancers[j].Tag
|
||||||
})
|
})
|
||||||
|
if apiJSON {
|
||||||
|
showJSONResponse(resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
for _, b := range resp.Balancers {
|
for _, b := range resp.Balancers {
|
||||||
showBalancerInfo(b)
|
showBalancerInfo(b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func showBalancerInfo(b *routerService.BalancerMsg) {
|
func showBalancerInfo(b *routerService.BalancerMsg) {
|
||||||
|
const tableIndent = 4
|
||||||
sb := new(strings.Builder)
|
sb := new(strings.Builder)
|
||||||
// Balancer
|
// Balancer
|
||||||
sb.WriteString(fmt.Sprintf("Balancer: %s\n", b.Tag))
|
sb.WriteString(fmt.Sprintf("Balancer: %s\n", b.Tag))
|
||||||
@ -71,25 +80,28 @@ func showBalancerInfo(b *routerService.BalancerMsg) {
|
|||||||
if b.Override != nil {
|
if b.Override != nil {
|
||||||
sb.WriteString(" - Selecting Override:\n")
|
sb.WriteString(" - Selecting Override:\n")
|
||||||
until := fmt.Sprintf("until: %s", b.Override.Until)
|
until := fmt.Sprintf("until: %s", b.Override.Until)
|
||||||
writeRow(sb, 0, nil, nil, until)
|
writeRow(sb, tableIndent, 0, []string{until}, nil)
|
||||||
for i, s := range b.Override.Selects {
|
for i, s := range b.Override.Selects {
|
||||||
writeRow(sb, i+1, nil, nil, s)
|
writeRow(sb, tableIndent, i+1, []string{s}, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
b.Titles = append(b.Titles, "Tag")
|
||||||
formats := getColumnFormats(b.Titles)
|
formats := getColumnFormats(b.Titles)
|
||||||
// Selects
|
// Selects
|
||||||
sb.WriteString(" - Selects:\n")
|
sb.WriteString(" - Selects:\n")
|
||||||
writeRow(sb, 0, b.Titles, formats, "Tag")
|
writeRow(sb, tableIndent, 0, b.Titles, formats)
|
||||||
for i, o := range b.Selects {
|
for i, o := range b.Selects {
|
||||||
writeRow(sb, i+1, o.Values, formats, o.Tag)
|
o.Values = append(o.Values, o.Tag)
|
||||||
|
writeRow(sb, tableIndent, i+1, o.Values, formats)
|
||||||
}
|
}
|
||||||
// Others
|
// Others
|
||||||
scnt := len(b.Selects)
|
scnt := len(b.Selects)
|
||||||
if len(b.Others) > 0 {
|
if len(b.Others) > 0 {
|
||||||
sb.WriteString(" - Others:\n")
|
sb.WriteString(" - Others:\n")
|
||||||
writeRow(sb, 0, b.Titles, formats, "Tag")
|
writeRow(sb, tableIndent, 0, b.Titles, formats)
|
||||||
for i, o := range b.Others {
|
for i, o := range b.Others {
|
||||||
writeRow(sb, scnt+i+1, o.Values, formats, o.Tag)
|
o.Values = append(o.Values, o.Tag)
|
||||||
|
writeRow(sb, tableIndent, scnt+i+1, o.Values, formats)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
os.Stdout.WriteString(sb.String())
|
os.Stdout.WriteString(sb.String())
|
||||||
@ -103,12 +115,12 @@ func getColumnFormats(titles []string) []string {
|
|||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeRow(sb *strings.Builder, index int, values, formats []string, tag string) {
|
func writeRow(sb *strings.Builder, indent, index int, values, formats []string) {
|
||||||
if index == 0 {
|
if index == 0 {
|
||||||
// title line
|
// title line
|
||||||
sb.WriteString(" ")
|
sb.WriteString(strings.Repeat(" ", indent+4))
|
||||||
} else {
|
} else {
|
||||||
sb.WriteString(fmt.Sprintf(" %-4d", index))
|
sb.WriteString(fmt.Sprintf("%s%-4d", strings.Repeat(" ", indent), index))
|
||||||
}
|
}
|
||||||
for i, v := range values {
|
for i, v := range values {
|
||||||
format := "%-14s"
|
format := "%-14s"
|
||||||
@ -117,6 +129,5 @@ func writeRow(sb *strings.Builder, index int, values, formats []string, tag stri
|
|||||||
}
|
}
|
||||||
sb.WriteString(fmt.Sprintf(format, v))
|
sb.WriteString(fmt.Sprintf(format, v))
|
||||||
}
|
}
|
||||||
sb.WriteString(tag)
|
|
||||||
sb.WriteByte('\n')
|
sb.WriteByte('\n')
|
||||||
}
|
}
|
||||||
|
@ -10,14 +10,14 @@ import (
|
|||||||
var cmdBalancerOverride = &base.Command{
|
var cmdBalancerOverride = &base.Command{
|
||||||
CustomFlags: true,
|
CustomFlags: true,
|
||||||
UsageLine: "{{.Exec}} api bo [--server=127.0.0.1:8080] <-b balancer> selectors...",
|
UsageLine: "{{.Exec}} api bo [--server=127.0.0.1:8080] <-b balancer> selectors...",
|
||||||
Short: "balancer select override",
|
Short: "balancer override",
|
||||||
Long: `
|
Long: `
|
||||||
Override a balancer's selecting in a duration of time.
|
Override a balancer's selection in a duration of time.
|
||||||
|
|
||||||
> Make sure you have "RoutingService" set in "config.api.services"
|
> Make sure you have "RoutingService" set in "config.api.services"
|
||||||
of server config.
|
of server config.
|
||||||
|
|
||||||
Once a balancer's selecting is overridden:
|
Once a balancer's selection is overridden:
|
||||||
|
|
||||||
- The selectors of the balancer won't apply.
|
- The selectors of the balancer won't apply.
|
||||||
- The strategy of the balancer stops selecting qualified nodes
|
- The strategy of the balancer stops selecting qualified nodes
|
||||||
@ -25,19 +25,20 @@ Once a balancer's selecting is overridden:
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
||||||
|
-b, -balancer <tag>
|
||||||
|
Tag of the target balancer. Required.
|
||||||
|
|
||||||
|
-v, -validity <duration>
|
||||||
|
Time duration of the validity of override, e.g.: 60s, 60m,
|
||||||
|
24h, 1m30s. Default 1h.
|
||||||
|
|
||||||
-r, -remove
|
-r, -remove
|
||||||
Remove the overridden
|
Remove the override
|
||||||
|
|
||||||
-b, -balancer
|
-s, -server <server:port>
|
||||||
Tag of the balancer. Required
|
|
||||||
|
|
||||||
-v, -validity
|
|
||||||
Time minutes of the validity of overridden. Default 60
|
|
||||||
|
|
||||||
-s, -server
|
|
||||||
The API server address. Default 127.0.0.1:8080
|
The API server address. Default 127.0.0.1:8080
|
||||||
|
|
||||||
-t, -timeout
|
-t, -timeout <seconds>
|
||||||
Timeout seconds to call API. Default 3
|
Timeout seconds to call API. Default 3
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
@ -51,13 +52,13 @@ Example:
|
|||||||
func executeBalancerOverride(cmd *base.Command, args []string) {
|
func executeBalancerOverride(cmd *base.Command, args []string) {
|
||||||
var (
|
var (
|
||||||
balancer string
|
balancer string
|
||||||
validity int64
|
validity time.Duration
|
||||||
remove bool
|
remove bool
|
||||||
)
|
)
|
||||||
cmd.Flag.StringVar(&balancer, "b", "", "")
|
cmd.Flag.StringVar(&balancer, "b", "", "")
|
||||||
cmd.Flag.StringVar(&balancer, "balancer", "", "")
|
cmd.Flag.StringVar(&balancer, "balancer", "", "")
|
||||||
cmd.Flag.Int64Var(&validity, "v", 60, "")
|
cmd.Flag.DurationVar(&validity, "v", time.Hour, "")
|
||||||
cmd.Flag.Int64Var(&validity, "validity", 60, "")
|
cmd.Flag.DurationVar(&validity, "validity", time.Hour, "")
|
||||||
cmd.Flag.BoolVar(&remove, "r", false, "")
|
cmd.Flag.BoolVar(&remove, "r", false, "")
|
||||||
cmd.Flag.BoolVar(&remove, "remove", false, "")
|
cmd.Flag.BoolVar(&remove, "remove", false, "")
|
||||||
setSharedFlags(cmd)
|
setSharedFlags(cmd)
|
||||||
@ -72,7 +73,7 @@ func executeBalancerOverride(cmd *base.Command, args []string) {
|
|||||||
|
|
||||||
v := int64(0)
|
v := int64(0)
|
||||||
if !remove {
|
if !remove {
|
||||||
v = int64(time.Duration(validity) * time.Minute)
|
v = int64(validity)
|
||||||
}
|
}
|
||||||
client := routerService.NewRoutingServiceClient(conn)
|
client := routerService.NewRoutingServiceClient(conn)
|
||||||
r := &routerService.OverrideSelectingRequest{
|
r := &routerService.OverrideSelectingRequest{
|
||||||
@ -82,6 +83,6 @@ func executeBalancerOverride(cmd *base.Command, args []string) {
|
|||||||
}
|
}
|
||||||
_, err := client.OverrideSelecting(ctx, r)
|
_, err := client.OverrideSelecting(ctx, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf("failed to perform balancer health checks: %s", err)
|
base.Fatalf("failed to override balancer: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,25 +4,31 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
handlerService "github.com/v2fly/v2ray-core/v4/app/proxyman/command"
|
handlerService "github.com/v2fly/v2ray-core/v4/app/proxyman/command"
|
||||||
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/infra/conf"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/main/commands/base"
|
"github.com/v2fly/v2ray-core/v4/main/commands/base"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/main/commands/helpers"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdAddInbounds = &base.Command{
|
var cmdAddInbounds = &base.Command{
|
||||||
CustomFlags: true,
|
CustomFlags: true,
|
||||||
UsageLine: "{{.Exec}} api adi [--server=127.0.0.1:8080] <c1.json> [c2.json]...",
|
UsageLine: "{{.Exec}} api adi [--server=127.0.0.1:8080] <c1.json> [c2.json]...",
|
||||||
Short: "Add inbounds",
|
Short: "add inbounds",
|
||||||
Long: `
|
Long: `
|
||||||
Add inbounds to V2Ray.
|
Add inbounds to V2Ray.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
||||||
-s, -server
|
-format <format>
|
||||||
|
Specify the input format.
|
||||||
|
Available values: "auto", "json", "toml", "yaml"
|
||||||
|
Default: "auto"
|
||||||
|
|
||||||
|
-r
|
||||||
|
Load folders recursively.
|
||||||
|
|
||||||
|
-s, -server <server:port>
|
||||||
The API server address. Default 127.0.0.1:8080
|
The API server address. Default 127.0.0.1:8080
|
||||||
|
|
||||||
-t, -timeout
|
-t, -timeout <seconds>
|
||||||
Timeout seconds to call API. Default 3
|
Timeout seconds to call API. Default 3
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
@ -34,26 +40,13 @@ Example:
|
|||||||
|
|
||||||
func executeAddInbounds(cmd *base.Command, args []string) {
|
func executeAddInbounds(cmd *base.Command, args []string) {
|
||||||
setSharedFlags(cmd)
|
setSharedFlags(cmd)
|
||||||
|
setSharedConfigFlags(cmd)
|
||||||
cmd.Flag.Parse(args)
|
cmd.Flag.Parse(args)
|
||||||
unnamedArgs := cmd.Flag.Args()
|
c, err := helpers.LoadConfig(cmd.Flag.Args(), apiConfigFormat, apiConfigRecursively)
|
||||||
if len(unnamedArgs) == 0 {
|
if err != nil {
|
||||||
fmt.Println("reading from stdin:")
|
base.Fatalf("%s", err)
|
||||||
unnamedArgs = []string{"stdin:"}
|
|
||||||
}
|
}
|
||||||
|
if len(c.InboundConfigs) == 0 {
|
||||||
ins := make([]conf.InboundDetourConfig, 0)
|
|
||||||
for _, arg := range unnamedArgs {
|
|
||||||
r, err := cmdarg.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")
|
base.Fatalf("no valid inbound found")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +54,7 @@ func executeAddInbounds(cmd *base.Command, args []string) {
|
|||||||
defer close()
|
defer close()
|
||||||
|
|
||||||
client := handlerService.NewHandlerServiceClient(conn)
|
client := handlerService.NewHandlerServiceClient(conn)
|
||||||
for _, in := range ins {
|
for _, in := range c.InboundConfigs {
|
||||||
fmt.Println("adding:", in.Tag)
|
fmt.Println("adding:", in.Tag)
|
||||||
i, err := in.Build()
|
i, err := in.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -70,10 +63,9 @@ func executeAddInbounds(cmd *base.Command, args []string) {
|
|||||||
r := &handlerService.AddInboundRequest{
|
r := &handlerService.AddInboundRequest{
|
||||||
Inbound: i,
|
Inbound: i,
|
||||||
}
|
}
|
||||||
resp, err := client.AddInbound(ctx, r)
|
_, err = client.AddInbound(ctx, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf("failed to add inbound: %s", err)
|
base.Fatalf("failed to add inbound: %s", err)
|
||||||
}
|
}
|
||||||
showResponese(resp)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,24 +4,31 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
handlerService "github.com/v2fly/v2ray-core/v4/app/proxyman/command"
|
handlerService "github.com/v2fly/v2ray-core/v4/app/proxyman/command"
|
||||||
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/main/commands/base"
|
"github.com/v2fly/v2ray-core/v4/main/commands/base"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/main/commands/helpers"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdRemoveInbounds = &base.Command{
|
var cmdRemoveInbounds = &base.Command{
|
||||||
CustomFlags: true,
|
CustomFlags: true,
|
||||||
UsageLine: "{{.Exec}} api rmi [--server=127.0.0.1:8080] <json_file|tag> [json_file] [tag]...",
|
UsageLine: "{{.Exec}} api rmi [--server=127.0.0.1:8080] <json_file|tag> [json_file] [tag]...",
|
||||||
Short: "Remove inbounds",
|
Short: "remove inbounds",
|
||||||
Long: `
|
Long: `
|
||||||
Remove inbounds from V2Ray.
|
Remove inbounds from V2Ray.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
||||||
-s, -server
|
-format <format>
|
||||||
|
Specify the input format.
|
||||||
|
Available values: "auto", "json", "toml", "yaml"
|
||||||
|
Default: "auto"
|
||||||
|
|
||||||
|
-r
|
||||||
|
Load folders recursively.
|
||||||
|
|
||||||
|
-s, -server <server:port>
|
||||||
The API server address. Default 127.0.0.1:8080
|
The API server address. Default 127.0.0.1:8080
|
||||||
|
|
||||||
-t, -timeout
|
-t, -timeout <seconds>
|
||||||
Timeout seconds to call API. Default 3
|
Timeout seconds to call API. Default 3
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
@ -33,48 +40,28 @@ Example:
|
|||||||
|
|
||||||
func executeRemoveInbounds(cmd *base.Command, args []string) {
|
func executeRemoveInbounds(cmd *base.Command, args []string) {
|
||||||
setSharedFlags(cmd)
|
setSharedFlags(cmd)
|
||||||
|
setSharedConfigFlags(cmd)
|
||||||
cmd.Flag.Parse(args)
|
cmd.Flag.Parse(args)
|
||||||
unnamedArgs := cmd.Flag.Args()
|
c, err := helpers.LoadConfig(cmd.Flag.Args(), apiConfigFormat, apiConfigRecursively)
|
||||||
if len(unnamedArgs) == 0 {
|
if err != nil {
|
||||||
fmt.Println("reading from stdin:")
|
base.Fatalf("%s", err)
|
||||||
unnamedArgs = []string{"stdin:"}
|
|
||||||
}
|
}
|
||||||
|
if len(c.InboundConfigs) == 0 {
|
||||||
tags := make([]string, 0)
|
|
||||||
for _, arg := range unnamedArgs {
|
|
||||||
if r, err := cmdarg.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")
|
base.Fatalf("no inbound to remove")
|
||||||
}
|
}
|
||||||
fmt.Println("removing inbounds:", tags)
|
|
||||||
|
|
||||||
conn, ctx, close := dialAPIServer()
|
conn, ctx, close := dialAPIServer()
|
||||||
defer close()
|
defer close()
|
||||||
|
|
||||||
client := handlerService.NewHandlerServiceClient(conn)
|
client := handlerService.NewHandlerServiceClient(conn)
|
||||||
for _, tag := range tags {
|
for _, c := range c.InboundConfigs {
|
||||||
fmt.Println("removing:", tag)
|
fmt.Println("removing:", c.Tag)
|
||||||
r := &handlerService.RemoveInboundRequest{
|
r := &handlerService.RemoveInboundRequest{
|
||||||
Tag: tag,
|
Tag: c.Tag,
|
||||||
}
|
}
|
||||||
resp, err := client.RemoveInbound(ctx, r)
|
_, err := client.RemoveInbound(ctx, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf("failed to remove inbound: %s", err)
|
base.Fatalf("failed to remove inbound: %s", err)
|
||||||
}
|
}
|
||||||
showResponese(resp)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
83
main/commands/all/api/log.go
Normal file
83
main/commands/all/api/log.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
logService "github.com/v2fly/v2ray-core/v4/app/log/command"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/main/commands/base"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdLog = &base.Command{
|
||||||
|
CustomFlags: true,
|
||||||
|
UsageLine: "{{.Exec}} api log [--server=127.0.0.1:8080]",
|
||||||
|
Short: "log operations",
|
||||||
|
Long: `
|
||||||
|
Follow and print logs from v2ray.
|
||||||
|
|
||||||
|
> It ignores -timeout flag while following logs
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
-restart
|
||||||
|
Restart the logger
|
||||||
|
|
||||||
|
-s, -server <server:port>
|
||||||
|
The API server address. Default 127.0.0.1:8080
|
||||||
|
|
||||||
|
-t, -timeout <seconds>
|
||||||
|
Timeout seconds to call API. Default 3
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{.Exec}} {{.LongName}}
|
||||||
|
{{.Exec}} {{.LongName}} --restart
|
||||||
|
`,
|
||||||
|
Run: executeRestartLogger,
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeRestartLogger(cmd *base.Command, args []string) {
|
||||||
|
var restart bool
|
||||||
|
cmd.Flag.BoolVar(&restart, "restart", false, "")
|
||||||
|
setSharedFlags(cmd)
|
||||||
|
cmd.Flag.Parse(args)
|
||||||
|
|
||||||
|
if restart {
|
||||||
|
restartLogger()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
followLogger()
|
||||||
|
}
|
||||||
|
|
||||||
|
func restartLogger() {
|
||||||
|
conn, ctx, close := dialAPIServer()
|
||||||
|
defer close()
|
||||||
|
client := logService.NewLoggerServiceClient(conn)
|
||||||
|
r := &logService.RestartLoggerRequest{}
|
||||||
|
_, err := client.RestartLogger(ctx, r)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to restart logger: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func followLogger() {
|
||||||
|
conn, ctx, close := dialAPIServerWithoutTimeout()
|
||||||
|
defer close()
|
||||||
|
client := logService.NewLoggerServiceClient(conn)
|
||||||
|
r := &logService.FollowLogRequest{}
|
||||||
|
stream, err := client.FollowLog(ctx, r)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to follow logger: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
resp, err := stream.Recv()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to fetch log: %s", err)
|
||||||
|
}
|
||||||
|
log.Print(resp.Message)
|
||||||
|
}
|
||||||
|
}
|
@ -1,40 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
logService "github.com/v2fly/v2ray-core/v4/app/log/command"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/main/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)
|
|
||||||
}
|
|
@ -4,25 +4,31 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
handlerService "github.com/v2fly/v2ray-core/v4/app/proxyman/command"
|
handlerService "github.com/v2fly/v2ray-core/v4/app/proxyman/command"
|
||||||
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/infra/conf"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/main/commands/base"
|
"github.com/v2fly/v2ray-core/v4/main/commands/base"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/main/commands/helpers"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdAddOutbounds = &base.Command{
|
var cmdAddOutbounds = &base.Command{
|
||||||
CustomFlags: true,
|
CustomFlags: true,
|
||||||
UsageLine: "{{.Exec}} api ado [--server=127.0.0.1:8080] <c1.json> [c2.json]...",
|
UsageLine: "{{.Exec}} api ado [--server=127.0.0.1:8080] <c1.json> [c2.json]...",
|
||||||
Short: "Add outbounds",
|
Short: "add outbounds",
|
||||||
Long: `
|
Long: `
|
||||||
Add outbounds to V2Ray.
|
Add outbounds to V2Ray.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
||||||
-s, -server
|
-format <format>
|
||||||
|
Specify the input format.
|
||||||
|
Available values: "auto", "json", "toml", "yaml"
|
||||||
|
Default: "auto"
|
||||||
|
|
||||||
|
-r
|
||||||
|
Load folders recursively.
|
||||||
|
|
||||||
|
-s, -server <server:port>
|
||||||
The API server address. Default 127.0.0.1:8080
|
The API server address. Default 127.0.0.1:8080
|
||||||
|
|
||||||
-t, -timeout
|
-t, -timeout <seconds>
|
||||||
Timeout seconds to call API. Default 3
|
Timeout seconds to call API. Default 3
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
@ -34,26 +40,13 @@ Example:
|
|||||||
|
|
||||||
func executeAddOutbounds(cmd *base.Command, args []string) {
|
func executeAddOutbounds(cmd *base.Command, args []string) {
|
||||||
setSharedFlags(cmd)
|
setSharedFlags(cmd)
|
||||||
|
setSharedConfigFlags(cmd)
|
||||||
cmd.Flag.Parse(args)
|
cmd.Flag.Parse(args)
|
||||||
unnamedArgs := cmd.Flag.Args()
|
c, err := helpers.LoadConfig(cmd.Flag.Args(), apiConfigFormat, apiConfigRecursively)
|
||||||
if len(unnamedArgs) == 0 {
|
if err != nil {
|
||||||
fmt.Println("Reading from STDIN")
|
base.Fatalf("%s", err)
|
||||||
unnamedArgs = []string{"stdin:"}
|
|
||||||
}
|
}
|
||||||
|
if len(c.OutboundConfigs) == 0 {
|
||||||
outs := make([]conf.OutboundDetourConfig, 0)
|
|
||||||
for _, arg := range unnamedArgs {
|
|
||||||
r, err := cmdarg.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")
|
base.Fatalf("no valid outbound found")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +54,7 @@ func executeAddOutbounds(cmd *base.Command, args []string) {
|
|||||||
defer close()
|
defer close()
|
||||||
|
|
||||||
client := handlerService.NewHandlerServiceClient(conn)
|
client := handlerService.NewHandlerServiceClient(conn)
|
||||||
for _, out := range outs {
|
for _, out := range c.OutboundConfigs {
|
||||||
fmt.Println("adding:", out.Tag)
|
fmt.Println("adding:", out.Tag)
|
||||||
o, err := out.Build()
|
o, err := out.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -70,10 +63,9 @@ func executeAddOutbounds(cmd *base.Command, args []string) {
|
|||||||
r := &handlerService.AddOutboundRequest{
|
r := &handlerService.AddOutboundRequest{
|
||||||
Outbound: o,
|
Outbound: o,
|
||||||
}
|
}
|
||||||
resp, err := client.AddOutbound(ctx, r)
|
_, err = client.AddOutbound(ctx, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf("failed to add outbound: %s", err)
|
base.Fatalf("failed to add outbound: %s", err)
|
||||||
}
|
}
|
||||||
showResponese(resp)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,24 +4,31 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
handlerService "github.com/v2fly/v2ray-core/v4/app/proxyman/command"
|
handlerService "github.com/v2fly/v2ray-core/v4/app/proxyman/command"
|
||||||
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/main/commands/base"
|
"github.com/v2fly/v2ray-core/v4/main/commands/base"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/main/commands/helpers"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdRemoveOutbounds = &base.Command{
|
var cmdRemoveOutbounds = &base.Command{
|
||||||
CustomFlags: true,
|
CustomFlags: true,
|
||||||
UsageLine: "{{.Exec}} api rmo [--server=127.0.0.1:8080] <json_file|tag> [json_file] [tag]...",
|
UsageLine: "{{.Exec}} api rmo [--server=127.0.0.1:8080] <json_file|tag> [json_file] [tag]...",
|
||||||
Short: "Remove outbounds",
|
Short: "remove outbounds",
|
||||||
Long: `
|
Long: `
|
||||||
Remove outbounds from V2Ray.
|
Remove outbounds from V2Ray.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
||||||
-s, -server
|
-format <format>
|
||||||
|
Specify the input format.
|
||||||
|
Available values: "auto", "json", "toml", "yaml"
|
||||||
|
Default: "auto"
|
||||||
|
|
||||||
|
-r
|
||||||
|
Load folders recursively.
|
||||||
|
|
||||||
|
-s, -server <server:port>
|
||||||
The API server address. Default 127.0.0.1:8080
|
The API server address. Default 127.0.0.1:8080
|
||||||
|
|
||||||
-t, -timeout
|
-t, -timeout <seconds>
|
||||||
Timeout seconds to call API. Default 3
|
Timeout seconds to call API. Default 3
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
@ -34,30 +41,12 @@ Example:
|
|||||||
func executeRemoveOutbounds(cmd *base.Command, args []string) {
|
func executeRemoveOutbounds(cmd *base.Command, args []string) {
|
||||||
setSharedFlags(cmd)
|
setSharedFlags(cmd)
|
||||||
cmd.Flag.Parse(args)
|
cmd.Flag.Parse(args)
|
||||||
unnamedArgs := cmd.Flag.Args()
|
setSharedConfigFlags(cmd)
|
||||||
if len(unnamedArgs) == 0 {
|
c, err := helpers.LoadConfig(cmd.Flag.Args(), apiConfigFormat, apiConfigRecursively)
|
||||||
fmt.Println("reading from stdin:")
|
if err != nil {
|
||||||
unnamedArgs = []string{"stdin:"}
|
base.Fatalf("%s", err)
|
||||||
}
|
}
|
||||||
|
if len(c.OutboundConfigs) == 0 {
|
||||||
tags := make([]string, 0)
|
|
||||||
for _, arg := range unnamedArgs {
|
|
||||||
if r, err := cmdarg.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")
|
base.Fatalf("no outbound to remove")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,15 +54,14 @@ func executeRemoveOutbounds(cmd *base.Command, args []string) {
|
|||||||
defer close()
|
defer close()
|
||||||
|
|
||||||
client := handlerService.NewHandlerServiceClient(conn)
|
client := handlerService.NewHandlerServiceClient(conn)
|
||||||
for _, tag := range tags {
|
for _, c := range c.OutboundConfigs {
|
||||||
fmt.Println("removing:", tag)
|
fmt.Println("removing:", c.Tag)
|
||||||
r := &handlerService.RemoveOutboundRequest{
|
r := &handlerService.RemoveOutboundRequest{
|
||||||
Tag: tag,
|
Tag: c.Tag,
|
||||||
}
|
}
|
||||||
resp, err := client.RemoveOutbound(ctx, r)
|
_, err := client.RemoveOutbound(ctx, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf("failed to remove outbound: %s", err)
|
base.Fatalf("failed to remove outbound: %s", err)
|
||||||
}
|
}
|
||||||
showResponese(resp)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,19 +5,22 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
|
||||||
|
core "github.com/v2fly/v2ray-core/v4"
|
||||||
"github.com/v2fly/v2ray-core/v4/main/commands/base"
|
"github.com/v2fly/v2ray-core/v4/main/commands/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
apiServerAddrPtr string
|
apiServerAddrPtr string
|
||||||
apiTimeout int
|
apiTimeout int
|
||||||
|
apiJSON bool
|
||||||
|
apiConfigFormat string
|
||||||
|
apiConfigRecursively bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func setSharedFlags(cmd *base.Command) {
|
func setSharedFlags(cmd *base.Command) {
|
||||||
@ -25,14 +28,17 @@ func setSharedFlags(cmd *base.Command) {
|
|||||||
cmd.Flag.StringVar(&apiServerAddrPtr, "server", "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, "t", 3, "")
|
||||||
cmd.Flag.IntVar(&apiTimeout, "timeout", 3, "")
|
cmd.Flag.IntVar(&apiTimeout, "timeout", 3, "")
|
||||||
|
cmd.Flag.BoolVar(&apiJSON, "json", false, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSharedConfigFlags(cmd *base.Command) {
|
||||||
|
cmd.Flag.StringVar(&apiConfigFormat, "format", core.FormatAuto, "")
|
||||||
|
cmd.Flag.BoolVar(&apiConfigRecursively, "r", false, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func dialAPIServer() (conn *grpc.ClientConn, ctx context.Context, close func()) {
|
func dialAPIServer() (conn *grpc.ClientConn, ctx context.Context, close func()) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(apiTimeout)*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(apiTimeout)*time.Second)
|
||||||
conn, err := grpc.DialContext(ctx, apiServerAddrPtr, grpc.WithInsecure(), grpc.WithBlock())
|
conn = dialAPIServerWithContext(ctx)
|
||||||
if err != nil {
|
|
||||||
base.Fatalf("failed to dial %s", apiServerAddrPtr)
|
|
||||||
}
|
|
||||||
close = func() {
|
close = func() {
|
||||||
cancel()
|
cancel()
|
||||||
conn.Close()
|
conn.Close()
|
||||||
@ -40,56 +46,40 @@ func dialAPIServer() (conn *grpc.ClientConn, ctx context.Context, close func())
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func showResponese(m proto.Message) {
|
func dialAPIServerWithoutTimeout() (conn *grpc.ClientConn, ctx context.Context, close func()) {
|
||||||
if isEmpty(m) {
|
ctx = context.Background()
|
||||||
// avoid outputs like `{}`, `{"key":{}}`
|
conn = dialAPIServerWithContext(ctx)
|
||||||
return
|
close = func() {
|
||||||
|
conn.Close()
|
||||||
}
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func dialAPIServerWithContext(ctx context.Context) (conn *grpc.ClientConn) {
|
||||||
|
conn, err := grpc.DialContext(ctx, apiServerAddrPtr, grpc.WithInsecure(), grpc.WithBlock())
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to dial %s", apiServerAddrPtr)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func protoToJSONString(m proto.Message, prefix, indent string) (string, error) {
|
||||||
b := new(strings.Builder)
|
b := new(strings.Builder)
|
||||||
e := json.NewEncoder(b)
|
e := json.NewEncoder(b)
|
||||||
e.SetIndent("", " ")
|
e.SetIndent(prefix, indent)
|
||||||
e.SetEscapeHTML(false)
|
e.SetEscapeHTML(false)
|
||||||
err := e.Encode(m)
|
err := e.Encode(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stdout, "%v\n", m)
|
return "", err
|
||||||
base.Fatalf("error encode json: %s", err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
fmt.Println(strings.TrimSpace(b.String()))
|
return strings.TrimSpace(b.String()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isEmpty checks if the response is empty (all zero values).
|
func showJSONResponse(m proto.Message) {
|
||||||
// proto.Message types always "omitempty" on fields,
|
output, err := protoToJSONString(m, "", "")
|
||||||
// there's no chance for a response to show zero-value messages,
|
if err != nil {
|
||||||
// so we can perform isZero test here
|
fmt.Fprintf(os.Stdout, "%v\n", m)
|
||||||
func isEmpty(response interface{}) bool {
|
base.Fatalf("error encode json: %s", err)
|
||||||
s := reflect.Indirect(reflect.ValueOf(response))
|
|
||||||
if s.Kind() == reflect.Invalid {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
switch s.Kind() {
|
fmt.Println(output)
|
||||||
case reflect.Struct:
|
|
||||||
for i := 0; i < s.NumField(); i++ {
|
|
||||||
f := s.Type().Field(i)
|
|
||||||
if f.Name[0] < 65 || f.Name[0] > 90 {
|
|
||||||
// continue if not exported.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
field := s.Field(i)
|
|
||||||
if !isEmpty(field.Interface()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case reflect.Array, reflect.Slice:
|
|
||||||
for i := 0; i < s.Len(); i++ {
|
|
||||||
if !isEmpty(s.Index(i).Interface()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if !s.IsZero() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
@ -1,140 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
statsService "github.com/v2fly/v2ray-core/v4/app/stats/command"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEmptyResponese_0(t *testing.T) {
|
|
||||||
r := &statsService.QueryStatsResponse{
|
|
||||||
Stat: []*statsService.Stat{
|
|
||||||
{
|
|
||||||
Name: "1>>2",
|
|
||||||
Value: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "1>>2>>3",
|
|
||||||
Value: 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert(t, isEmpty(r), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptyResponese_1(t *testing.T) {
|
|
||||||
r := (*statsService.QueryStatsResponse)(nil)
|
|
||||||
assert(t, isEmpty(r), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptyResponese_2(t *testing.T) {
|
|
||||||
r := &statsService.QueryStatsResponse{
|
|
||||||
Stat: nil,
|
|
||||||
}
|
|
||||||
assert(t, isEmpty(r), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptyResponese_3(t *testing.T) {
|
|
||||||
r := &statsService.QueryStatsResponse{
|
|
||||||
Stat: []*statsService.Stat{},
|
|
||||||
}
|
|
||||||
assert(t, isEmpty(r), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptyResponese_4(t *testing.T) {
|
|
||||||
r := &statsService.QueryStatsResponse{
|
|
||||||
Stat: []*statsService.Stat{
|
|
||||||
{
|
|
||||||
Name: "",
|
|
||||||
Value: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert(t, isEmpty(r), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptyResponese_5(t *testing.T) {
|
|
||||||
type test struct {
|
|
||||||
Value *statsService.QueryStatsResponse
|
|
||||||
}
|
|
||||||
r := &test{
|
|
||||||
Value: &statsService.QueryStatsResponse{
|
|
||||||
Stat: []*statsService.Stat{
|
|
||||||
{
|
|
||||||
Name: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert(t, isEmpty(r), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptyResponese_6(t *testing.T) {
|
|
||||||
type test struct {
|
|
||||||
Value *statsService.QueryStatsResponse
|
|
||||||
}
|
|
||||||
r := &test{
|
|
||||||
Value: &statsService.QueryStatsResponse{
|
|
||||||
Stat: []*statsService.Stat{
|
|
||||||
{
|
|
||||||
Value: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert(t, isEmpty(r), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptyResponese_7(t *testing.T) {
|
|
||||||
type test struct {
|
|
||||||
Value *int
|
|
||||||
}
|
|
||||||
v := 1
|
|
||||||
r := &test{
|
|
||||||
Value: &v,
|
|
||||||
}
|
|
||||||
assert(t, isEmpty(r), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptyResponese_8(t *testing.T) {
|
|
||||||
type test struct {
|
|
||||||
Value *int
|
|
||||||
}
|
|
||||||
v := 0
|
|
||||||
r := &test{
|
|
||||||
Value: &v,
|
|
||||||
}
|
|
||||||
assert(t, isEmpty(r), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptyResponese_9(t *testing.T) {
|
|
||||||
assert(t, isEmpty(0), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptyResponese_10(t *testing.T) {
|
|
||||||
assert(t, isEmpty(1), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptyResponese_11(t *testing.T) {
|
|
||||||
r := []*statsService.Stat{
|
|
||||||
{
|
|
||||||
Name: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert(t, isEmpty(r), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptyResponese_12(t *testing.T) {
|
|
||||||
r := []*statsService.Stat{
|
|
||||||
{
|
|
||||||
Value: 1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert(t, isEmpty(r), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func assert(t *testing.T, value, expected bool) {
|
|
||||||
if value != expected {
|
|
||||||
t.Fatalf("Expected: %v, actual: %v", expected, value)
|
|
||||||
}
|
|
||||||
}
|
|
165
main/commands/all/api/stats.go
Normal file
165
main/commands/all/api/stats.go
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
statsService "github.com/v2fly/v2ray-core/v4/app/stats/command"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/common/units"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/main/commands/base"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdStats = &base.Command{
|
||||||
|
CustomFlags: true,
|
||||||
|
UsageLine: "{{.Exec}} api stats [--server=127.0.0.1:8080] [pattern]...",
|
||||||
|
Short: "query statistics",
|
||||||
|
Long: `
|
||||||
|
Query statistics from V2Ray.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
-regexp
|
||||||
|
The patterns are using regexp.
|
||||||
|
|
||||||
|
-reset
|
||||||
|
Fetch values then reset statistics counters to 0.
|
||||||
|
|
||||||
|
-runtime
|
||||||
|
Get runtime statistics.
|
||||||
|
|
||||||
|
-json
|
||||||
|
Use json output.
|
||||||
|
|
||||||
|
-s, -server <server:port>
|
||||||
|
The API server address. Default 127.0.0.1:8080
|
||||||
|
|
||||||
|
-t, -timeout <seconds>
|
||||||
|
Timeout seconds to call API. Default 3
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{.Exec}} {{.LongName}} -runtime
|
||||||
|
{{.Exec}} {{.LongName}} node1
|
||||||
|
{{.Exec}} {{.LongName}} -json node1 node2
|
||||||
|
{{.Exec}} {{.LongName}} -regexp 'node1.+downlink'
|
||||||
|
`,
|
||||||
|
Run: executeStats,
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeStats(cmd *base.Command, args []string) {
|
||||||
|
setSharedFlags(cmd)
|
||||||
|
var (
|
||||||
|
runtime bool
|
||||||
|
regexp bool
|
||||||
|
reset bool
|
||||||
|
)
|
||||||
|
cmd.Flag.BoolVar(&runtime, "runtime", false, "")
|
||||||
|
cmd.Flag.BoolVar(®exp, "regexp", false, "")
|
||||||
|
cmd.Flag.BoolVar(&reset, "reset", false, "")
|
||||||
|
cmd.Flag.Parse(args)
|
||||||
|
unnamed := cmd.Flag.Args()
|
||||||
|
if runtime {
|
||||||
|
getRuntimeStats(apiJSON)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
getStats(unnamed, regexp, reset, apiJSON)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRuntimeStats(jsonOutput bool) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if jsonOutput {
|
||||||
|
showJSONResponse(resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
showRuntimeStats(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func showRuntimeStats(s *statsService.SysStatsResponse) {
|
||||||
|
formats := []string{"%-22s", "%-10s"}
|
||||||
|
rows := [][]string{
|
||||||
|
{"Up time", (time.Duration(s.Uptime) * time.Second).String()},
|
||||||
|
{"Memory obtained", units.ByteSize(s.Sys).String()},
|
||||||
|
{"Number of goroutines", fmt.Sprintf("%d", s.NumGoroutine)},
|
||||||
|
{"Heap allocated", units.ByteSize(s.Alloc).String()},
|
||||||
|
{"Live objects", fmt.Sprintf("%d", s.LiveObjects)},
|
||||||
|
{"Heap allocated total", units.ByteSize(s.TotalAlloc).String()},
|
||||||
|
{"Heap allocate count", fmt.Sprintf("%d", s.Mallocs)},
|
||||||
|
{"Heap free count", fmt.Sprintf("%d", s.Frees)},
|
||||||
|
{"Number of GC", fmt.Sprintf("%d", s.NumGC)},
|
||||||
|
{"Time of GC pause", (time.Duration(s.PauseTotalNs) * time.Nanosecond).String()},
|
||||||
|
}
|
||||||
|
sb := new(strings.Builder)
|
||||||
|
writeRow(sb, 0, 0,
|
||||||
|
[]string{"Item", "Value"},
|
||||||
|
formats,
|
||||||
|
)
|
||||||
|
for i, r := range rows {
|
||||||
|
writeRow(sb, 0, i+1, r, formats)
|
||||||
|
}
|
||||||
|
os.Stdout.WriteString(sb.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStats(patterns []string, regexp, reset, jsonOutput bool) {
|
||||||
|
conn, ctx, close := dialAPIServer()
|
||||||
|
defer close()
|
||||||
|
|
||||||
|
client := statsService.NewStatsServiceClient(conn)
|
||||||
|
r := &statsService.QueryStatsRequest{
|
||||||
|
Patterns: patterns,
|
||||||
|
Regexp: regexp,
|
||||||
|
Reset_: reset,
|
||||||
|
}
|
||||||
|
resp, err := client.QueryStats(ctx, r)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to query stats: %s", err)
|
||||||
|
}
|
||||||
|
if jsonOutput {
|
||||||
|
showJSONResponse(resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sort.Slice(resp.Stat, func(i, j int) bool {
|
||||||
|
return resp.Stat[i].Name < resp.Stat[j].Name
|
||||||
|
})
|
||||||
|
showStats(resp.Stat)
|
||||||
|
}
|
||||||
|
|
||||||
|
func showStats(stats []*statsService.Stat) {
|
||||||
|
if len(stats) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
formats := []string{"%-12s", "%s"}
|
||||||
|
sum := int64(0)
|
||||||
|
sb := new(strings.Builder)
|
||||||
|
idx := 0
|
||||||
|
writeRow(sb, 0, 0,
|
||||||
|
[]string{"Value", "Name"},
|
||||||
|
formats,
|
||||||
|
)
|
||||||
|
for _, stat := range stats {
|
||||||
|
// if stat.Value == 0 {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
idx++
|
||||||
|
sum += stat.Value
|
||||||
|
writeRow(
|
||||||
|
sb, 0, idx,
|
||||||
|
[]string{units.ByteSize(stat.Value).String(), stat.Name},
|
||||||
|
formats,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
sb.WriteString(
|
||||||
|
fmt.Sprintf("\nTotal: %s\n", units.ByteSize(sum)),
|
||||||
|
)
|
||||||
|
os.Stdout.WriteString(sb.String())
|
||||||
|
}
|
@ -1,55 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
statsService "github.com/v2fly/v2ray-core/v4/app/stats/command"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/main/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)
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
statsService "github.com/v2fly/v2ray-core/v4/app/stats/command"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/main/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)
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
statsService "github.com/v2fly/v2ray-core/v4/app/stats/command"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/main/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)
|
|
||||||
}
|
|
@ -10,32 +10,35 @@ import (
|
|||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
core "github.com/v2fly/v2ray-core/v4"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/merge"
|
||||||
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
|
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
|
||||||
"github.com/v2fly/v2ray-core/v4/main/commands/base"
|
"github.com/v2fly/v2ray-core/v4/main/commands/base"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/main/commands/helpers"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdConvert = &base.Command{
|
var cmdConvert = &base.Command{
|
||||||
CustomFlags: true,
|
CustomFlags: true,
|
||||||
UsageLine: "{{.Exec}} convert [c1.json] [<url>.json] [dir1] ...",
|
UsageLine: "{{.Exec}} convert [c1.json] [<url>.json] [dir1] ...",
|
||||||
Short: "Convert config files",
|
Short: "convert config files",
|
||||||
Long: `
|
Long: `
|
||||||
Convert config files between different formats. Files are merged
|
Convert config files between different formats. Files are merged
|
||||||
before convert if multiple assigned.
|
before convert if multiple assigned.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
||||||
-i, -input
|
-i, -input <format>
|
||||||
Specify the input format.
|
Specify the input format.
|
||||||
Available values: "json", "toml", "yaml"
|
Available values: "auto", "json", "toml", "yaml"
|
||||||
Default: "json"
|
Default: "auto"
|
||||||
|
|
||||||
-o, -output
|
-o, -output <format>
|
||||||
Specify the output format
|
Specify the output format
|
||||||
Available values: "json", "toml", "yaml", "protobuf" / "pb"
|
Available values: "json", "toml", "yaml", "protobuf" / "pb"
|
||||||
Default: "json"
|
Default: "json"
|
||||||
|
|
||||||
-r
|
-r
|
||||||
Load confdir recursively.
|
Load folders recursively.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
@ -62,15 +65,10 @@ var (
|
|||||||
outputFormat string
|
outputFormat string
|
||||||
confDirRecursively bool
|
confDirRecursively bool
|
||||||
)
|
)
|
||||||
var formatExtensions = map[string][]string{
|
|
||||||
"json": {".json", ".jsonc"},
|
|
||||||
"toml": {".toml"},
|
|
||||||
"yaml": {".yaml", ".yml"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func setConfArgs(cmd *base.Command) {
|
func setConfArgs(cmd *base.Command) {
|
||||||
cmd.Flag.StringVar(&inputFormat, "input", "json", "")
|
cmd.Flag.StringVar(&inputFormat, "input", core.FormatAuto, "")
|
||||||
cmd.Flag.StringVar(&inputFormat, "i", "json", "")
|
cmd.Flag.StringVar(&inputFormat, "i", core.FormatAuto, "")
|
||||||
cmd.Flag.StringVar(&outputFormat, "output", "json", "")
|
cmd.Flag.StringVar(&outputFormat, "output", "json", "")
|
||||||
cmd.Flag.StringVar(&outputFormat, "o", "json", "")
|
cmd.Flag.StringVar(&outputFormat, "o", "json", "")
|
||||||
cmd.Flag.BoolVar(&confDirRecursively, "r", false, "")
|
cmd.Flag.BoolVar(&confDirRecursively, "r", false, "")
|
||||||
@ -78,37 +76,36 @@ func setConfArgs(cmd *base.Command) {
|
|||||||
func executeConvert(cmd *base.Command, args []string) {
|
func executeConvert(cmd *base.Command, args []string) {
|
||||||
setConfArgs(cmd)
|
setConfArgs(cmd)
|
||||||
cmd.Flag.Parse(args)
|
cmd.Flag.Parse(args)
|
||||||
unnamed := cmd.Flag.Args()
|
|
||||||
inputFormat = strings.ToLower(inputFormat)
|
inputFormat = strings.ToLower(inputFormat)
|
||||||
outputFormat = strings.ToLower(outputFormat)
|
outputFormat = strings.ToLower(outputFormat)
|
||||||
|
|
||||||
files := resolveFolderToFiles(unnamed, formatExtensions[inputFormat], confDirRecursively)
|
m, err := helpers.LoadConfigToMap(cmd.Flag.Args(), inputFormat, confDirRecursively)
|
||||||
if len(files) == 0 {
|
if err != nil {
|
||||||
base.Fatalf("empty config list")
|
base.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
err = merge.ApplyRules(m)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
m := mergeConvertToMap(files, inputFormat)
|
|
||||||
|
|
||||||
var (
|
var out []byte
|
||||||
out []byte
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
switch outputFormat {
|
switch outputFormat {
|
||||||
case "json":
|
case core.FormatJSON:
|
||||||
out, err = json.Marshal(m)
|
out, err = json.Marshal(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf("failed to marshal json: %s", err)
|
base.Fatalf("failed to marshal json: %s", err)
|
||||||
}
|
}
|
||||||
case "toml":
|
case core.FormatTOML:
|
||||||
out, err = toml.Marshal(m)
|
out, err = toml.Marshal(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf("failed to marshal json: %s", err)
|
base.Fatalf("failed to marshal json: %s", err)
|
||||||
}
|
}
|
||||||
case "yaml":
|
case core.FormatYAML:
|
||||||
out, err = yaml.Marshal(m)
|
out, err = yaml.Marshal(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf("failed to marshal json: %s", err)
|
base.Fatalf("failed to marshal json: %s", err)
|
||||||
}
|
}
|
||||||
case "pb", "protobuf":
|
case core.FormatProtobuf, core.FormatProtobufShort:
|
||||||
data, err := json.Marshal(m)
|
data, err := json.Marshal(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf("failed to marshal json: %s", err)
|
base.Fatalf("failed to marshal json: %s", err)
|
||||||
@ -133,6 +130,6 @@ func executeConvert(cmd *base.Command, args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Stdout.Write(out); err != nil {
|
if _, err := os.Stdout.Write(out); err != nil {
|
||||||
base.Fatalf("failed to write proto config: %s", err)
|
base.Fatalf("failed to write stdout: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,138 +0,0 @@
|
|||||||
package all
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/infra/conf/json"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/infra/conf/merge"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/main/commands/base"
|
|
||||||
)
|
|
||||||
|
|
||||||
func mergeConvertToMap(files []string, format string) map[string]interface{} {
|
|
||||||
var (
|
|
||||||
m map[string]interface{}
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
switch inputFormat {
|
|
||||||
case "json":
|
|
||||||
m, err = merge.FilesToMap(files)
|
|
||||||
if err != nil {
|
|
||||||
base.Fatalf("failed to load json: %s", err)
|
|
||||||
}
|
|
||||||
case "toml":
|
|
||||||
bs, err := tomlsToJSONs(files)
|
|
||||||
if err != nil {
|
|
||||||
base.Fatalf("failed to convert toml to json: %s", err)
|
|
||||||
}
|
|
||||||
m, err = merge.BytesToMap(bs)
|
|
||||||
if err != nil {
|
|
||||||
base.Fatalf("failed to merge converted json: %s", err)
|
|
||||||
}
|
|
||||||
case "yaml":
|
|
||||||
bs, err := yamlsToJSONs(files)
|
|
||||||
if err != nil {
|
|
||||||
base.Fatalf("failed to convert yaml to json: %s", err)
|
|
||||||
}
|
|
||||||
m, err = merge.BytesToMap(bs)
|
|
||||||
if err != nil {
|
|
||||||
base.Fatalf("failed to merge converted json: %s", err)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
base.Errorf("invalid input format: %s", format)
|
|
||||||
base.Errorf("Run '%s help %s' for details.", base.CommandEnv.Exec, cmdConvert.LongName())
|
|
||||||
base.Exit()
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolveFolderToFiles expands folder path (if any and it exists) to file paths.
|
|
||||||
// Any other paths, like file, even URL, it returns them as is.
|
|
||||||
func resolveFolderToFiles(paths []string, extensions []string, recursively bool) []string {
|
|
||||||
dirReader := readConfDir
|
|
||||||
if recursively {
|
|
||||||
dirReader = readConfDirRecursively
|
|
||||||
}
|
|
||||||
files := make([]string, 0)
|
|
||||||
for _, p := range paths {
|
|
||||||
i, err := os.Stat(p)
|
|
||||||
if err == nil && i.IsDir() {
|
|
||||||
files = append(files, dirReader(p, extensions)...)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
files = append(files, p)
|
|
||||||
}
|
|
||||||
return files
|
|
||||||
}
|
|
||||||
|
|
||||||
func readConfDir(dirPath string, extensions []string) []string {
|
|
||||||
confs, err := ioutil.ReadDir(dirPath)
|
|
||||||
if err != nil {
|
|
||||||
base.Fatalf("failed to read dir %s: %s", dirPath, err)
|
|
||||||
}
|
|
||||||
files := make([]string, 0)
|
|
||||||
for _, f := range confs {
|
|
||||||
ext := filepath.Ext(f.Name())
|
|
||||||
for _, e := range extensions {
|
|
||||||
if strings.EqualFold(ext, e) {
|
|
||||||
files = append(files, filepath.Join(dirPath, f.Name()))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files
|
|
||||||
}
|
|
||||||
|
|
||||||
// getFolderFiles get files in the folder and it's children
|
|
||||||
func readConfDirRecursively(dirPath string, extensions []string) []string {
|
|
||||||
files := make([]string, 0)
|
|
||||||
err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
|
|
||||||
ext := filepath.Ext(path)
|
|
||||||
for _, e := range extensions {
|
|
||||||
if strings.EqualFold(ext, e) {
|
|
||||||
files = append(files, path)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
base.Fatalf("failed to read dir %s: %s", dirPath, err)
|
|
||||||
}
|
|
||||||
return files
|
|
||||||
}
|
|
||||||
|
|
||||||
func yamlsToJSONs(files []string) ([][]byte, error) {
|
|
||||||
jsons := make([][]byte, 0)
|
|
||||||
for _, file := range files {
|
|
||||||
bs, err := cmdarg.LoadArgToBytes(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
j, err := json.FromYAML(bs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
jsons = append(jsons, j)
|
|
||||||
}
|
|
||||||
return jsons, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func tomlsToJSONs(files []string) ([][]byte, error) {
|
|
||||||
jsons := make([][]byte, 0)
|
|
||||||
for _, file := range files {
|
|
||||||
bs, err := cmdarg.LoadArgToBytes(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
j, err := json.FromTOML(bs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
jsons = append(jsons, j)
|
|
||||||
}
|
|
||||||
return jsons, nil
|
|
||||||
}
|
|
@ -10,44 +10,41 @@ var docFormat = &base.Command{
|
|||||||
Long: `
|
Long: `
|
||||||
{{.Exec}} supports different config formats:
|
{{.Exec}} supports different config formats:
|
||||||
|
|
||||||
|
* auto
|
||||||
|
The default loader, supports all extensions below.
|
||||||
|
It loads config by format detecting, with mixed
|
||||||
|
formats support.
|
||||||
|
|
||||||
* json (.json, .jsonc)
|
* json (.json, .jsonc)
|
||||||
The default loader, multiple config files support.
|
The json loader, multiple files support, mergeable.
|
||||||
|
|
||||||
* toml (.toml)
|
* toml (.toml)
|
||||||
The toml loader, multiple config files support.
|
The toml loader, multiple files support, mergeable.
|
||||||
|
|
||||||
* yaml (.yml)
|
* yaml (.yml)
|
||||||
The yaml loader, multiple config files support.
|
The yaml loader, multiple files support, mergeable.
|
||||||
|
|
||||||
* protobuf / pb (.pb)
|
* protobuf / pb (.pb)
|
||||||
Single conifg file support. If multiple files assigned,
|
Single file support, unmergeable.
|
||||||
only the first one is loaded.
|
|
||||||
|
|
||||||
If "-format" is not explicitly specified, {{.Exec}} will choose
|
|
||||||
a loader by detecting the extension of the first config file, or
|
|
||||||
use the default loader.
|
|
||||||
|
|
||||||
The following explains how format loaders behave with examples.
|
The following explains how format loaders behave with examples.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
{{.Exec}} run -d dir (1)
|
{{.Exec}} run -d dir (1)
|
||||||
{{.Exec}} run -format=protobuf -d dir (2)
|
{{.Exec}} run -c c1.json -c c2.yaml (2)
|
||||||
{{.Exec}} test -c c1.yml -d dir (3)
|
{{.Exec}} run -format=json -d dir (3)
|
||||||
{{.Exec}} test -format=pb -c c1.json (4)
|
{{.Exec}} test -c c1.yml -c c2.pb (4)
|
||||||
|
{{.Exec}} test -format=pb -d dir (5)
|
||||||
|
{{.Exec}} test -format=protobuf -c c1.json (6)
|
||||||
|
|
||||||
(1) The default json loader is used, {{.Exec}} will try to load all
|
(1) Load all supported files in the "dir".
|
||||||
json files in the "dir".
|
(2) JSON and YAML are merged and loaded.
|
||||||
|
(3) Load all JSON files in the "dir".
|
||||||
(2) The protobuf loader is specified, {{.Exec}} will try to find
|
(4) Goes error since .pb is not mergeable to others
|
||||||
all protobuf files in the "dir", but only the the first
|
(5) Works only when single .pb file found, if not, failed due to
|
||||||
.pb file is loaded.
|
unmergeable.
|
||||||
|
(6) Force load "c1.json" as protobuf, no matter its extension.
|
||||||
(3) The yaml loader is selected because of the "c1.yml" file,
|
|
||||||
{{.Exec}} will try to load "c1.yml" and all yaml files in
|
|
||||||
the "dir".
|
|
||||||
|
|
||||||
(4) The protobuf loader is specified, {{.Exec}} will load
|
|
||||||
"c1.json" as protobuf, no matter its extension.
|
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,9 @@ Merging of config files is applied in following commands:
|
|||||||
{{.Exec}} run -c c1.json -c c2.json ...
|
{{.Exec}} run -c c1.json -c c2.json ...
|
||||||
{{.Exec}} test -c c1.yaml -c c2.yaml ...
|
{{.Exec}} test -c c1.yaml -c c2.yaml ...
|
||||||
{{.Exec}} convert c1.json dir1 ...
|
{{.Exec}} convert c1.json dir1 ...
|
||||||
|
{{.Exec}} api ado c1.json dir1 ...
|
||||||
|
{{.Exec}} api rmi c1.json dir1 ...
|
||||||
|
... and more ...
|
||||||
|
|
||||||
Support of toml and yaml is implemented by converting them to json,
|
Support of toml and yaml is implemented by converting them to json,
|
||||||
both merge and load. So we take json as example here.
|
both merge and load. So we take json as example here.
|
||||||
|
@ -17,29 +17,29 @@ import (
|
|||||||
// 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=v2fly.org] [--expire=240h]",
|
UsageLine: "{{.Exec}} tls cert [--ca] [--domain=v2fly.org] [--expire=240h]",
|
||||||
Short: "Generate TLS certificates",
|
Short: "generate TLS certificates",
|
||||||
Long: `
|
Long: `
|
||||||
Generate TLS certificates.
|
Generate TLS certificates.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
||||||
-domain=domain_name
|
-domain <domain_name>
|
||||||
The domain name for the certificate.
|
The domain name for the certificate.
|
||||||
|
|
||||||
-org=organization
|
-org <organization>
|
||||||
The organization name for the certificate.
|
The organization name for the certificate.
|
||||||
|
|
||||||
-ca
|
-ca
|
||||||
Whether this certificate is a CA
|
The certificate is a CA
|
||||||
|
|
||||||
-json
|
-json
|
||||||
The output of certificate to JSON
|
To output certificate to JSON
|
||||||
|
|
||||||
-file
|
-file <path>
|
||||||
The certificate path to save.
|
The certificate path to save.
|
||||||
|
|
||||||
-expire
|
-expire <days>
|
||||||
Expire time of the certificate. Default value 3 months.
|
Expire days of the certificate. Default 90 days.
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,12 +54,12 @@ var (
|
|||||||
return true
|
return true
|
||||||
}()
|
}()
|
||||||
|
|
||||||
certCommonName = cmdCert.Flag.String("name", "V2Ray Inc", "The common name of this certificate")
|
certCommonName = cmdCert.Flag.String("name", "V2Ray Inc", "")
|
||||||
certOrganization = cmdCert.Flag.String("org", "V2Ray Inc", "Organization of the certificate")
|
certOrganization = cmdCert.Flag.String("org", "V2Ray Inc", "")
|
||||||
certIsCA = cmdCert.Flag.Bool("ca", false, "Whether this certificate is a CA")
|
certIsCA = cmdCert.Flag.Bool("ca", false, "")
|
||||||
certJSONOutput = cmdCert.Flag.Bool("json", true, "Print certificate in JSON format")
|
certJSONOutput = cmdCert.Flag.Bool("json", true, "")
|
||||||
certFileOutput = cmdCert.Flag.String("file", "", "Save certificate in file.")
|
certFileOutput = cmdCert.Flag.String("file", "", "")
|
||||||
certExpire = cmdCert.Flag.Duration("expire", time.Hour*24*90 /* 90 days */, "Time until the certificate expires. Default value 3 months.")
|
certExpire = cmdCert.Flag.Uint("expire", 90, "")
|
||||||
)
|
)
|
||||||
|
|
||||||
func executeCert(cmd *base.Command, args []string) {
|
func executeCert(cmd *base.Command, args []string) {
|
||||||
@ -69,7 +69,7 @@ func executeCert(cmd *base.Command, args []string) {
|
|||||||
opts = append(opts, cert.KeyUsage(x509.KeyUsageCertSign|x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature))
|
opts = append(opts, cert.KeyUsage(x509.KeyUsageCertSign|x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature))
|
||||||
}
|
}
|
||||||
|
|
||||||
opts = append(opts, cert.NotAfter(time.Now().Add(*certExpire)))
|
opts = append(opts, cert.NotAfter(time.Now().Add(time.Duration(*certExpire)*time.Hour*24)))
|
||||||
opts = append(opts, cert.CommonName(*certCommonName))
|
opts = append(opts, cert.CommonName(*certCommonName))
|
||||||
if len(certDomainNames) > 0 {
|
if len(certDomainNames) > 0 {
|
||||||
opts = append(opts, cert.DNSNames(certDomainNames...))
|
opts = append(opts, cert.DNSNames(certDomainNames...))
|
||||||
|
@ -12,13 +12,13 @@ import (
|
|||||||
// 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: `
|
||||||
Ping the domain with TLS handshake.
|
Ping the domain with TLS handshake.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
||||||
-ip
|
-ip <ip>
|
||||||
The IP address of the domain.
|
The IP address of the domain.
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
var cmdUUID = &base.Command{
|
var cmdUUID = &base.Command{
|
||||||
UsageLine: "{{.Exec}} uuid",
|
UsageLine: "{{.Exec}} uuid",
|
||||||
Short: "Generate new UUIDs",
|
Short: "generate new UUIDs",
|
||||||
Long: `Generate new UUIDs.
|
Long: `Generate new UUIDs.
|
||||||
`,
|
`,
|
||||||
Run: executeUUID,
|
Run: executeUUID,
|
||||||
|
@ -9,13 +9,13 @@ import (
|
|||||||
|
|
||||||
var cmdVerify = &base.Command{
|
var cmdVerify = &base.Command{
|
||||||
UsageLine: "{{.Exec}} verify [--sig=sig-file] file",
|
UsageLine: "{{.Exec}} verify [--sig=sig-file] file",
|
||||||
Short: "Verify if a binary is officially signed",
|
Short: "verify if a binary is officially signed",
|
||||||
Long: `
|
Long: `
|
||||||
Verify if a binary is officially signed.
|
Verify if a binary is officially signed.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
||||||
-sig
|
-sig <signature_file>
|
||||||
The path to the signature file
|
The path to the signature file
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
55
main/commands/helpers/config_load.go
Normal file
55
main/commands/helpers/config_load.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/merge"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/mergers"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoadConfig load config files to *conf.Config, it will:
|
||||||
|
// - resolve folder to files
|
||||||
|
// - try to read stdin if no file specified
|
||||||
|
func LoadConfig(files []string, format string, recursively bool) (*conf.Config, error) {
|
||||||
|
m, err := LoadConfigToMap(files, format, recursively)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bs, err := merge.FromMap(m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r := bytes.NewReader(bs)
|
||||||
|
return serial.DecodeJSONConfig(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadConfigToMap load config files to map, it will:
|
||||||
|
// - resolve folder to files
|
||||||
|
// - try to read stdin if no file specified
|
||||||
|
func LoadConfigToMap(files []string, format string, recursively bool) (map[string]interface{}, error) {
|
||||||
|
var err error
|
||||||
|
if len(files) > 0 {
|
||||||
|
var extensions []string
|
||||||
|
extensions, err := mergers.GetExtensions(format)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
files, err = ResolveFolderToFiles(files, extensions, recursively)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
if len(files) == 0 {
|
||||||
|
err = mergers.MergeAs(format, os.Stdin, m)
|
||||||
|
} else {
|
||||||
|
err = mergers.MergeAs(format, files, m)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
70
main/commands/helpers/fs.go
Normal file
70
main/commands/helpers/fs.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadDir finds files according to extensions in the dir
|
||||||
|
func ReadDir(dir string, extensions []string) ([]string, error) {
|
||||||
|
confs, err := ioutil.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
files := make([]string, 0)
|
||||||
|
for _, f := range confs {
|
||||||
|
ext := filepath.Ext(f.Name())
|
||||||
|
for _, e := range extensions {
|
||||||
|
if strings.EqualFold(ext, e) {
|
||||||
|
files = append(files, filepath.Join(dir, f.Name()))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadDirRecursively finds files according to extensions in the dir recursively
|
||||||
|
func ReadDirRecursively(dir string, extensions []string) ([]string, error) {
|
||||||
|
files := make([]string, 0)
|
||||||
|
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
ext := filepath.Ext(path)
|
||||||
|
for _, e := range extensions {
|
||||||
|
if strings.EqualFold(ext, e) {
|
||||||
|
files = append(files, path)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveFolderToFiles expands folder path (if any and it exists) to file paths.
|
||||||
|
// Any other paths, like file, even URL, it returns them as is.
|
||||||
|
func ResolveFolderToFiles(paths []string, extensions []string, recursively bool) ([]string, error) {
|
||||||
|
dirReader := ReadDir
|
||||||
|
if recursively {
|
||||||
|
dirReader = ReadDirRecursively
|
||||||
|
}
|
||||||
|
files := make([]string, 0)
|
||||||
|
for _, p := range paths {
|
||||||
|
i, err := os.Stat(p)
|
||||||
|
if err == nil && i.IsDir() {
|
||||||
|
fs, err := dirReader(p, extensions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read dir %s: %s", p, err)
|
||||||
|
}
|
||||||
|
files = append(files, fs...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
files = append(files, p)
|
||||||
|
}
|
||||||
|
return files, nil
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
@ -20,22 +21,22 @@ import (
|
|||||||
var CmdRun = &base.Command{
|
var CmdRun = &base.Command{
|
||||||
CustomFlags: true,
|
CustomFlags: true,
|
||||||
UsageLine: "{{.Exec}} run [-c config.json] [-d dir]",
|
UsageLine: "{{.Exec}} run [-c config.json] [-d dir]",
|
||||||
Short: "Run V2Ray with config",
|
Short: "run V2Ray with config",
|
||||||
Long: `
|
Long: `
|
||||||
Run V2Ray with config.
|
Run V2Ray with config.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
||||||
-c, -config
|
-c, -config <file>
|
||||||
Config file for V2Ray. Multiple assign is accepted.
|
Config file for V2Ray. Multiple assign is accepted.
|
||||||
|
|
||||||
-d, -confdir
|
-d, -confdir <dir>
|
||||||
A dir with config files. Multiple assign is accepted.
|
A dir with config files. Multiple assign is accepted.
|
||||||
|
|
||||||
-r
|
-r
|
||||||
Load confdir recursively.
|
Load confdir recursively.
|
||||||
|
|
||||||
-format
|
-format <format>
|
||||||
Format of input files. (default "json")
|
Format of input files. (default "json")
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
@ -56,7 +57,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func setConfigFlags(cmd *base.Command) {
|
func setConfigFlags(cmd *base.Command) {
|
||||||
configFormat = cmd.Flag.String("format", "", "")
|
configFormat = cmd.Flag.String("format", core.FormatAuto, "")
|
||||||
configDirRecursively = cmd.Flag.Bool("r", false, "")
|
configDirRecursively = cmd.Flag.Bool("r", false, "")
|
||||||
|
|
||||||
cmd.Flag.Var(&configFiles, "config", "")
|
cmd.Flag.Var(&configFiles, "config", "")
|
||||||
@ -69,6 +70,7 @@ func executeRun(cmd *base.Command, args []string) {
|
|||||||
setConfigFlags(cmd)
|
setConfigFlags(cmd)
|
||||||
cmd.Flag.Parse(args)
|
cmd.Flag.Parse(args)
|
||||||
printVersion()
|
printVersion()
|
||||||
|
configFiles = getConfigFilePath()
|
||||||
server, err := startV2Ray()
|
server, err := startV2Ray()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf("Failed to start: %s", err)
|
base.Fatalf("Failed to start: %s", err)
|
||||||
@ -139,20 +141,8 @@ func readConfDirRecursively(dirPath string, extension []string) cmdarg.Arg {
|
|||||||
return files
|
return files
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLoaderExtension() ([]string, error) {
|
|
||||||
firstFile := ""
|
|
||||||
if len(configFiles) > 0 {
|
|
||||||
firstFile = configFiles[0]
|
|
||||||
}
|
|
||||||
loader, err := core.GetConfigLoader(*configFormat, firstFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return loader.Extension, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getConfigFilePath() cmdarg.Arg {
|
func getConfigFilePath() cmdarg.Arg {
|
||||||
extension, err := getLoaderExtension()
|
extension, err := core.GetLoaderExtensions(*configFormat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf(err.Error())
|
base.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
@ -190,16 +180,18 @@ func getConfigFilePath() cmdarg.Arg {
|
|||||||
return cmdarg.Arg{configFile}
|
return cmdarg.Arg{configFile}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Using config from STDIN")
|
return nil
|
||||||
return cmdarg.Arg{"stdin:"}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func startV2Ray() (core.Server, error) {
|
func startV2Ray() (core.Server, error) {
|
||||||
configFiles := getConfigFilePath()
|
config, err := core.LoadConfig(*configFormat, configFiles)
|
||||||
|
|
||||||
config, err := core.LoadConfig(*configFormat, configFiles[0], configFiles)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newError("failed to read config files: [", configFiles.String(), "]").Base(err)
|
if len(configFiles) == 0 {
|
||||||
|
err = newError("failed to load config").Base(err)
|
||||||
|
} else {
|
||||||
|
err = newError(fmt.Sprintf("failed to load config: %s", configFiles)).Base(err)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
server, err := core.New(config)
|
server, err := core.New(config)
|
||||||
|
@ -12,22 +12,22 @@ import (
|
|||||||
var CmdTest = &base.Command{
|
var CmdTest = &base.Command{
|
||||||
CustomFlags: true,
|
CustomFlags: true,
|
||||||
UsageLine: "{{.Exec}} test [-format=json] [-c config.json] [-d dir]",
|
UsageLine: "{{.Exec}} test [-format=json] [-c config.json] [-d dir]",
|
||||||
Short: "Test config files",
|
Short: "test config files",
|
||||||
Long: `
|
Long: `
|
||||||
Test config files, without launching V2Ray server.
|
Test config files, without launching V2Ray server.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
||||||
-c, -config
|
-c, -config <file>
|
||||||
Config file for V2Ray. Multiple assign is accepted.
|
Config file for V2Ray. Multiple assign is accepted.
|
||||||
|
|
||||||
-d, -confdir
|
-d, -confdir <dir>
|
||||||
A dir with config files. Multiple assign is accepted.
|
A dir with config files. Multiple assign is accepted.
|
||||||
|
|
||||||
-r
|
-r
|
||||||
Load confdir recursively.
|
Load confdir recursively.
|
||||||
|
|
||||||
-format
|
-format <format>
|
||||||
Format of input files. (default "json")
|
Format of input files. (default "json")
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
@ -44,7 +44,7 @@ func executeTest(cmd *base.Command, args []string) {
|
|||||||
setConfigFlags(cmd)
|
setConfigFlags(cmd)
|
||||||
cmd.Flag.Parse(args)
|
cmd.Flag.Parse(args)
|
||||||
|
|
||||||
extension, err := getLoaderExtension()
|
extension, err := core.GetLoaderExtensions(*configFormat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf(err.Error())
|
base.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
@ -59,32 +59,10 @@ func executeTest(cmd *base.Command, args []string) {
|
|||||||
configFiles = append(configFiles, dirReader(d, extension)...)
|
configFiles = append(configFiles, dirReader(d, extension)...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(configFiles) == 0 {
|
|
||||||
if len(configDirs) == 0 {
|
|
||||||
cmd.Flag.Usage()
|
|
||||||
base.SetExitStatus(1)
|
|
||||||
base.Exit()
|
|
||||||
}
|
|
||||||
base.Fatalf("no config file found with extension: %s", extension)
|
|
||||||
}
|
|
||||||
printVersion()
|
printVersion()
|
||||||
_, err = startV2RayTesting()
|
_, err = startV2Ray()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf("Test failed: %s", err)
|
base.Fatalf("Test failed: %s", err)
|
||||||
}
|
}
|
||||||
fmt.Println("Configuration OK.")
|
fmt.Println("Configuration OK.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func startV2RayTesting() (core.Server, error) {
|
|
||||||
config, err := core.LoadConfig(*configFormat, 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
|
|
||||||
}
|
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
// CmdVersion prints V2Ray Versions
|
// CmdVersion prints V2Ray Versions
|
||||||
var CmdVersion = &base.Command{
|
var CmdVersion = &base.Command{
|
||||||
UsageLine: "{{.Exec}} version",
|
UsageLine: "{{.Exec}} version",
|
||||||
Short: "Print V2Ray Versions",
|
Short: "print V2Ray version",
|
||||||
Long: `Prints the build information for V2Ray.
|
Long: `Prints the build information for V2Ray.
|
||||||
`,
|
`,
|
||||||
Run: executeVersion,
|
Run: executeVersion,
|
||||||
|
@ -56,14 +56,8 @@ import (
|
|||||||
_ "github.com/v2fly/v2ray-core/v4/transport/internet/headers/wechat"
|
_ "github.com/v2fly/v2ray-core/v4/transport/internet/headers/wechat"
|
||||||
_ "github.com/v2fly/v2ray-core/v4/transport/internet/headers/wireguard"
|
_ "github.com/v2fly/v2ray-core/v4/transport/internet/headers/wireguard"
|
||||||
|
|
||||||
// JSON config support.
|
// JSON, TOML, YAML config support.
|
||||||
_ "github.com/v2fly/v2ray-core/v4/main/json"
|
_ "github.com/v2fly/v2ray-core/v4/main/formats"
|
||||||
|
|
||||||
// TOML config support.
|
|
||||||
_ "github.com/v2fly/v2ray-core/v4/main/toml"
|
|
||||||
|
|
||||||
// YAML config support.
|
|
||||||
_ "github.com/v2fly/v2ray-core/v4/main/yaml"
|
|
||||||
|
|
||||||
// commands
|
// commands
|
||||||
_ "github.com/v2fly/v2ray-core/v4/main/commands/all"
|
_ "github.com/v2fly/v2ray-core/v4/main/commands/all"
|
||||||
|
9
main/formats/errors.generated.go
Normal file
9
main/formats/errors.generated.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package formats
|
||||||
|
|
||||||
|
import "github.com/v2fly/v2ray-core/v4/common/errors"
|
||||||
|
|
||||||
|
type errPathObjHolder struct{}
|
||||||
|
|
||||||
|
func newError(values ...interface{}) *errors.Error {
|
||||||
|
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||||
|
}
|
56
main/formats/formats.go
Normal file
56
main/formats/formats.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package formats
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
core "github.com/v2fly/v2ray-core/v4"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/common"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/merge"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/mergers"
|
||||||
|
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for _, formatName := range mergers.GetAllNames() {
|
||||||
|
loader, err := makeMergeLoader(formatName)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if formatName == core.FormatAuto {
|
||||||
|
loader.Extension = nil
|
||||||
|
}
|
||||||
|
common.Must(core.RegisterConfigLoader(loader))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeMergeLoader(formatName string) (*core.ConfigFormat, error) {
|
||||||
|
extenstoins, err := mergers.GetExtensions(formatName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &core.ConfigFormat{
|
||||||
|
Name: []string{formatName},
|
||||||
|
Extension: extenstoins,
|
||||||
|
Loader: makeLoaderFunc(formatName),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeLoaderFunc(formatName string) core.ConfigLoader {
|
||||||
|
return func(input interface{}) (*core.Config, error) {
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
err := mergers.MergeAs(formatName, input, m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data, err := merge.FromMap(m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r := bytes.NewReader(data)
|
||||||
|
cf, err := serial.DecodeJSONConfig(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cf.Build()
|
||||||
|
}
|
||||||
|
}
|
@ -1,38 +0,0 @@
|
|||||||
package json
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
core "github.com/v2fly/v2ray-core/v4"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/common"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/infra/conf/merge"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
common.Must(core.RegisterConfigLoader(&core.ConfigFormat{
|
|
||||||
Name: []string{"JSON"},
|
|
||||||
Extension: []string{".json", ".jsonc"},
|
|
||||||
Loader: func(input interface{}) (*core.Config, error) {
|
|
||||||
switch v := input.(type) {
|
|
||||||
case cmdarg.Arg:
|
|
||||||
data, err := merge.FilesToJSON(v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r := bytes.NewReader(data)
|
|
||||||
cf, err := serial.DecodeJSONConfig(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return cf.Build()
|
|
||||||
case io.Reader:
|
|
||||||
return serial.LoadJSONConfig(v)
|
|
||||||
default:
|
|
||||||
return nil, newError("unknow type")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
package toml
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
core "github.com/v2fly/v2ray-core/v4"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/common"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/infra/conf/json"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/infra/conf/merge"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
common.Must(core.RegisterConfigLoader(&core.ConfigFormat{
|
|
||||||
Name: []string{"TOML"},
|
|
||||||
Extension: []string{".toml"},
|
|
||||||
Loader: func(input interface{}) (*core.Config, error) {
|
|
||||||
switch v := input.(type) {
|
|
||||||
case cmdarg.Arg:
|
|
||||||
bs, err := tomlsToJSONs(v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data, err := merge.BytesToJSON(bs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r := bytes.NewReader(data)
|
|
||||||
cf, err := serial.DecodeJSONConfig(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return cf.Build()
|
|
||||||
case io.Reader:
|
|
||||||
bs, err := ioutil.ReadAll(v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
bs, err = json.FromTOML(bs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return serial.LoadJSONConfig(bytes.NewBuffer(bs))
|
|
||||||
default:
|
|
||||||
return nil, errors.New("unknow type")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
func tomlsToJSONs(files []string) ([][]byte, error) {
|
|
||||||
jsons := make([][]byte, 0)
|
|
||||||
for _, file := range files {
|
|
||||||
bs, err := cmdarg.LoadArgToBytes(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
j, err := json.FromTOML(bs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
jsons = append(jsons, j)
|
|
||||||
}
|
|
||||||
return jsons, nil
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
package yaml
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
core "github.com/v2fly/v2ray-core/v4"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/common"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/common/cmdarg"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/infra/conf/json"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/infra/conf/merge"
|
|
||||||
"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
common.Must(core.RegisterConfigLoader(&core.ConfigFormat{
|
|
||||||
Name: []string{"YAML"},
|
|
||||||
Extension: []string{".yml", ".yaml"},
|
|
||||||
Loader: func(input interface{}) (*core.Config, error) {
|
|
||||||
switch v := input.(type) {
|
|
||||||
case cmdarg.Arg:
|
|
||||||
bs, err := yamlsToJSONs(v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data, err := merge.BytesToJSON(bs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r := bytes.NewReader(data)
|
|
||||||
cf, err := serial.DecodeJSONConfig(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return cf.Build()
|
|
||||||
case io.Reader:
|
|
||||||
bs, err := ioutil.ReadAll(v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
bs, err = json.FromYAML(bs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return serial.LoadJSONConfig(bytes.NewBuffer(bs))
|
|
||||||
default:
|
|
||||||
return nil, errors.New("unknow type")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
func yamlsToJSONs(files []string) ([][]byte, error) {
|
|
||||||
jsons := make([][]byte, 0)
|
|
||||||
for _, file := range files {
|
|
||||||
bs, err := cmdarg.LoadArgToBytes(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
j, err := json.FromYAML(bs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
jsons = append(jsons, j)
|
|
||||||
}
|
|
||||||
return jsons, nil
|
|
||||||
}
|
|
@ -27,7 +27,7 @@ func RunV2RayProtobuf(config []byte) *exec.Cmd {
|
|||||||
os.MkdirAll(covDir, os.ModeDir)
|
os.MkdirAll(covDir, os.ModeDir)
|
||||||
randomID := uuid.New()
|
randomID := uuid.New()
|
||||||
profile := randomID.String() + ".out"
|
profile := randomID.String() + ".out"
|
||||||
proc := exec.Command(testBinaryPath, "run", "-config=stdin:", "-format=pb", "-test.run", "TestRunMainForCoverage", "-test.coverprofile", profile, "-test.outputdir", covDir)
|
proc := exec.Command(testBinaryPath, "run", "-format=pb", "-test.run", "TestRunMainForCoverage", "-test.coverprofile", profile, "-test.outputdir", covDir)
|
||||||
proc.Stdin = bytes.NewBuffer(config)
|
proc.Stdin = bytes.NewBuffer(config)
|
||||||
proc.Stderr = os.Stderr
|
proc.Stderr = os.Stderr
|
||||||
proc.Stdout = os.Stdout
|
proc.Stdout = os.Stdout
|
||||||
|
@ -22,7 +22,7 @@ func BuildV2Ray() error {
|
|||||||
|
|
||||||
func RunV2RayProtobuf(config []byte) *exec.Cmd {
|
func RunV2RayProtobuf(config []byte) *exec.Cmd {
|
||||||
genTestBinaryPath()
|
genTestBinaryPath()
|
||||||
proc := exec.Command(testBinaryPath, "run", "-config=stdin:", "-format=pb")
|
proc := exec.Command(testBinaryPath, "run", "-format=pb")
|
||||||
proc.Stdin = bytes.NewBuffer(config)
|
proc.Stdin = bytes.NewBuffer(config)
|
||||||
proc.Stderr = os.Stderr
|
proc.Stderr = os.Stderr
|
||||||
proc.Stdout = os.Stdout
|
proc.Stdout = os.Stdout
|
||||||
|
@ -85,7 +85,7 @@ func TestV2RayClose(t *testing.T) {
|
|||||||
cfgBytes, err := proto.Marshal(config)
|
cfgBytes, err := proto.Marshal(config)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
server, err := StartInstance("protobuf", cfgBytes)
|
server, err := StartInstance(FormatProtobuf, cfgBytes)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
server.Close()
|
server.Close()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user