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 }