github.com/sagernet/sing-box@v1.9.0-rc.20/cmd/sing-box/cmd_merge.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  
     9  	C "github.com/sagernet/sing-box/constant"
    10  	"github.com/sagernet/sing-box/log"
    11  	"github.com/sagernet/sing-box/option"
    12  	"github.com/sagernet/sing/common"
    13  	E "github.com/sagernet/sing/common/exceptions"
    14  	"github.com/sagernet/sing/common/json"
    15  	"github.com/sagernet/sing/common/rw"
    16  
    17  	"github.com/spf13/cobra"
    18  )
    19  
    20  var commandMerge = &cobra.Command{
    21  	Use:   "merge <output>",
    22  	Short: "Merge configurations",
    23  	Run: func(cmd *cobra.Command, args []string) {
    24  		err := merge(args[0])
    25  		if err != nil {
    26  			log.Fatal(err)
    27  		}
    28  	},
    29  	Args: cobra.ExactArgs(1),
    30  }
    31  
    32  func init() {
    33  	mainCommand.AddCommand(commandMerge)
    34  }
    35  
    36  func merge(outputPath string) error {
    37  	mergedOptions, err := readConfigAndMerge()
    38  	if err != nil {
    39  		return err
    40  	}
    41  	err = mergePathResources(&mergedOptions)
    42  	if err != nil {
    43  		return err
    44  	}
    45  	buffer := new(bytes.Buffer)
    46  	encoder := json.NewEncoder(buffer)
    47  	encoder.SetIndent("", "  ")
    48  	err = encoder.Encode(mergedOptions)
    49  	if err != nil {
    50  		return E.Cause(err, "encode config")
    51  	}
    52  	if existsContent, err := os.ReadFile(outputPath); err != nil {
    53  		if string(existsContent) == buffer.String() {
    54  			return nil
    55  		}
    56  	}
    57  	err = rw.WriteFile(outputPath, buffer.Bytes())
    58  	if err != nil {
    59  		return err
    60  	}
    61  	outputPath, _ = filepath.Abs(outputPath)
    62  	os.Stderr.WriteString(outputPath + "\n")
    63  	return nil
    64  }
    65  
    66  func mergePathResources(options *option.Options) error {
    67  	for index, inbound := range options.Inbounds {
    68  		rawOptions, err := inbound.RawOptions()
    69  		if err != nil {
    70  			return err
    71  		}
    72  		if tlsOptions, containsTLSOptions := rawOptions.(option.InboundTLSOptionsWrapper); containsTLSOptions {
    73  			tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions()))
    74  		}
    75  		options.Inbounds[index] = inbound
    76  	}
    77  	for index, outbound := range options.Outbounds {
    78  		rawOptions, err := outbound.RawOptions()
    79  		if err != nil {
    80  			return err
    81  		}
    82  		switch outbound.Type {
    83  		case C.TypeSSH:
    84  			outbound.SSHOptions = mergeSSHOutboundOptions(outbound.SSHOptions)
    85  		}
    86  		if tlsOptions, containsTLSOptions := rawOptions.(option.OutboundTLSOptionsWrapper); containsTLSOptions {
    87  			tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions()))
    88  		}
    89  		options.Outbounds[index] = outbound
    90  	}
    91  	return nil
    92  }
    93  
    94  func mergeTLSInboundOptions(options *option.InboundTLSOptions) *option.InboundTLSOptions {
    95  	if options == nil {
    96  		return nil
    97  	}
    98  	if options.CertificatePath != "" {
    99  		if content, err := os.ReadFile(options.CertificatePath); err == nil {
   100  			options.Certificate = trimStringArray(strings.Split(string(content), "\n"))
   101  		}
   102  	}
   103  	if options.KeyPath != "" {
   104  		if content, err := os.ReadFile(options.KeyPath); err == nil {
   105  			options.Key = trimStringArray(strings.Split(string(content), "\n"))
   106  		}
   107  	}
   108  	if options.ECH != nil {
   109  		if options.ECH.KeyPath != "" {
   110  			if content, err := os.ReadFile(options.ECH.KeyPath); err == nil {
   111  				options.ECH.Key = trimStringArray(strings.Split(string(content), "\n"))
   112  			}
   113  		}
   114  	}
   115  	return options
   116  }
   117  
   118  func mergeTLSOutboundOptions(options *option.OutboundTLSOptions) *option.OutboundTLSOptions {
   119  	if options == nil {
   120  		return nil
   121  	}
   122  	if options.CertificatePath != "" {
   123  		if content, err := os.ReadFile(options.CertificatePath); err == nil {
   124  			options.Certificate = trimStringArray(strings.Split(string(content), "\n"))
   125  		}
   126  	}
   127  	if options.ECH != nil {
   128  		if options.ECH.ConfigPath != "" {
   129  			if content, err := os.ReadFile(options.ECH.ConfigPath); err == nil {
   130  				options.ECH.Config = trimStringArray(strings.Split(string(content), "\n"))
   131  			}
   132  		}
   133  	}
   134  	return options
   135  }
   136  
   137  func mergeSSHOutboundOptions(options option.SSHOutboundOptions) option.SSHOutboundOptions {
   138  	if options.PrivateKeyPath != "" {
   139  		if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil {
   140  			options.PrivateKey = trimStringArray(strings.Split(string(content), "\n"))
   141  		}
   142  	}
   143  	return options
   144  }
   145  
   146  func trimStringArray(array []string) []string {
   147  	return common.Filter(array, func(it string) bool {
   148  		return strings.TrimSpace(it) != ""
   149  	})
   150  }