cmd_run.go 4.6 KB

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