main.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. // Package main is the entry point for the 3x-ui web panel application.
  2. // It initializes the database, web server, and handles command-line operations for managing the panel.
  3. package main
  4. import (
  5. "flag"
  6. "fmt"
  7. "log"
  8. "os"
  9. "os/signal"
  10. "syscall"
  11. _ "unsafe"
  12. "github.com/mhsanaei/3x-ui/v2/config"
  13. "github.com/mhsanaei/3x-ui/v2/database"
  14. "github.com/mhsanaei/3x-ui/v2/logger"
  15. "github.com/mhsanaei/3x-ui/v2/sub"
  16. "github.com/mhsanaei/3x-ui/v2/util/crypto"
  17. "github.com/mhsanaei/3x-ui/v2/web"
  18. "github.com/mhsanaei/3x-ui/v2/web/global"
  19. "github.com/mhsanaei/3x-ui/v2/web/service"
  20. "github.com/joho/godotenv"
  21. "github.com/op/go-logging"
  22. )
  23. // runWebServer initializes and starts the web server for the 3x-ui panel.
  24. func runWebServer() {
  25. log.Printf("Starting %v %v", config.GetName(), config.GetVersion())
  26. switch config.GetLogLevel() {
  27. case config.Debug:
  28. logger.InitLogger(logging.DEBUG)
  29. case config.Info:
  30. logger.InitLogger(logging.INFO)
  31. case config.Notice:
  32. logger.InitLogger(logging.NOTICE)
  33. case config.Warning:
  34. logger.InitLogger(logging.WARNING)
  35. case config.Error:
  36. logger.InitLogger(logging.ERROR)
  37. default:
  38. log.Fatalf("Unknown log level: %v", config.GetLogLevel())
  39. }
  40. godotenv.Load()
  41. err := database.InitDB(config.GetDBPath())
  42. if err != nil {
  43. log.Fatalf("Error initializing database: %v", err)
  44. }
  45. var server *web.Server
  46. server = web.NewServer()
  47. global.SetWebServer(server)
  48. err = server.Start()
  49. if err != nil {
  50. log.Fatalf("Error starting web server: %v", err)
  51. return
  52. }
  53. var subServer *sub.Server
  54. subServer = sub.NewServer()
  55. global.SetSubServer(subServer)
  56. err = subServer.Start()
  57. if err != nil {
  58. log.Fatalf("Error starting sub server: %v", err)
  59. return
  60. }
  61. sigCh := make(chan os.Signal, 1)
  62. // Trap shutdown signals
  63. signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM)
  64. for {
  65. sig := <-sigCh
  66. switch sig {
  67. case syscall.SIGHUP:
  68. logger.Info("Received SIGHUP signal. Restarting servers...")
  69. err := server.Stop()
  70. if err != nil {
  71. logger.Debug("Error stopping web server:", err)
  72. }
  73. err = subServer.Stop()
  74. if err != nil {
  75. logger.Debug("Error stopping sub server:", err)
  76. }
  77. server = web.NewServer()
  78. global.SetWebServer(server)
  79. err = server.Start()
  80. if err != nil {
  81. log.Fatalf("Error restarting web server: %v", err)
  82. return
  83. }
  84. log.Println("Web server restarted successfully.")
  85. subServer = sub.NewServer()
  86. global.SetSubServer(subServer)
  87. err = subServer.Start()
  88. if err != nil {
  89. log.Fatalf("Error restarting sub server: %v", err)
  90. return
  91. }
  92. log.Println("Sub server restarted successfully.")
  93. default:
  94. server.Stop()
  95. subServer.Stop()
  96. log.Println("Shutting down servers.")
  97. return
  98. }
  99. }
  100. }
  101. // resetSetting resets all panel settings to their default values.
  102. func resetSetting() {
  103. err := database.InitDB(config.GetDBPath())
  104. if err != nil {
  105. fmt.Println("Failed to initialize database:", err)
  106. return
  107. }
  108. settingService := service.SettingService{}
  109. err = settingService.ResetSettings()
  110. if err != nil {
  111. fmt.Println("Failed to reset settings:", err)
  112. } else {
  113. fmt.Println("Settings successfully reset.")
  114. }
  115. }
  116. // showSetting displays the current panel settings if show is true.
  117. func showSetting(show bool) {
  118. if show {
  119. settingService := service.SettingService{}
  120. port, err := settingService.GetPort()
  121. if err != nil {
  122. fmt.Println("get current port failed, error info:", err)
  123. }
  124. webBasePath, err := settingService.GetBasePath()
  125. if err != nil {
  126. fmt.Println("get webBasePath failed, error info:", err)
  127. }
  128. certFile, err := settingService.GetCertFile()
  129. if err != nil {
  130. fmt.Println("get cert file failed, error info:", err)
  131. }
  132. keyFile, err := settingService.GetKeyFile()
  133. if err != nil {
  134. fmt.Println("get key file failed, error info:", err)
  135. }
  136. userService := service.UserService{}
  137. userModel, err := userService.GetFirstUser()
  138. if err != nil {
  139. fmt.Println("get current user info failed, error info:", err)
  140. }
  141. if userModel.Username == "" || userModel.Password == "" {
  142. fmt.Println("current username or password is empty")
  143. }
  144. fmt.Println("current panel settings as follows:")
  145. if certFile == "" || keyFile == "" {
  146. fmt.Println("Warning: Panel is not secure with SSL")
  147. } else {
  148. fmt.Println("Panel is secure with SSL")
  149. }
  150. hasDefaultCredential := func() bool {
  151. return userModel.Username == "admin" && crypto.CheckPasswordHash(userModel.Password, "admin")
  152. }()
  153. fmt.Println("hasDefaultCredential:", hasDefaultCredential)
  154. fmt.Println("port:", port)
  155. fmt.Println("webBasePath:", webBasePath)
  156. }
  157. }
  158. // updateTgbotEnableSts enables or disables the Telegram bot notifications based on the status parameter.
  159. func updateTgbotEnableSts(status bool) {
  160. settingService := service.SettingService{}
  161. currentTgSts, err := settingService.GetTgbotEnabled()
  162. if err != nil {
  163. fmt.Println(err)
  164. return
  165. }
  166. logger.Infof("current enabletgbot status[%v],need update to status[%v]", currentTgSts, status)
  167. if currentTgSts != status {
  168. err := settingService.SetTgbotEnabled(status)
  169. if err != nil {
  170. fmt.Println(err)
  171. return
  172. } else {
  173. logger.Infof("SetTgbotEnabled[%v] success", status)
  174. }
  175. }
  176. }
  177. // updateTgbotSetting updates Telegram bot settings including token, chat ID, and runtime schedule.
  178. func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime string) {
  179. err := database.InitDB(config.GetDBPath())
  180. if err != nil {
  181. fmt.Println("Error initializing database:", err)
  182. return
  183. }
  184. settingService := service.SettingService{}
  185. if tgBotToken != "" {
  186. err := settingService.SetTgBotToken(tgBotToken)
  187. if err != nil {
  188. fmt.Printf("Error setting Telegram bot token: %v\n", err)
  189. return
  190. }
  191. logger.Info("Successfully updated Telegram bot token.")
  192. }
  193. if tgBotRuntime != "" {
  194. err := settingService.SetTgbotRuntime(tgBotRuntime)
  195. if err != nil {
  196. fmt.Printf("Error setting Telegram bot runtime: %v\n", err)
  197. return
  198. }
  199. logger.Infof("Successfully updated Telegram bot runtime to [%s].", tgBotRuntime)
  200. }
  201. if tgBotChatid != "" {
  202. err := settingService.SetTgBotChatId(tgBotChatid)
  203. if err != nil {
  204. fmt.Printf("Error setting Telegram bot chat ID: %v\n", err)
  205. return
  206. }
  207. logger.Info("Successfully updated Telegram bot chat ID.")
  208. }
  209. }
  210. // updateSetting updates various panel settings including port, credentials, base path, listen IP, and two-factor authentication.
  211. func updateSetting(port int, username string, password string, webBasePath string, listenIP string, resetTwoFactor bool) {
  212. err := database.InitDB(config.GetDBPath())
  213. if err != nil {
  214. fmt.Println("Database initialization failed:", err)
  215. return
  216. }
  217. settingService := service.SettingService{}
  218. userService := service.UserService{}
  219. if port > 0 {
  220. err := settingService.SetPort(port)
  221. if err != nil {
  222. fmt.Println("Failed to set port:", err)
  223. } else {
  224. fmt.Printf("Port set successfully: %v\n", port)
  225. }
  226. }
  227. if username != "" || password != "" {
  228. err := userService.UpdateFirstUser(username, password)
  229. if err != nil {
  230. fmt.Println("Failed to update username and password:", err)
  231. } else {
  232. fmt.Println("Username and password updated successfully")
  233. }
  234. }
  235. if webBasePath != "" {
  236. err := settingService.SetBasePath(webBasePath)
  237. if err != nil {
  238. fmt.Println("Failed to set base URI path:", err)
  239. } else {
  240. fmt.Println("Base URI path set successfully")
  241. }
  242. }
  243. if resetTwoFactor {
  244. err := settingService.SetTwoFactorEnable(false)
  245. if err != nil {
  246. fmt.Println("Failed to reset two-factor authentication:", err)
  247. } else {
  248. settingService.SetTwoFactorToken("")
  249. fmt.Println("Two-factor authentication reset successfully")
  250. }
  251. }
  252. if listenIP != "" {
  253. err := settingService.SetListen(listenIP)
  254. if err != nil {
  255. fmt.Println("Failed to set listen IP:", err)
  256. } else {
  257. fmt.Printf("listen %v set successfully", listenIP)
  258. }
  259. }
  260. }
  261. // updateCert updates the SSL certificate files for the panel.
  262. func updateCert(publicKey string, privateKey string) {
  263. err := database.InitDB(config.GetDBPath())
  264. if err != nil {
  265. fmt.Println(err)
  266. return
  267. }
  268. if (privateKey != "" && publicKey != "") || (privateKey == "" && publicKey == "") {
  269. settingService := service.SettingService{}
  270. err = settingService.SetCertFile(publicKey)
  271. if err != nil {
  272. fmt.Println("set certificate public key failed:", err)
  273. } else {
  274. fmt.Println("set certificate public key success")
  275. }
  276. err = settingService.SetKeyFile(privateKey)
  277. if err != nil {
  278. fmt.Println("set certificate private key failed:", err)
  279. } else {
  280. fmt.Println("set certificate private key success")
  281. }
  282. } else {
  283. fmt.Println("both public and private key should be entered.")
  284. }
  285. }
  286. // GetCertificate displays the current SSL certificate settings if getCert is true.
  287. func GetCertificate(getCert bool) {
  288. if getCert {
  289. settingService := service.SettingService{}
  290. certFile, err := settingService.GetCertFile()
  291. if err != nil {
  292. fmt.Println("get cert file failed, error info:", err)
  293. }
  294. keyFile, err := settingService.GetKeyFile()
  295. if err != nil {
  296. fmt.Println("get key file failed, error info:", err)
  297. }
  298. fmt.Println("cert:", certFile)
  299. fmt.Println("key:", keyFile)
  300. }
  301. }
  302. // GetListenIP displays the current panel listen IP address if getListen is true.
  303. func GetListenIP(getListen bool) {
  304. if getListen {
  305. settingService := service.SettingService{}
  306. ListenIP, err := settingService.GetListen()
  307. if err != nil {
  308. log.Printf("Failed to retrieve listen IP: %v", err)
  309. return
  310. }
  311. fmt.Println("listenIP:", ListenIP)
  312. }
  313. }
  314. // migrateDb performs database migration operations for the 3x-ui panel.
  315. func migrateDb() {
  316. inboundService := service.InboundService{}
  317. err := database.InitDB(config.GetDBPath())
  318. if err != nil {
  319. log.Fatal(err)
  320. }
  321. fmt.Println("Start migrating database...")
  322. inboundService.MigrateDB()
  323. fmt.Println("Migration done!")
  324. }
  325. // main is the entry point of the 3x-ui application.
  326. // It parses command-line arguments to run the web server, migrate database, or update settings.
  327. func main() {
  328. if len(os.Args) < 2 {
  329. runWebServer()
  330. return
  331. }
  332. var showVersion bool
  333. flag.BoolVar(&showVersion, "v", false, "show version")
  334. runCmd := flag.NewFlagSet("run", flag.ExitOnError)
  335. settingCmd := flag.NewFlagSet("setting", flag.ExitOnError)
  336. var port int
  337. var username string
  338. var password string
  339. var webBasePath string
  340. var listenIP string
  341. var getListen bool
  342. var webCertFile string
  343. var webKeyFile string
  344. var tgbottoken string
  345. var tgbotchatid string
  346. var enabletgbot bool
  347. var tgbotRuntime string
  348. var reset bool
  349. var show bool
  350. var getCert bool
  351. var resetTwoFactor bool
  352. settingCmd.BoolVar(&reset, "reset", false, "Reset all settings")
  353. settingCmd.BoolVar(&show, "show", false, "Display current settings")
  354. settingCmd.IntVar(&port, "port", 0, "Set panel port number")
  355. settingCmd.StringVar(&username, "username", "", "Set login username")
  356. settingCmd.StringVar(&password, "password", "", "Set login password")
  357. settingCmd.StringVar(&webBasePath, "webBasePath", "", "Set base path for Panel")
  358. settingCmd.StringVar(&listenIP, "listenIP", "", "set panel listenIP IP")
  359. settingCmd.BoolVar(&resetTwoFactor, "resetTwoFactor", false, "Reset two-factor authentication settings")
  360. settingCmd.BoolVar(&getListen, "getListen", false, "Display current panel listenIP IP")
  361. settingCmd.BoolVar(&getCert, "getCert", false, "Display current certificate settings")
  362. settingCmd.StringVar(&webCertFile, "webCert", "", "Set path to public key file for panel")
  363. settingCmd.StringVar(&webKeyFile, "webCertKey", "", "Set path to private key file for panel")
  364. settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "Set token for Telegram bot")
  365. settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "Set cron time for Telegram bot notifications")
  366. settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "Set chat ID for Telegram bot notifications")
  367. settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "Enable notifications via Telegram bot")
  368. oldUsage := flag.Usage
  369. flag.Usage = func() {
  370. oldUsage()
  371. fmt.Println()
  372. fmt.Println("Commands:")
  373. fmt.Println(" run run web panel")
  374. fmt.Println(" migrate migrate form other/old x-ui")
  375. fmt.Println(" setting set settings")
  376. }
  377. flag.Parse()
  378. if showVersion {
  379. fmt.Println(config.GetVersion())
  380. return
  381. }
  382. switch os.Args[1] {
  383. case "run":
  384. err := runCmd.Parse(os.Args[2:])
  385. if err != nil {
  386. fmt.Println(err)
  387. return
  388. }
  389. runWebServer()
  390. case "migrate":
  391. migrateDb()
  392. case "setting":
  393. err := settingCmd.Parse(os.Args[2:])
  394. if err != nil {
  395. fmt.Println(err)
  396. return
  397. }
  398. if reset {
  399. resetSetting()
  400. } else {
  401. updateSetting(port, username, password, webBasePath, listenIP, resetTwoFactor)
  402. }
  403. if show {
  404. showSetting(show)
  405. }
  406. if getListen {
  407. GetListenIP(getListen)
  408. }
  409. if getCert {
  410. GetCertificate(getCert)
  411. }
  412. if (tgbottoken != "") || (tgbotchatid != "") || (tgbotRuntime != "") {
  413. updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime)
  414. }
  415. if enabletgbot {
  416. updateTgbotEnableSts(enabletgbot)
  417. }
  418. case "cert":
  419. err := settingCmd.Parse(os.Args[2:])
  420. if err != nil {
  421. fmt.Println(err)
  422. return
  423. }
  424. if reset {
  425. updateCert("", "")
  426. } else {
  427. updateCert(webCertFile, webKeyFile)
  428. }
  429. default:
  430. fmt.Println("Invalid subcommands")
  431. fmt.Println()
  432. runCmd.Usage()
  433. fmt.Println()
  434. settingCmd.Usage()
  435. }
  436. }