main.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  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. "net/http"
  9. _ "net/http/pprof"
  10. "os"
  11. "os/signal"
  12. "syscall"
  13. "time"
  14. _ "unsafe"
  15. "github.com/mhsanaei/3x-ui/v3/internal/config"
  16. "github.com/mhsanaei/3x-ui/v3/internal/database"
  17. "github.com/mhsanaei/3x-ui/v3/internal/logger"
  18. "github.com/mhsanaei/3x-ui/v3/internal/sub"
  19. "github.com/mhsanaei/3x-ui/v3/internal/util/crypto"
  20. "github.com/mhsanaei/3x-ui/v3/internal/util/sys"
  21. "github.com/mhsanaei/3x-ui/v3/internal/web"
  22. "github.com/mhsanaei/3x-ui/v3/internal/web/global"
  23. "github.com/mhsanaei/3x-ui/v3/internal/web/service"
  24. "github.com/mhsanaei/3x-ui/v3/internal/web/service/panel"
  25. "github.com/mhsanaei/3x-ui/v3/internal/web/service/tgbot"
  26. "github.com/joho/godotenv"
  27. "github.com/op/go-logging"
  28. )
  29. // runWebServer initializes and starts the web server for the 3x-ui panel.
  30. func runWebServer() {
  31. log.Printf("Starting %v %v", config.GetName(), config.GetVersion())
  32. switch config.GetLogLevel() {
  33. case config.Debug:
  34. logger.InitLogger(logging.DEBUG)
  35. case config.Info:
  36. logger.InitLogger(logging.INFO)
  37. case config.Notice:
  38. logger.InitLogger(logging.NOTICE)
  39. case config.Warning:
  40. logger.InitLogger(logging.WARNING)
  41. case config.Error:
  42. logger.InitLogger(logging.ERROR)
  43. default:
  44. log.Fatalf("Unknown log level: %v", config.GetLogLevel())
  45. }
  46. godotenv.Load()
  47. if limit, source := sys.ApplyMemoryLimit(); limit > 0 {
  48. logger.Infof("Go memory soft limit set to %d MiB (%s)", limit>>20, source)
  49. } else {
  50. logger.Info("Go memory soft limit not enforced: ", source)
  51. }
  52. if os.Getenv("XUI_PPROF") == "true" {
  53. go func() {
  54. logger.Info("pprof profiling server listening on 127.0.0.1:6060")
  55. if err := http.ListenAndServe("127.0.0.1:6060", nil); err != nil {
  56. logger.Warning("pprof server stopped: ", err)
  57. }
  58. }()
  59. }
  60. err := database.InitDB(config.GetDBPath())
  61. if err != nil {
  62. log.Fatalf("Error initializing database: %v", err)
  63. }
  64. var server *web.Server
  65. server = web.NewServer()
  66. global.SetWebServer(server)
  67. err = server.Start()
  68. if err != nil {
  69. log.Fatalf("Error starting web server: %v", err)
  70. return
  71. }
  72. var subServer *sub.Server
  73. sub.SetDistFS(web.EmbeddedDist())
  74. service.RegisterSubLinkProvider(sub.NewLinkProvider())
  75. subServer = sub.NewServer()
  76. global.SetSubServer(subServer)
  77. err = subServer.Start()
  78. if err != nil {
  79. log.Fatalf("Error starting sub server: %v", err)
  80. return
  81. }
  82. sigCh := make(chan os.Signal, 1)
  83. // Trap shutdown signals
  84. signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM, sys.SIGUSR1, os.Interrupt)
  85. global.SetRestartHook(func() {
  86. select {
  87. case sigCh <- syscall.SIGHUP:
  88. default:
  89. }
  90. })
  91. for {
  92. sig := <-sigCh
  93. switch sig {
  94. case syscall.SIGHUP:
  95. logger.Info("Received SIGHUP signal. Restarting servers...")
  96. err := server.StopPanelOnly()
  97. if err != nil {
  98. logger.Debug("Error stopping web server:", err)
  99. }
  100. err = subServer.Stop()
  101. if err != nil {
  102. logger.Debug("Error stopping sub server:", err)
  103. }
  104. server = web.NewServer()
  105. global.SetWebServer(server)
  106. err = server.StartPanelOnly()
  107. if err != nil {
  108. log.Fatalf("Error restarting web server: %v", err)
  109. return
  110. }
  111. log.Println("Web server restarted successfully.")
  112. sub.SetDistFS(web.EmbeddedDist())
  113. subServer = sub.NewServer()
  114. global.SetSubServer(subServer)
  115. err = subServer.Start()
  116. if err != nil {
  117. log.Fatalf("Error restarting sub server: %v", err)
  118. return
  119. }
  120. log.Println("Sub server restarted successfully.")
  121. case sys.SIGUSR1:
  122. logger.Info("Received USR1 signal, restarting xray-core...")
  123. err := server.RestartXray()
  124. if err != nil {
  125. logger.Error("Failed to restart xray-core:", err)
  126. }
  127. default:
  128. // --- FIX FOR TELEGRAM BOT CONFLICT (409) on full shutdown ---
  129. tgbot.StopBot()
  130. // ------------------------------------------------------------
  131. server.Stop()
  132. subServer.Stop()
  133. log.Println("Shutting down servers.")
  134. return
  135. }
  136. }
  137. }
  138. // resetSetting resets all panel settings to their default values.
  139. func resetSetting() error {
  140. err := database.InitDB(config.GetDBPath())
  141. if err != nil {
  142. fmt.Println("Failed to initialize database:", err)
  143. return err
  144. }
  145. settingService := service.SettingService{}
  146. err = settingService.ResetSettings()
  147. if err != nil {
  148. fmt.Println("Failed to reset settings:", err)
  149. return err
  150. } else {
  151. fmt.Println("Settings successfully reset.")
  152. }
  153. return nil
  154. }
  155. // showSetting displays the current panel settings if show is true.
  156. func showSetting(show bool) {
  157. if show {
  158. settingService := service.SettingService{}
  159. port, err := settingService.GetPort()
  160. if err != nil {
  161. fmt.Println("get current port failed, error info:", err)
  162. }
  163. webBasePath, err := settingService.GetBasePath()
  164. if err != nil {
  165. fmt.Println("get webBasePath failed, error info:", err)
  166. }
  167. certFile, err := settingService.GetCertFile()
  168. if err != nil {
  169. fmt.Println("get cert file failed, error info:", err)
  170. }
  171. keyFile, err := settingService.GetKeyFile()
  172. if err != nil {
  173. fmt.Println("get key file failed, error info:", err)
  174. }
  175. userService := panel.UserService{}
  176. userModel, err := userService.GetFirstUser()
  177. if err != nil {
  178. fmt.Println("get current user info failed, error info:", err)
  179. }
  180. if userModel.Username == "" || userModel.Password == "" {
  181. fmt.Println("current username or password is empty")
  182. }
  183. fmt.Println("current panel settings as follows:")
  184. if certFile == "" || keyFile == "" {
  185. fmt.Println("Warning: Panel is not secure with SSL")
  186. } else {
  187. fmt.Println("Panel is secure with SSL")
  188. }
  189. hasDefaultCredential := func() bool {
  190. return userModel.Username == "admin" && crypto.CheckPasswordHash(userModel.Password, "admin")
  191. }()
  192. fmt.Println("hasDefaultCredential:", hasDefaultCredential)
  193. fmt.Println("port:", port)
  194. fmt.Println("webBasePath:", webBasePath)
  195. }
  196. }
  197. // updateTgbotEnableSts enables or disables the Telegram bot notifications based on the status parameter.
  198. func updateTgbotEnableSts(status bool) {
  199. settingService := service.SettingService{}
  200. currentTgSts, err := settingService.GetTgbotEnabled()
  201. if err != nil {
  202. fmt.Println(err)
  203. return
  204. }
  205. logger.Infof("current enabletgbot status[%v],need update to status[%v]", currentTgSts, status)
  206. if currentTgSts != status {
  207. err := settingService.SetTgbotEnabled(status)
  208. if err != nil {
  209. fmt.Println(err)
  210. return
  211. } else {
  212. logger.Infof("SetTgbotEnabled[%v] success", status)
  213. }
  214. }
  215. }
  216. // updateTgbotSetting updates Telegram bot settings including token, chat ID, and runtime schedule.
  217. func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime string) {
  218. err := database.InitDB(config.GetDBPath())
  219. if err != nil {
  220. fmt.Println("Error initializing database:", err)
  221. return
  222. }
  223. settingService := service.SettingService{}
  224. if tgBotToken != "" {
  225. err := settingService.SetTgBotToken(tgBotToken)
  226. if err != nil {
  227. fmt.Printf("Error setting Telegram bot token: %v\n", err)
  228. return
  229. }
  230. logger.Info("Successfully updated Telegram bot token.")
  231. }
  232. if tgBotRuntime != "" {
  233. err := settingService.SetTgbotRuntime(tgBotRuntime)
  234. if err != nil {
  235. fmt.Printf("Error setting Telegram bot runtime: %v\n", err)
  236. return
  237. }
  238. logger.Infof("Successfully updated Telegram bot runtime to [%s].", tgBotRuntime)
  239. }
  240. if tgBotChatid != "" {
  241. err := settingService.SetTgBotChatId(tgBotChatid)
  242. if err != nil {
  243. fmt.Printf("Error setting Telegram bot chat ID: %v\n", err)
  244. return
  245. }
  246. logger.Info("Successfully updated Telegram bot chat ID.")
  247. }
  248. }
  249. // updateSetting updates various panel settings including port, credentials, base path, listen IP, and two-factor authentication.
  250. func updateSetting(port int, username string, password string, webBasePath string, listenIP string, resetTwoFactor bool) error {
  251. err := database.InitDB(config.GetDBPath())
  252. if err != nil {
  253. fmt.Println("Database initialization failed:", err)
  254. return err
  255. }
  256. settingService := service.SettingService{}
  257. userService := panel.UserService{}
  258. if port > 0 {
  259. err := settingService.SetPort(port)
  260. if err != nil {
  261. fmt.Println("Failed to set port:", err)
  262. } else {
  263. fmt.Printf("Port set successfully: %v\n", port)
  264. }
  265. }
  266. if username != "" || password != "" {
  267. err := userService.UpdateFirstUser(username, password)
  268. if err != nil {
  269. fmt.Println("Failed to update username and password:", err)
  270. } else {
  271. fmt.Println("Username and password updated successfully")
  272. }
  273. }
  274. if webBasePath != "" {
  275. err := settingService.SetBasePath(webBasePath)
  276. if err != nil {
  277. fmt.Println("Failed to set base URI path:", err)
  278. } else {
  279. fmt.Println("Base URI path set successfully")
  280. }
  281. }
  282. if resetTwoFactor {
  283. err := settingService.SetTwoFactorEnable(false)
  284. if err != nil {
  285. fmt.Println("Failed to reset two-factor authentication:", err)
  286. } else {
  287. settingService.SetTwoFactorToken("")
  288. fmt.Println("Two-factor authentication reset successfully")
  289. }
  290. }
  291. if listenIP != "" {
  292. err := settingService.SetListen(listenIP)
  293. if err != nil {
  294. fmt.Println("Failed to set listen IP:", err)
  295. } else {
  296. fmt.Printf("listen %v set successfully", listenIP)
  297. }
  298. }
  299. return nil
  300. }
  301. // updateCert updates the SSL certificate files for the panel.
  302. func updateCert(publicKey string, privateKey string) {
  303. err := database.InitDB(config.GetDBPath())
  304. if err != nil {
  305. fmt.Println(err)
  306. return
  307. }
  308. if (privateKey != "" && publicKey != "") || (privateKey == "" && publicKey == "") {
  309. settingService := service.SettingService{}
  310. err = settingService.SetCertFile(publicKey)
  311. if err != nil {
  312. fmt.Println("set certificate public key failed:", err)
  313. } else {
  314. fmt.Println("set certificate public key success")
  315. }
  316. err = settingService.SetKeyFile(privateKey)
  317. if err != nil {
  318. fmt.Println("set certificate private key failed:", err)
  319. } else {
  320. fmt.Println("set certificate private key success")
  321. }
  322. err = settingService.SetSubCertFile(publicKey)
  323. if err != nil {
  324. fmt.Println("set certificate for subscription public key failed:", err)
  325. } else {
  326. fmt.Println("set certificate for subscription public key success")
  327. }
  328. err = settingService.SetSubKeyFile(privateKey)
  329. if err != nil {
  330. fmt.Println("set certificate for subscription private key failed:", err)
  331. } else {
  332. fmt.Println("set certificate for subscription private key success")
  333. }
  334. } else {
  335. fmt.Println("both public and private key should be entered.")
  336. }
  337. }
  338. // GetCertificate displays the current SSL certificate settings if getCert is true.
  339. func GetCertificate(getCert bool) {
  340. if getCert {
  341. settingService := service.SettingService{}
  342. certFile, err := settingService.GetCertFile()
  343. if err != nil {
  344. fmt.Println("get cert file failed, error info:", err)
  345. }
  346. keyFile, err := settingService.GetKeyFile()
  347. if err != nil {
  348. fmt.Println("get key file failed, error info:", err)
  349. }
  350. fmt.Println("cert:", certFile)
  351. fmt.Println("key:", keyFile)
  352. }
  353. }
  354. // GetListenIP displays the current panel listen IP address if getListen is true.
  355. func GetListenIP(getListen bool) {
  356. if getListen {
  357. settingService := service.SettingService{}
  358. ListenIP, err := settingService.GetListen()
  359. if err != nil {
  360. log.Printf("Failed to retrieve listen IP: %v", err)
  361. return
  362. }
  363. fmt.Println("listenIP:", ListenIP)
  364. }
  365. }
  366. func GetApiToken(getApiToken bool) {
  367. if !getApiToken {
  368. return
  369. }
  370. err := database.InitDB(config.GetDBPath())
  371. if err != nil {
  372. fmt.Println("open database failed, error info:", err)
  373. return
  374. }
  375. apiTokenService := panel.ApiTokenService{}
  376. tokens, err := apiTokenService.List()
  377. if err != nil {
  378. fmt.Println("get apiToken failed, error info:", err)
  379. return
  380. }
  381. if len(tokens) > 0 {
  382. fmt.Printf("There are %d API token(s) configured. Existing tokens cannot be retrieved in plaintext because only hashes are stored.\n", len(tokens))
  383. fmt.Println("If you have lost your token, you can manage and generate new tokens through the Panel UI (Settings -> API Tokens).")
  384. // Create a new fallback token so the CLI is still useful without the UI
  385. fallbackName := fmt.Sprintf("cli-fallback-%d", time.Now().Unix())
  386. created, err := apiTokenService.Create(fallbackName)
  387. if err != nil {
  388. fmt.Println("Failed to create a fallback API token:", err)
  389. return
  390. }
  391. fmt.Println("\nA new fallback token has been generated for your convenience:")
  392. fmt.Println("apiToken:", created.Token)
  393. return
  394. }
  395. created, err := apiTokenService.Create("install")
  396. if err != nil {
  397. fmt.Println("create apiToken failed, error info:", err)
  398. return
  399. }
  400. fmt.Println("apiToken:", created.Token)
  401. }
  402. // migrateDb performs database migration operations for the 3x-ui panel.
  403. func migrateDb() {
  404. inboundService := service.InboundService{}
  405. err := database.InitDB(config.GetDBPath())
  406. if err != nil {
  407. log.Fatal(err)
  408. }
  409. fmt.Println("Start migrating database...")
  410. inboundService.MigrateDB()
  411. fmt.Println("Migration done!")
  412. }
  413. // loadServiceEnvFile loads the systemd EnvironmentFile so CLI subcommands like
  414. // "x-ui setting" hit the same database backend as the panel. godotenv.Load does
  415. // not override variables already in the environment, so it is a no-op for the
  416. // systemd-managed service.
  417. func loadServiceEnvFile() {
  418. for _, path := range config.GetEnvFilePaths() {
  419. if _, err := os.Stat(path); err != nil {
  420. continue
  421. }
  422. if err := godotenv.Load(path); err != nil {
  423. log.Printf("warning: failed to load env file %s: %v", path, err)
  424. }
  425. return
  426. }
  427. }
  428. // main is the entry point of the 3x-ui application.
  429. // It parses command-line arguments to run the web server, migrate database, or update settings.
  430. func main() {
  431. loadServiceEnvFile()
  432. if len(os.Args) < 2 {
  433. runWebServer()
  434. return
  435. }
  436. var showVersion bool
  437. flag.BoolVar(&showVersion, "v", false, "show version")
  438. runCmd := flag.NewFlagSet("run", flag.ExitOnError)
  439. migrateDbCmd := flag.NewFlagSet("migrate-db", flag.ExitOnError)
  440. var migrateDsn string
  441. var migrateSrc string
  442. var migrateDump string
  443. var migrateRestore string
  444. var migrateOut string
  445. migrateDbCmd.StringVar(&migrateDsn, "dsn", "", "Destination PostgreSQL DSN (postgres://user:pass@host:port/db?sslmode=disable)")
  446. migrateDbCmd.StringVar(&migrateSrc, "src", "", "Source SQLite file (defaults to the configured x-ui.db)")
  447. migrateDbCmd.StringVar(&migrateDump, "dump", "", "Write a portable SQL text dump of --src to this file (.db -> .dump)")
  448. migrateDbCmd.StringVar(&migrateRestore, "restore", "", "Rebuild a SQLite database from this SQL text dump (.dump -> .db); requires --out")
  449. migrateDbCmd.StringVar(&migrateOut, "out", "", "Destination SQLite file for --restore (must not already exist)")
  450. settingCmd := flag.NewFlagSet("setting", flag.ExitOnError)
  451. var port int
  452. var username string
  453. var password string
  454. var webBasePath string
  455. var listenIP string
  456. var getListen bool
  457. var webCertFile string
  458. var webKeyFile string
  459. var tgbottoken string
  460. var tgbotchatid string
  461. var enabletgbot bool
  462. var tgbotRuntime string
  463. var reset bool
  464. var show bool
  465. var getCert bool
  466. var getApiToken bool
  467. var resetTwoFactor bool
  468. settingCmd.BoolVar(&reset, "reset", false, "Reset all settings")
  469. settingCmd.BoolVar(&show, "show", false, "Display current settings")
  470. settingCmd.IntVar(&port, "port", 0, "Set panel port number")
  471. settingCmd.StringVar(&username, "username", "", "Set login username")
  472. settingCmd.StringVar(&password, "password", "", "Set login password")
  473. settingCmd.StringVar(&webBasePath, "webBasePath", "", "Set base path for Panel")
  474. settingCmd.StringVar(&listenIP, "listenIP", "", "set panel listenIP IP")
  475. settingCmd.BoolVar(&resetTwoFactor, "resetTwoFactor", false, "Reset two-factor authentication settings")
  476. settingCmd.BoolVar(&getListen, "getListen", false, "Display current panel listenIP IP")
  477. settingCmd.BoolVar(&getCert, "getCert", false, "Display current certificate settings")
  478. settingCmd.BoolVar(&getApiToken, "getApiToken", false, "Display current API token")
  479. settingCmd.StringVar(&webCertFile, "webCert", "", "Set path to public key file for panel")
  480. settingCmd.StringVar(&webKeyFile, "webCertKey", "", "Set path to private key file for panel")
  481. settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "Set token for Telegram bot")
  482. settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "Set cron time for Telegram bot notifications")
  483. settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "Set chat ID for Telegram bot notifications")
  484. settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "Enable notifications via Telegram bot")
  485. oldUsage := flag.Usage
  486. flag.Usage = func() {
  487. oldUsage()
  488. fmt.Println()
  489. fmt.Println("Commands:")
  490. fmt.Println(" run run web panel")
  491. fmt.Println(" migrate migrate form other/old x-ui")
  492. fmt.Println(" migrate-db SQLite <-> .dump (--dump/--restore) or copy into PostgreSQL (--dsn)")
  493. fmt.Println(" setting set settings")
  494. }
  495. flag.Parse()
  496. if showVersion {
  497. fmt.Println(config.GetVersion())
  498. return
  499. }
  500. switch os.Args[1] {
  501. case "run":
  502. err := runCmd.Parse(os.Args[2:])
  503. if err != nil {
  504. fmt.Println(err)
  505. return
  506. }
  507. runWebServer()
  508. case "migrate":
  509. migrateDb()
  510. case "migrate-db":
  511. if err := migrateDbCmd.Parse(os.Args[2:]); err != nil {
  512. fmt.Println(err)
  513. return
  514. }
  515. src := migrateSrc
  516. if src == "" {
  517. src = config.GetDBPath()
  518. }
  519. switch {
  520. case migrateDump != "":
  521. if err := database.DumpSQLite(src, migrateDump); err != nil {
  522. fmt.Println("dump failed:", err)
  523. os.Exit(1)
  524. }
  525. fmt.Printf("Dumped %s -> %s\n", src, migrateDump)
  526. case migrateRestore != "":
  527. if migrateOut == "" {
  528. fmt.Println("--out is required when using --restore: the destination .db path (must not exist)")
  529. return
  530. }
  531. if err := database.RestoreSQLite(migrateRestore, migrateOut); err != nil {
  532. fmt.Println("restore failed:", err)
  533. os.Exit(1)
  534. }
  535. fmt.Printf("Restored %s -> %s\n", migrateRestore, migrateOut)
  536. case migrateDsn != "":
  537. if err := database.MigrateData(src, migrateDsn); err != nil {
  538. fmt.Println("migration failed:", err)
  539. os.Exit(1)
  540. }
  541. default:
  542. fmt.Println("nothing to do: pass --dump <file>, --restore <file> --out <db>, or --dsn <postgres-dsn>")
  543. }
  544. case "setting":
  545. err := settingCmd.Parse(os.Args[2:])
  546. if err != nil {
  547. fmt.Println(err)
  548. return
  549. }
  550. if reset {
  551. if err = resetSetting(); err != nil {
  552. return
  553. }
  554. } else {
  555. if err = updateSetting(port, username, password, webBasePath, listenIP, resetTwoFactor); err != nil {
  556. return
  557. }
  558. }
  559. if show {
  560. showSetting(show)
  561. }
  562. if getListen {
  563. GetListenIP(getListen)
  564. }
  565. if getCert {
  566. GetCertificate(getCert)
  567. }
  568. if getApiToken {
  569. GetApiToken(getApiToken)
  570. }
  571. if (tgbottoken != "") || (tgbotchatid != "") || (tgbotRuntime != "") {
  572. updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime)
  573. }
  574. if enabletgbot {
  575. updateTgbotEnableSts(enabletgbot)
  576. }
  577. case "cert":
  578. err := settingCmd.Parse(os.Args[2:])
  579. if err != nil {
  580. fmt.Println(err)
  581. return
  582. }
  583. if reset {
  584. updateCert("", "")
  585. } else {
  586. updateCert(webCertFile, webKeyFile)
  587. }
  588. default:
  589. fmt.Println("Invalid subcommands")
  590. fmt.Println()
  591. runCmd.Usage()
  592. fmt.Println()
  593. settingCmd.Usage()
  594. }
  595. }