cli.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  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. "errors"
  6. "flag"
  7. "fmt"
  8. "io"
  9. "log/slog"
  10. "os"
  11. "miniflux.app/v2/internal/config"
  12. "miniflux.app/v2/internal/database"
  13. "miniflux.app/v2/internal/locale"
  14. "miniflux.app/v2/internal/storage"
  15. "miniflux.app/v2/internal/ui/static"
  16. "miniflux.app/v2/internal/version"
  17. )
  18. const (
  19. flagInfoHelp = "Show build information"
  20. flagVersionHelp = "Show application version"
  21. flagMigrateHelp = "Run SQL migrations"
  22. flagFlushSessionsHelp = "Flush all sessions (disconnect users)"
  23. flagCreateAdminHelp = "Create an admin user from an interactive terminal"
  24. flagResetPasswordHelp = "Reset user password"
  25. flagResetFeedErrorsHelp = "Clear all feed errors for all users"
  26. flagDebugModeHelp = "Show debug logs"
  27. flagConfigFileHelp = "Load configuration file"
  28. flagConfigDumpHelp = "Print parsed configuration values"
  29. flagHealthCheckHelp = `Perform a health check on the given endpoint (the value "auto" try to guess the health check endpoint).`
  30. flagRefreshFeedsHelp = "Refresh a batch of feeds and exit"
  31. flagRunCleanupTasksHelp = "Run cleanup tasks (delete old sessions and archives old entries)"
  32. flagExportUserFeedsHelp = "Export user feeds (provide the username as argument)"
  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. flagDebugMode bool
  46. flagConfigFile string
  47. flagConfigDump bool
  48. flagHealthCheck string
  49. flagRefreshFeeds bool
  50. flagRunCleanupTasks bool
  51. flagExportUserFeeds string
  52. )
  53. flag.BoolVar(&flagInfo, "info", false, flagInfoHelp)
  54. flag.BoolVar(&flagInfo, "i", false, flagInfoHelp)
  55. flag.BoolVar(&flagVersion, "version", false, flagVersionHelp)
  56. flag.BoolVar(&flagVersion, "v", false, flagVersionHelp)
  57. flag.BoolVar(&flagMigrate, "migrate", false, flagMigrateHelp)
  58. flag.BoolVar(&flagFlushSessions, "flush-sessions", false, flagFlushSessionsHelp)
  59. flag.BoolVar(&flagCreateAdmin, "create-admin", false, flagCreateAdminHelp)
  60. flag.BoolVar(&flagResetPassword, "reset-password", false, flagResetPasswordHelp)
  61. flag.BoolVar(&flagResetFeedErrors, "reset-feed-errors", false, flagResetFeedErrorsHelp)
  62. flag.BoolVar(&flagDebugMode, "debug", false, flagDebugModeHelp)
  63. flag.StringVar(&flagConfigFile, "config-file", "", flagConfigFileHelp)
  64. flag.StringVar(&flagConfigFile, "c", "", flagConfigFileHelp)
  65. flag.BoolVar(&flagConfigDump, "config-dump", false, flagConfigDumpHelp)
  66. flag.StringVar(&flagHealthCheck, "healthcheck", "", flagHealthCheckHelp)
  67. flag.BoolVar(&flagRefreshFeeds, "refresh-feeds", false, flagRefreshFeedsHelp)
  68. flag.BoolVar(&flagRunCleanupTasks, "run-cleanup-tasks", false, flagRunCleanupTasksHelp)
  69. flag.StringVar(&flagExportUserFeeds, "export-user-feeds", "", flagExportUserFeedsHelp)
  70. flag.Parse()
  71. cfg := config.NewParser()
  72. if flagConfigFile != "" {
  73. config.Opts, err = cfg.ParseFile(flagConfigFile)
  74. if err != nil {
  75. printErrorAndExit(err)
  76. }
  77. }
  78. config.Opts, err = cfg.ParseEnvironmentVariables()
  79. if err != nil {
  80. printErrorAndExit(err)
  81. }
  82. if flagConfigDump {
  83. fmt.Print(config.Opts)
  84. return
  85. }
  86. if flagDebugMode {
  87. config.Opts.SetLogLevel("debug")
  88. }
  89. logFile := config.Opts.LogFile()
  90. var logFileHandler io.Writer
  91. switch logFile {
  92. case "stdout":
  93. logFileHandler = os.Stdout
  94. case "stderr":
  95. logFileHandler = os.Stderr
  96. default:
  97. logFileHandler, err = os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
  98. if err != nil {
  99. printErrorAndExit(fmt.Errorf("unable to open log file: %v", err))
  100. }
  101. defer logFileHandler.(*os.File).Close()
  102. }
  103. if err := InitializeDefaultLogger(config.Opts.LogLevel(), logFileHandler, config.Opts.LogFormat(), config.Opts.LogDateTime()); err != nil {
  104. printErrorAndExit(err)
  105. }
  106. if flagHealthCheck != "" {
  107. doHealthCheck(flagHealthCheck)
  108. return
  109. }
  110. if flagInfo {
  111. info()
  112. return
  113. }
  114. if flagVersion {
  115. fmt.Println(version.Version)
  116. return
  117. }
  118. if config.Opts.IsDefaultDatabaseURL() {
  119. slog.Info("The default value for DATABASE_URL is used")
  120. }
  121. if err := locale.LoadCatalogMessages(); err != nil {
  122. printErrorAndExit(fmt.Errorf("unable to load translations: %v", err))
  123. }
  124. if err := static.CalculateBinaryFileChecksums(); err != nil {
  125. printErrorAndExit(fmt.Errorf("unable to calculate binary file checksums: %v", err))
  126. }
  127. if err := static.GenerateStylesheetsBundles(); err != nil {
  128. printErrorAndExit(fmt.Errorf("unable to generate stylesheets bundles: %v", err))
  129. }
  130. if err := static.GenerateJavascriptBundles(); err != nil {
  131. printErrorAndExit(fmt.Errorf("unable to generate javascript bundles: %v", err))
  132. }
  133. db, err := database.NewConnectionPool(
  134. config.Opts.DatabaseURL(),
  135. config.Opts.DatabaseMinConns(),
  136. config.Opts.DatabaseMaxConns(),
  137. config.Opts.DatabaseConnectionLifetime(),
  138. )
  139. if err != nil {
  140. printErrorAndExit(fmt.Errorf("unable to connect to database: %v", err))
  141. }
  142. defer db.Close()
  143. store := storage.NewStorage(db)
  144. if err := store.Ping(); err != nil {
  145. printErrorAndExit(err)
  146. }
  147. if flagMigrate {
  148. if err := database.Migrate(db); err != nil {
  149. printErrorAndExit(err)
  150. }
  151. return
  152. }
  153. if flagResetFeedErrors {
  154. store.ResetFeedErrors()
  155. return
  156. }
  157. if flagExportUserFeeds != "" {
  158. exportUserFeeds(store, flagExportUserFeeds)
  159. return
  160. }
  161. if flagFlushSessions {
  162. flushSessions(store)
  163. return
  164. }
  165. if flagCreateAdmin {
  166. createAdminUserFromInteractiveTerminal(store)
  167. return
  168. }
  169. if flagResetPassword {
  170. resetPassword(store)
  171. return
  172. }
  173. // Run migrations and start the daemon.
  174. if config.Opts.RunMigrations() {
  175. if err := database.Migrate(db); err != nil {
  176. printErrorAndExit(err)
  177. }
  178. }
  179. if err := database.IsSchemaUpToDate(db); err != nil {
  180. printErrorAndExit(err)
  181. }
  182. if config.Opts.CreateAdmin() {
  183. createAdminUserFromEnvironmentVariables(store)
  184. }
  185. if flagRefreshFeeds {
  186. refreshFeeds(store)
  187. return
  188. }
  189. if flagRunCleanupTasks {
  190. runCleanupTasks(store)
  191. return
  192. }
  193. if config.Opts.DisableLocalAuth() {
  194. switch {
  195. case config.Opts.OAuth2Provider() == "" && config.Opts.AuthProxyHeader() == "":
  196. printErrorAndExit(errors.New("DISABLE_LOCAL_AUTH is enabled but neither OAUTH2_PROVIDER nor AUTH_PROXY_HEADER is not set. Please enable at least one authentication source"))
  197. case config.Opts.OAuth2Provider() != "" && !config.Opts.IsOAuth2UserCreationAllowed():
  198. printErrorAndExit(errors.New("DISABLE_LOCAL_AUTH is enabled and an OAUTH2_PROVIDER is configured, but OAUTH2_USER_CREATION is not enabled"))
  199. case config.Opts.AuthProxyHeader() != "" && !config.Opts.IsAuthProxyUserCreationAllowed():
  200. printErrorAndExit(errors.New("DISABLE_LOCAL_AUTH is enabled and an AUTH_PROXY_HEADER is configured, but AUTH_PROXY_USER_CREATION is not enabled"))
  201. }
  202. }
  203. startDaemon(store)
  204. }
  205. func printErrorAndExit(err error) {
  206. fmt.Fprintf(os.Stderr, "%v\n", err)
  207. os.Exit(1)
  208. }