github.com/kelleygo/clashcore@v1.0.2/main.go (about) 1 package main 2 3 import ( 4 "flag" 5 "fmt" 6 "os" 7 "os/signal" 8 "path/filepath" 9 "runtime" 10 "strings" 11 "sync" 12 "syscall" 13 "time" 14 15 "github.com/kelleygo/clashcore/config" 16 C "github.com/kelleygo/clashcore/constant" 17 "github.com/kelleygo/clashcore/constant/features" 18 "github.com/kelleygo/clashcore/hub" 19 "github.com/kelleygo/clashcore/hub/executor" 20 "github.com/kelleygo/clashcore/log" 21 22 "go.uber.org/automaxprocs/maxprocs" 23 ) 24 25 var ( 26 version bool 27 testConfig bool 28 geodataMode bool 29 homeDir string 30 configFile string 31 externalUI string 32 externalController string 33 externalControllerUnix string 34 secret string 35 updateGeoMux sync.Mutex 36 updatingGeo = false 37 ) 38 39 func init() { 40 flag.StringVar(&homeDir, "d", os.Getenv("CLASH_HOME_DIR"), "set configuration directory") 41 flag.StringVar(&configFile, "f", os.Getenv("CLASH_CONFIG_FILE"), "specify configuration file") 42 flag.StringVar(&externalUI, "ext-ui", os.Getenv("CLASH_OVERRIDE_EXTERNAL_UI_DIR"), "override external ui directory") 43 flag.StringVar(&externalController, "ext-ctl", os.Getenv("CLASH_OVERRIDE_EXTERNAL_CONTROLLER"), "override external controller address") 44 flag.StringVar(&externalControllerUnix, "ext-ctl-unix", os.Getenv("CLASH_OVERRIDE_EXTERNAL_CONTROLLER_UNIX"), "override external controller unix address") 45 flag.StringVar(&secret, "secret", os.Getenv("CLASH_OVERRIDE_SECRET"), "override secret for RESTful API") 46 flag.BoolVar(&geodataMode, "m", false, "set geodata mode") 47 flag.BoolVar(&version, "v", false, "show current version of yiclashcore") 48 flag.BoolVar(&testConfig, "t", false, "test configuration and exit") 49 flag.Parse() 50 } 51 52 func main() { 53 _, _ = maxprocs.Set(maxprocs.Logger(func(string, ...any) {})) 54 if version { 55 fmt.Printf("YiClashCore Meta %s %s %s with %s %s\n", 56 C.Version, runtime.GOOS, runtime.GOARCH, runtime.Version(), C.BuildTime) 57 if tags := features.Tags(); len(tags) != 0 { 58 fmt.Printf("Use tags: %s\n", strings.Join(tags, ", ")) 59 } 60 61 return 62 } 63 64 if homeDir != "" { 65 if !filepath.IsAbs(homeDir) { 66 currentDir, _ := os.Getwd() 67 homeDir = filepath.Join(currentDir, homeDir) 68 } 69 C.SetHomeDir(homeDir) 70 } 71 72 if configFile != "" { 73 if !filepath.IsAbs(configFile) { 74 currentDir, _ := os.Getwd() 75 configFile = filepath.Join(currentDir, configFile) 76 } 77 } else { 78 configFile = filepath.Join(C.Path.HomeDir(), C.Path.Config()) 79 } 80 C.SetConfig(configFile) 81 82 if geodataMode { 83 C.GeodataMode = true 84 } 85 86 if err := config.Init(C.Path.HomeDir()); err != nil { 87 log.Fatalln("Initial configuration directory error: %s", err.Error()) 88 } 89 90 if testConfig { 91 if _, err := executor.Parse(); err != nil { 92 log.Errorln(err.Error()) 93 fmt.Printf("configuration file %s test failed\n", C.Path.Config()) 94 os.Exit(1) 95 } 96 fmt.Printf("configuration file %s test is successful\n", C.Path.Config()) 97 return 98 } 99 100 var options []hub.Option 101 if externalUI != "" { 102 options = append(options, hub.WithExternalUI(externalUI)) 103 } 104 if externalController != "" { 105 options = append(options, hub.WithExternalController(externalController)) 106 } 107 if externalControllerUnix != "" { 108 options = append(options, hub.WithExternalControllerUnix(externalControllerUnix)) 109 } 110 if secret != "" { 111 options = append(options, hub.WithSecret(secret)) 112 } 113 114 if err := hub.Parse(options...); err != nil { 115 log.Fatalln("Parse config error: %s", err.Error()) 116 } 117 118 if C.GeoAutoUpdate { 119 ticker := time.NewTicker(time.Duration(C.GeoUpdateInterval) * time.Hour) 120 121 log.Infoln("[GEO] Start update GEO database every %d hours", C.GeoUpdateInterval) 122 go func() { 123 for range ticker.C { 124 updateGeoDatabases() 125 } 126 }() 127 } 128 129 defer executor.Shutdown() 130 131 termSign := make(chan os.Signal, 1) 132 hupSign := make(chan os.Signal, 1) 133 signal.Notify(termSign, syscall.SIGINT, syscall.SIGTERM) 134 signal.Notify(hupSign, syscall.SIGHUP) 135 for { 136 select { 137 case <-termSign: 138 return 139 case <-hupSign: 140 if cfg, err := executor.ParseWithPath(C.Path.Config()); err == nil { 141 executor.ApplyConfig(cfg, true) 142 } else { 143 log.Errorln("Parse config error: %s", err.Error()) 144 } 145 } 146 } 147 } 148 149 func updateGeoDatabases() { 150 log.Infoln("[GEO] Start updating GEO database") 151 updateGeoMux.Lock() 152 153 if updatingGeo { 154 updateGeoMux.Unlock() 155 log.Infoln("[GEO] GEO database is updating, skip") 156 return 157 } 158 159 updatingGeo = true 160 updateGeoMux.Unlock() 161 162 go func() { 163 defer func() { 164 updatingGeo = false 165 }() 166 167 log.Infoln("[GEO] Updating GEO database") 168 169 if err := config.UpdateGeoDatabases(); err != nil { 170 log.Errorln("[GEO] update GEO database error: %s", err.Error()) 171 return 172 } 173 174 cfg, err := executor.ParseWithPath(C.Path.Config()) 175 if err != nil { 176 log.Errorln("[GEO] update GEO database failed: %s", err.Error()) 177 return 178 } 179 180 log.Infoln("[GEO] Update GEO database success, apply new config") 181 executor.ApplyConfig(cfg, false) 182 }() 183 }