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  }