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