123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- package main
- import (
- "context"
- "io"
- "os"
- "os/signal"
- "path/filepath"
- runtimeDebug "runtime/debug"
- "sort"
- "strings"
- "syscall"
- "time"
- "github.com/sagernet/sing-box"
- C "github.com/sagernet/sing-box/constant"
- "github.com/sagernet/sing-box/log"
- "github.com/sagernet/sing-box/option"
- E "github.com/sagernet/sing/common/exceptions"
- "github.com/sagernet/sing/common/json"
- "github.com/sagernet/sing/common/json/badjson"
- "github.com/spf13/cobra"
- )
- var commandRun = &cobra.Command{
- Use: "run",
- Short: "Run service",
- Run: func(cmd *cobra.Command, args []string) {
- err := run()
- if err != nil {
- log.Fatal(err)
- }
- },
- }
- func init() {
- mainCommand.AddCommand(commandRun)
- }
- type OptionsEntry struct {
- content []byte
- path string
- options option.Options
- }
- func readConfigAt(path string) (*OptionsEntry, error) {
- var (
- configContent []byte
- err error
- )
- if path == "stdin" {
- configContent, err = io.ReadAll(os.Stdin)
- } else {
- configContent, err = os.ReadFile(path)
- }
- if err != nil {
- return nil, E.Cause(err, "read config at ", path)
- }
- options, err := json.UnmarshalExtended[option.Options](configContent)
- if err != nil {
- return nil, E.Cause(err, "decode config at ", path)
- }
- return &OptionsEntry{
- content: configContent,
- path: path,
- options: options,
- }, nil
- }
- func readConfig() ([]*OptionsEntry, error) {
- var optionsList []*OptionsEntry
- for _, path := range configPaths {
- optionsEntry, err := readConfigAt(path)
- if err != nil {
- return nil, err
- }
- optionsList = append(optionsList, optionsEntry)
- }
- for _, directory := range configDirectories {
- entries, err := os.ReadDir(directory)
- if err != nil {
- return nil, E.Cause(err, "read config directory at ", directory)
- }
- for _, entry := range entries {
- if !strings.HasSuffix(entry.Name(), ".json") || entry.IsDir() {
- continue
- }
- optionsEntry, err := readConfigAt(filepath.Join(directory, entry.Name()))
- if err != nil {
- return nil, err
- }
- optionsList = append(optionsList, optionsEntry)
- }
- }
- sort.Slice(optionsList, func(i, j int) bool {
- return optionsList[i].path < optionsList[j].path
- })
- return optionsList, nil
- }
- func readConfigAndMerge() (option.Options, error) {
- optionsList, err := readConfig()
- if err != nil {
- return option.Options{}, err
- }
- if len(optionsList) == 1 {
- return optionsList[0].options, nil
- }
- var mergedMessage json.RawMessage
- for _, options := range optionsList {
- mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage, false)
- if err != nil {
- return option.Options{}, E.Cause(err, "merge config at ", options.path)
- }
- }
- var mergedOptions option.Options
- err = mergedOptions.UnmarshalJSON(mergedMessage)
- if err != nil {
- return option.Options{}, E.Cause(err, "unmarshal merged config")
- }
- return mergedOptions, nil
- }
- func create() (*box.Box, context.CancelFunc, error) {
- options, err := readConfigAndMerge()
- if err != nil {
- return nil, nil, err
- }
- if disableColor {
- if options.Log == nil {
- options.Log = &option.LogOptions{}
- }
- options.Log.DisableColor = true
- }
- ctx, cancel := context.WithCancel(globalCtx)
- instance, err := box.New(box.Options{
- Context: ctx,
- Options: options,
- })
- if err != nil {
- cancel()
- return nil, nil, E.Cause(err, "create service")
- }
- osSignals := make(chan os.Signal, 1)
- signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
- defer func() {
- signal.Stop(osSignals)
- close(osSignals)
- }()
- startCtx, finishStart := context.WithCancel(context.Background())
- go func() {
- _, loaded := <-osSignals
- if loaded {
- cancel()
- closeMonitor(startCtx)
- }
- }()
- err = instance.Start()
- finishStart()
- if err != nil {
- cancel()
- return nil, nil, E.Cause(err, "start service")
- }
- return instance, cancel, nil
- }
- func run() error {
- osSignals := make(chan os.Signal, 1)
- signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
- defer signal.Stop(osSignals)
- for {
- instance, cancel, err := create()
- if err != nil {
- return err
- }
- runtimeDebug.FreeOSMemory()
- for {
- osSignal := <-osSignals
- if osSignal == syscall.SIGHUP {
- err = check()
- if err != nil {
- log.Error(E.Cause(err, "reload service"))
- continue
- }
- }
- cancel()
- closeCtx, closed := context.WithCancel(context.Background())
- go closeMonitor(closeCtx)
- err = instance.Close()
- closed()
- if osSignal != syscall.SIGHUP {
- if err != nil {
- log.Error(E.Cause(err, "sing-box did not closed properly"))
- }
- return nil
- }
- break
- }
- }
- }
- func closeMonitor(ctx context.Context) {
- time.Sleep(C.FatalStopTimeout)
- select {
- case <-ctx.Done():
- return
- default:
- }
- log.Fatal("sing-box did not close!")
- }
|