cli.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package cli // import "miniflux.app/v2/internal/cli"
  4. import (
  5. "flag"
  6. "fmt"
  7. "io"
  8. "log/slog"
  9. "os"
  10. "miniflux.app/v2/internal/config"
  11. "miniflux.app/v2/internal/database"
  12. "miniflux.app/v2/internal/proxyrotator"
  13. "miniflux.app/v2/internal/storage"
  14. "miniflux.app/v2/internal/ui/static"
  15. "miniflux.app/v2/internal/version"
  16. )
  17. const (
  18. flagInfoHelp = "Show build information"
  19. flagVersionHelp = "Show application version"
  20. flagMigrateHelp = "Run SQL migrations"
  21. flagFlushSessionsHelp = "Flush all sessions (disconnect users)"
  22. flagCreateAdminHelp = "Create an admin user from an interactive terminal"
  23. flagResetPasswordHelp = "Reset user password"
  24. flagResetFeedErrorsHelp = "Clear all feed errors for all users"
  25. flagDebugModeHelp = "Show debug logs"
  26. flagConfigFileHelp = "Load configuration file"
  27. flagConfigDumpHelp = "Print parsed configuration values"
  28. flagHealthCheckHelp = `Perform a health check on the given endpoint (the value "auto" tries to guess the health check endpoint).`
  29. flagRefreshFeedsHelp = "Refresh a batch of feeds and exit"
  30. flagRunCleanupTasksHelp = "Run cleanup tasks (delete old sessions and archive old entries)"
  31. flagExportUserFeedsHelp = "Export user feeds (provide the username as argument)"
  32. flagResetNextCheckAtHelp = "Reset the next check time for all feeds"
  33. )
  34. // Parse parses command line arguments.
  35. func Parse() {
  36. var (
  37. err error
  38. flagInfo bool
  39. flagVersion bool
  40. flagMigrate bool
  41. flagFlushSessions bool
  42. flagCreateAdmin bool
  43. flagResetPassword bool
  44. flagResetFeedErrors bool
  45. flagResetFeedNextCheckAt bool
  46. flagDebugMode bool
  47. flagConfigFile string
  48. flagConfigDump bool
  49. flagHealthCheck string
  50. flagRefreshFeeds bool
  51. flagRunCleanupTasks bool
  52. flagExportUserFeeds string
  53. )
  54. flag.BoolVar(&flagInfo, "info", false, flagInfoHelp)
  55. flag.BoolVar(&flagInfo, "i", false, flagInfoHelp)
  56. flag.BoolVar(&flagVersion, "version", false, flagVersionHelp)
  57. flag.BoolVar(&flagVersion, "v", false, flagVersionHelp)
  58. flag.BoolVar(&flagMigrate, "migrate", false, flagMigrateHelp)
  59. flag.BoolVar(&flagFlushSessions, "flush-sessions", false, flagFlushSessionsHelp)
  60. flag.BoolVar(&flagCreateAdmin, "create-admin", false, flagCreateAdminHelp)
  61. flag.BoolVar(&flagResetPassword, "reset-password", false, flagResetPasswordHelp)
  62. flag.BoolVar(&flagResetFeedErrors, "reset-feed-errors", false, flagResetFeedErrorsHelp)
  63. flag.BoolVar(&flagResetFeedNextCheckAt, "reset-feed-next-check-at", false, flagResetNextCheckAtHelp)
  64. flag.BoolVar(&flagDebugMode, "debug", false, flagDebugModeHelp)
  65. flag.StringVar(&flagConfigFile, "config-file", "", flagConfigFileHelp)
  66. flag.StringVar(&flagConfigFile, "c", "", flagConfigFileHelp)
  67. flag.BoolVar(&flagConfigDump, "config-dump", false, flagConfigDumpHelp)
  68. flag.StringVar(&flagHealthCheck, "healthcheck", "", flagHealthCheckHelp)
  69. flag.BoolVar(&flagRefreshFeeds, "refresh-feeds", false, flagRefreshFeedsHelp)
  70. flag.BoolVar(&flagRunCleanupTasks, "run-cleanup-tasks", false, flagRunCleanupTasksHelp)
  71. flag.StringVar(&flagExportUserFeeds, "export-user-feeds", "", flagExportUserFeedsHelp)
  72. flag.Parse()
  73. cfg := config.NewConfigParser()
  74. if flagConfigFile != "" {
  75. config.Opts, err = cfg.ParseFile(flagConfigFile)
  76. if err != nil {
  77. printErrorAndExit(err)
  78. }
  79. }
  80. config.Opts, err = cfg.ParseEnvironmentVariables()
  81. if err != nil {
  82. printErrorAndExit(err)
  83. }
  84. if err := config.Opts.Validate(); err != nil {
  85. printErrorAndExit(err)
  86. }
  87. if flagConfigDump {
  88. fmt.Print(config.Opts)
  89. return
  90. }
  91. if flagInfo {
  92. info()
  93. return
  94. }
  95. if flagVersion {
  96. fmt.Println(version.Version)
  97. return
  98. }
  99. if flagDebugMode {
  100. config.Opts.SetLogLevel("debug")
  101. }
  102. logFile := config.Opts.LogFile()
  103. var logFileHandler io.Writer
  104. switch logFile {
  105. case "stdout":
  106. logFileHandler = os.Stdout
  107. case "stderr":
  108. logFileHandler = os.Stderr
  109. default:
  110. logFileHandler, err = os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
  111. if err != nil {
  112. printErrorAndExit(fmt.Errorf("unable to open log file: %v", err))
  113. }
  114. defer logFileHandler.(*os.File).Close()
  115. }
  116. if err := InitializeDefaultLogger(config.Opts.LogLevel(), logFileHandler, config.Opts.LogFormat(), config.Opts.LogDateTime()); err != nil {
  117. printErrorAndExit(err)
  118. }
  119. if flagHealthCheck != "" {
  120. doHealthCheck(flagHealthCheck)
  121. return
  122. }
  123. if config.Opts.IsDefaultDatabaseURL() {
  124. slog.Info("The default value for DATABASE_URL is used")
  125. }
  126. if err := static.GenerateBinaryBundles(); err != nil {
  127. printErrorAndExit(fmt.Errorf("unable to generate binary files bundle: %v", err))
  128. }
  129. if err := static.GenerateStylesheetsBundles(); err != nil {
  130. printErrorAndExit(fmt.Errorf("unable to generate stylesheets bundle: %v", err))
  131. }
  132. if err := static.GenerateJavascriptBundles(config.Opts.WebAuthn()); err != nil {
  133. printErrorAndExit(fmt.Errorf("unable to generate javascript bundle: %v", err))
  134. }
  135. db, err := database.NewConnectionPool(
  136. config.Opts.DatabaseURL(),
  137. config.Opts.DatabaseMinConns(),
  138. config.Opts.DatabaseMaxConns(),
  139. config.Opts.DatabaseConnectionLifetime(),
  140. )
  141. if err != nil {
  142. printErrorAndExit(fmt.Errorf("unable to connect to database: %v", err))
  143. }
  144. defer db.Close()
  145. store := storage.NewStorage(db)
  146. if err := store.Ping(); err != nil {
  147. printErrorAndExit(err)
  148. }
  149. if flagMigrate {
  150. if err := database.Migrate(db); err != nil {
  151. printErrorAndExit(err)
  152. }
  153. return
  154. }
  155. if flagResetFeedErrors {
  156. if err := store.ResetFeedErrors(); err != nil {
  157. printErrorAndExit(err)
  158. }
  159. return
  160. }
  161. if flagResetFeedNextCheckAt {
  162. if err := store.ResetNextCheckAt(); err != nil {
  163. printErrorAndExit(err)
  164. }
  165. return
  166. }
  167. if flagExportUserFeeds != "" {
  168. exportUserFeeds(store, flagExportUserFeeds)
  169. return
  170. }
  171. if flagFlushSessions {
  172. flushSessions(store)
  173. return
  174. }
  175. if flagCreateAdmin {
  176. createAdminUserFromInteractiveTerminal(store)
  177. return
  178. }
  179. if flagResetPassword {
  180. resetPassword(store)
  181. return
  182. }
  183. // Run migrations and start the daemon.
  184. if config.Opts.RunMigrations() {
  185. if err := database.Migrate(db); err != nil {
  186. printErrorAndExit(err)
  187. }
  188. }
  189. if err := database.IsSchemaUpToDate(db); err != nil {
  190. printErrorAndExit(err)
  191. }
  192. if config.Opts.CreateAdmin() {
  193. createAdminUserFromEnvironmentVariables(store)
  194. }
  195. if config.Opts.HasHTTPClientProxiesConfigured() {
  196. slog.Info("Initializing proxy rotation", slog.Int("proxies_count", len(config.Opts.HTTPClientProxies())))
  197. proxyrotator.ProxyRotatorInstance, err = proxyrotator.NewProxyRotator(config.Opts.HTTPClientProxies())
  198. if err != nil {
  199. printErrorAndExit(fmt.Errorf("unable to initialize proxy rotator: %v", err))
  200. }
  201. }
  202. if flagRefreshFeeds {
  203. refreshFeeds(store)
  204. return
  205. }
  206. if flagRunCleanupTasks {
  207. runCleanupTasks(store)
  208. return
  209. }
  210. startDaemon(store)
  211. }
  212. func printErrorAndExit(err error) {
  213. fmt.Fprintln(os.Stderr, err)
  214. os.Exit(1)
  215. }