github.com/sagernet/sing-box@v1.9.0-rc.20/cmd/sing-box/cmd_run.go (about) 1 package main 2 3 import ( 4 "context" 5 "io" 6 "os" 7 "os/signal" 8 "path/filepath" 9 runtimeDebug "runtime/debug" 10 "sort" 11 "strings" 12 "syscall" 13 "time" 14 15 "github.com/sagernet/sing-box" 16 C "github.com/sagernet/sing-box/constant" 17 "github.com/sagernet/sing-box/log" 18 "github.com/sagernet/sing-box/option" 19 E "github.com/sagernet/sing/common/exceptions" 20 "github.com/sagernet/sing/common/json" 21 "github.com/sagernet/sing/common/json/badjson" 22 23 "github.com/spf13/cobra" 24 ) 25 26 var commandRun = &cobra.Command{ 27 Use: "run", 28 Short: "Run service", 29 Run: func(cmd *cobra.Command, args []string) { 30 err := run() 31 if err != nil { 32 log.Fatal(err) 33 } 34 }, 35 } 36 37 func init() { 38 mainCommand.AddCommand(commandRun) 39 } 40 41 type OptionsEntry struct { 42 content []byte 43 path string 44 options option.Options 45 } 46 47 func readConfigAt(path string) (*OptionsEntry, error) { 48 var ( 49 configContent []byte 50 err error 51 ) 52 if path == "stdin" { 53 configContent, err = io.ReadAll(os.Stdin) 54 } else { 55 configContent, err = os.ReadFile(path) 56 } 57 if err != nil { 58 return nil, E.Cause(err, "read config at ", path) 59 } 60 options, err := json.UnmarshalExtended[option.Options](configContent) 61 if err != nil { 62 return nil, E.Cause(err, "decode config at ", path) 63 } 64 return &OptionsEntry{ 65 content: configContent, 66 path: path, 67 options: options, 68 }, nil 69 } 70 71 func readConfig() ([]*OptionsEntry, error) { 72 var optionsList []*OptionsEntry 73 for _, path := range configPaths { 74 optionsEntry, err := readConfigAt(path) 75 if err != nil { 76 return nil, err 77 } 78 optionsList = append(optionsList, optionsEntry) 79 } 80 for _, directory := range configDirectories { 81 entries, err := os.ReadDir(directory) 82 if err != nil { 83 return nil, E.Cause(err, "read config directory at ", directory) 84 } 85 for _, entry := range entries { 86 if !strings.HasSuffix(entry.Name(), ".json") || entry.IsDir() { 87 continue 88 } 89 optionsEntry, err := readConfigAt(filepath.Join(directory, entry.Name())) 90 if err != nil { 91 return nil, err 92 } 93 optionsList = append(optionsList, optionsEntry) 94 } 95 } 96 sort.Slice(optionsList, func(i, j int) bool { 97 return optionsList[i].path < optionsList[j].path 98 }) 99 return optionsList, nil 100 } 101 102 func readConfigAndMerge() (option.Options, error) { 103 optionsList, err := readConfig() 104 if err != nil { 105 return option.Options{}, err 106 } 107 if len(optionsList) == 1 { 108 return optionsList[0].options, nil 109 } 110 var mergedMessage json.RawMessage 111 for _, options := range optionsList { 112 mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage) 113 if err != nil { 114 return option.Options{}, E.Cause(err, "merge config at ", options.path) 115 } 116 } 117 var mergedOptions option.Options 118 err = mergedOptions.UnmarshalJSON(mergedMessage) 119 if err != nil { 120 return option.Options{}, E.Cause(err, "unmarshal merged config") 121 } 122 return mergedOptions, nil 123 } 124 125 func create() (*box.Box, context.CancelFunc, error) { 126 options, err := readConfigAndMerge() 127 if err != nil { 128 return nil, nil, err 129 } 130 if disableColor { 131 if options.Log == nil { 132 options.Log = &option.LogOptions{} 133 } 134 options.Log.DisableColor = true 135 } 136 ctx, cancel := context.WithCancel(globalCtx) 137 instance, err := box.New(box.Options{ 138 Context: ctx, 139 Options: options, 140 }) 141 if err != nil { 142 cancel() 143 return nil, nil, E.Cause(err, "create service") 144 } 145 146 osSignals := make(chan os.Signal, 1) 147 signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) 148 defer func() { 149 signal.Stop(osSignals) 150 close(osSignals) 151 }() 152 startCtx, finishStart := context.WithCancel(context.Background()) 153 go func() { 154 _, loaded := <-osSignals 155 if loaded { 156 cancel() 157 closeMonitor(startCtx) 158 } 159 }() 160 err = instance.Start() 161 finishStart() 162 if err != nil { 163 cancel() 164 return nil, nil, E.Cause(err, "start service") 165 } 166 return instance, cancel, nil 167 } 168 169 func run() error { 170 osSignals := make(chan os.Signal, 1) 171 signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) 172 defer signal.Stop(osSignals) 173 for { 174 instance, cancel, err := create() 175 if err != nil { 176 return err 177 } 178 runtimeDebug.FreeOSMemory() 179 for { 180 osSignal := <-osSignals 181 if osSignal == syscall.SIGHUP { 182 err = check() 183 if err != nil { 184 log.Error(E.Cause(err, "reload service")) 185 continue 186 } 187 } 188 cancel() 189 closeCtx, closed := context.WithCancel(context.Background()) 190 go closeMonitor(closeCtx) 191 instance.Close() 192 closed() 193 if osSignal != syscall.SIGHUP { 194 return nil 195 } 196 break 197 } 198 } 199 } 200 201 func closeMonitor(ctx context.Context) { 202 time.Sleep(C.FatalStopTimeout) 203 select { 204 case <-ctx.Done(): 205 return 206 default: 207 } 208 log.Fatal("sing-box did not close!") 209 }