github.com/xtls/xray-core@v1.8.12-0.20240518155711-3168d27b0bdb/main/run.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "log" 6 "os" 7 "os/signal" 8 "path" 9 "path/filepath" 10 "regexp" 11 "runtime" 12 "runtime/debug" 13 "strings" 14 "syscall" 15 "time" 16 17 "github.com/xtls/xray-core/common/cmdarg" 18 clog "github.com/xtls/xray-core/common/log" 19 "github.com/xtls/xray-core/common/platform" 20 "github.com/xtls/xray-core/core" 21 "github.com/xtls/xray-core/main/commands/base" 22 ) 23 24 var cmdRun = &base.Command{ 25 UsageLine: "{{.Exec}} run [-c config.json] [-confdir dir]", 26 Short: "Run Xray with config, the default command", 27 Long: ` 28 Run Xray with config, the default command. 29 30 The -config=file, -c=file flags set the config files for 31 Xray. Multiple assign is accepted. 32 33 The -confdir=dir flag sets a dir with multiple json config 34 35 The -format=json flag sets the format of config files. 36 Default "auto". 37 38 The -test flag tells Xray to test config files only, 39 without launching the server. 40 41 The -dump flag tells Xray to print the merged config. 42 `, 43 } 44 45 func init() { 46 cmdRun.Run = executeRun // break init loop 47 } 48 49 var ( 50 configFiles cmdarg.Arg // "Config file for Xray.", the option is customed type, parse in main 51 configDir string 52 dump = cmdRun.Flag.Bool("dump", false, "Dump merged config only, without launching Xray server.") 53 test = cmdRun.Flag.Bool("test", false, "Test config file only, without launching Xray server.") 54 format = cmdRun.Flag.String("format", "auto", "Format of input file.") 55 56 /* We have to do this here because Golang's Test will also need to parse flag, before 57 * main func in this file is run. 58 */ 59 _ = func() bool { 60 cmdRun.Flag.Var(&configFiles, "config", "Config path for Xray.") 61 cmdRun.Flag.Var(&configFiles, "c", "Short alias of -config") 62 cmdRun.Flag.StringVar(&configDir, "confdir", "", "A dir with multiple json config") 63 64 return true 65 }() 66 ) 67 68 func executeRun(cmd *base.Command, args []string) { 69 if *dump { 70 clog.ReplaceWithSeverityLogger(clog.Severity_Warning) 71 errCode := dumpConfig() 72 os.Exit(errCode) 73 } 74 75 printVersion() 76 server, err := startXray() 77 if err != nil { 78 fmt.Println("Failed to start:", err) 79 // Configuration error. Exit with a special value to prevent systemd from restarting. 80 os.Exit(23) 81 } 82 83 if *test { 84 fmt.Println("Configuration OK.") 85 os.Exit(0) 86 } 87 88 if err := server.Start(); err != nil { 89 fmt.Println("Failed to start:", err) 90 os.Exit(-1) 91 } 92 defer server.Close() 93 94 /* 95 conf.FileCache = nil 96 conf.IPCache = nil 97 conf.SiteCache = nil 98 */ 99 100 // Explicitly triggering GC to remove garbage from config loading. 101 runtime.GC() 102 debug.FreeOSMemory() 103 104 { 105 osSignals := make(chan os.Signal, 1) 106 signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM) 107 <-osSignals 108 } 109 } 110 111 func dumpConfig() int { 112 files := getConfigFilePath(false) 113 if config, err := core.GetMergedConfig(files); err != nil { 114 fmt.Println(err) 115 time.Sleep(1 * time.Second) 116 return 23 117 } else { 118 fmt.Print(config) 119 } 120 return 0 121 } 122 123 func fileExists(file string) bool { 124 info, err := os.Stat(file) 125 return err == nil && !info.IsDir() 126 } 127 128 func dirExists(file string) bool { 129 if file == "" { 130 return false 131 } 132 info, err := os.Stat(file) 133 return err == nil && info.IsDir() 134 } 135 136 func getRegepxByFormat() string { 137 switch strings.ToLower(*format) { 138 case "json": 139 return `^.+\.(json|jsonc)$` 140 case "toml": 141 return `^.+\.toml$` 142 case "yaml", "yml": 143 return `^.+\.(yaml|yml)$` 144 default: 145 return `^.+\.(json|jsonc|toml|yaml|yml)$` 146 } 147 } 148 149 func readConfDir(dirPath string) { 150 confs, err := os.ReadDir(dirPath) 151 if err != nil { 152 log.Fatalln(err) 153 } 154 for _, f := range confs { 155 matched, err := regexp.MatchString(getRegepxByFormat(), f.Name()) 156 if err != nil { 157 log.Fatalln(err) 158 } 159 if matched { 160 configFiles.Set(path.Join(dirPath, f.Name())) 161 } 162 } 163 } 164 165 func getConfigFilePath(verbose bool) cmdarg.Arg { 166 if dirExists(configDir) { 167 if verbose { 168 log.Println("Using confdir from arg:", configDir) 169 } 170 readConfDir(configDir) 171 } else if envConfDir := platform.GetConfDirPath(); dirExists(envConfDir) { 172 if verbose { 173 log.Println("Using confdir from env:", envConfDir) 174 } 175 readConfDir(envConfDir) 176 } 177 178 if len(configFiles) > 0 { 179 return configFiles 180 } 181 182 if workingDir, err := os.Getwd(); err == nil { 183 configFile := filepath.Join(workingDir, "config.json") 184 if fileExists(configFile) { 185 if verbose { 186 log.Println("Using default config: ", configFile) 187 } 188 return cmdarg.Arg{configFile} 189 } 190 } 191 192 if configFile := platform.GetConfigurationPath(); fileExists(configFile) { 193 if verbose { 194 log.Println("Using config from env: ", configFile) 195 } 196 return cmdarg.Arg{configFile} 197 } 198 199 if verbose { 200 log.Println("Using config from STDIN") 201 } 202 return cmdarg.Arg{"stdin:"} 203 } 204 205 func getConfigFormat() string { 206 f := core.GetFormatByExtension(*format) 207 if f == "" { 208 f = "auto" 209 } 210 return f 211 } 212 213 func startXray() (core.Server, error) { 214 configFiles := getConfigFilePath(true) 215 216 // config, err := core.LoadConfig(getConfigFormat(), configFiles[0], configFiles) 217 218 c, err := core.LoadConfig(getConfigFormat(), configFiles) 219 if err != nil { 220 return nil, newError("failed to load config files: [", configFiles.String(), "]").Base(err) 221 } 222 223 server, err := core.New(c) 224 if err != nil { 225 return nil, newError("failed to create server").Base(err) 226 } 227 228 return server, nil 229 }