Xray-core/main/run.go

210 lines
4.9 KiB
Go

package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"os/signal"
"path"
"path/filepath"
"runtime"
"runtime/debug"
"strings"
"syscall"
"github.com/golang/protobuf/proto"
"github.com/xtls/xray-core/app/proxyman"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/cmdarg"
"github.com/xtls/xray-core/common/platform"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/infra/conf"
"github.com/xtls/xray-core/main/commands/base"
)
var cmdRun = &base.Command{
UsageLine: "{{.Exec}} run [-c config.json] [-confdir dir]",
Short: "Run Xray with config, the default command",
Long: `
Run Xray with config, the default command.
The -config=file, -c=file flags set the config files for
Xray. Multiple assign is accepted.
The -confdir=dir flag sets a dir with multiple json config
The -format=json flag sets the format of config files.
Default "json".
The -test flag tells Xray to test config files only,
without launching the server
`,
}
func init() {
cmdRun.Run = executeRun //break init loop
}
var (
configFiles cmdarg.Arg // "Config file for Xray.", the option is customed type, parse in main
configDir string
test = cmdRun.Flag.Bool("test", false, "Test config file only, without launching Xray server.")
format = cmdRun.Flag.String("format", "json", "Format of input file.")
/* We have to do this here because Golang's Test will also need to parse flag, before
* main func in this file is run.
*/
_ = func() bool {
cmdRun.Flag.Var(&configFiles, "config", "Config path for Xray.")
cmdRun.Flag.Var(&configFiles, "c", "Short alias of -config")
cmdRun.Flag.StringVar(&configDir, "confdir", "", "A dir with multiple json config")
return true
}()
)
func executeRun(cmd *base.Command, args []string) {
printVersion()
server, err := startXray()
if err != nil {
fmt.Println("Failed to start:", err)
// Configuration error. Exit with a special value to prevent systemd from restarting.
os.Exit(23)
}
if *test {
fmt.Println("Configuration OK.")
os.Exit(0)
}
if err := server.Start(); err != nil {
fmt.Println("Failed to start:", err)
os.Exit(-1)
}
defer server.Close()
conf.FileCache = nil
conf.IPCache = nil
conf.SiteCache = nil
// Explicitly triggering GC to remove garbage from config loading.
runtime.GC()
debug.FreeOSMemory()
{
osSignals := make(chan os.Signal, 1)
signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM)
<-osSignals
}
}
func fileExists(file string) bool {
info, err := os.Stat(file)
return err == nil && !info.IsDir()
}
func dirExists(file string) bool {
if file == "" {
return false
}
info, err := os.Stat(file)
return err == nil && info.IsDir()
}
func readConfDir(dirPath string) {
confs, err := ioutil.ReadDir(dirPath)
if err != nil {
log.Fatalln(err)
}
for _, f := range confs {
if strings.HasSuffix(f.Name(), ".json") {
configFiles.Set(path.Join(dirPath, f.Name()))
}
}
}
func getConfigFilePath() cmdarg.Arg {
if dirExists(configDir) {
log.Println("Using confdir from arg:", configDir)
readConfDir(configDir)
} else if envConfDir := platform.GetConfDirPath(); dirExists(envConfDir) {
log.Println("Using confdir from env:", envConfDir)
readConfDir(envConfDir)
}
if len(configFiles) > 0 {
return configFiles
}
if workingDir, err := os.Getwd(); err == nil {
configFile := filepath.Join(workingDir, "config.json")
if fileExists(configFile) {
log.Println("Using default config: ", configFile)
return cmdarg.Arg{configFile}
}
}
if configFile := platform.GetConfigurationPath(); fileExists(configFile) {
log.Println("Using config from env: ", configFile)
return cmdarg.Arg{configFile}
}
log.Println("Using config from STDIN")
return cmdarg.Arg{"stdin:"}
}
func getConfigFormat() string {
switch strings.ToLower(*format) {
case "pb", "protobuf":
return "protobuf"
case "yaml", "yml":
return "yaml"
case "toml":
return "toml"
default:
return "json"
}
}
func startXray() (core.Server, error) {
configFiles := getConfigFilePath()
config, err := core.LoadConfig(getConfigFormat(), configFiles[0], configFiles)
//config, err := core.LoadConfigs(getConfigFormat(), configFiles)
if err != nil {
return nil, newError("failed to load config files: [", configFiles.String(), "]").Base(err)
}
v, t := false, false
for _, outbound := range config.Outbound {
s := strings.ToLower(outbound.ProxySettings.Type)
l := len(s)
if l >= 16 && s[11:16] == "vless" || l >= 16 && s[11:16] == "vmess" {
v = true
continue
}
if l >= 17 && s[11:17] == "trojan" || l >= 22 && s[11:22] == "shadowsocks" {
var m proxyman.SenderConfig
proto.Unmarshal(outbound.SenderSettings.Value, &m)
if m.MultiplexSettings == nil || !m.MultiplexSettings.Enabled {
t = true
}
}
}
if v && !t {
buf.Cone = false
}
server, err := core.New(config)
if err != nil {
return nil, newError("failed to create server").Base(err)
}
return server, nil
}