|
@@ -5,10 +5,14 @@ import (
|
|
|
"io"
|
|
|
"os"
|
|
|
"os/signal"
|
|
|
+ "path/filepath"
|
|
|
runtimeDebug "runtime/debug"
|
|
|
+ "sort"
|
|
|
+ "strings"
|
|
|
"syscall"
|
|
|
|
|
|
"github.com/sagernet/sing-box"
|
|
|
+ "github.com/sagernet/sing-box/common/badjsonmerge"
|
|
|
"github.com/sagernet/sing-box/log"
|
|
|
"github.com/sagernet/sing-box/option"
|
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
@@ -31,29 +35,85 @@ func init() {
|
|
|
mainCommand.AddCommand(commandRun)
|
|
|
}
|
|
|
|
|
|
-func readConfig() (option.Options, error) {
|
|
|
+type OptionsEntry struct {
|
|
|
+ content []byte
|
|
|
+ path string
|
|
|
+ options option.Options
|
|
|
+}
|
|
|
+
|
|
|
+func readConfigAt(path string) (*OptionsEntry, error) {
|
|
|
var (
|
|
|
configContent []byte
|
|
|
err error
|
|
|
)
|
|
|
- if configPath == "stdin" {
|
|
|
+ if path == "stdin" {
|
|
|
configContent, err = io.ReadAll(os.Stdin)
|
|
|
} else {
|
|
|
- configContent, err = os.ReadFile(configPath)
|
|
|
+ configContent, err = os.ReadFile(path)
|
|
|
}
|
|
|
if err != nil {
|
|
|
- return option.Options{}, E.Cause(err, "read config")
|
|
|
+ return nil, E.Cause(err, "read config at ", path)
|
|
|
}
|
|
|
var options option.Options
|
|
|
err = options.UnmarshalJSON(configContent)
|
|
|
if err != nil {
|
|
|
- return option.Options{}, E.Cause(err, "decode config")
|
|
|
+ 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
|
|
|
+ }
|
|
|
+ var mergedOptions option.Options
|
|
|
+ for _, options := range optionsList {
|
|
|
+ mergedOptions, err = badjsonmerge.MergeOptions(options.options, mergedOptions)
|
|
|
+ if err != nil {
|
|
|
+ return option.Options{}, E.Cause(err, "merge config at ", options.path)
|
|
|
+ }
|
|
|
}
|
|
|
- return options, nil
|
|
|
+ return mergedOptions, nil
|
|
|
}
|
|
|
|
|
|
func create() (*box.Box, context.CancelFunc, error) {
|
|
|
- options, err := readConfig()
|
|
|
+ options, err := readConfigAndMerge()
|
|
|
if err != nil {
|
|
|
return nil, nil, err
|
|
|
}
|