cli.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  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/proxyrotator"
  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" tries to guess the health check endpoint).`
  30. flagRefreshFeedsHelp = "Refresh a batch of feeds and exit"
  31. flagRunCleanupTasksHelp = "Run cleanup tasks (delete old sessions and archive old entries)"
  32. flagExportUserFeedsHelp = "Export user feeds (provide the username as argument)"
  33. flagResetNextCheckAtHelp = "Reset the next check time for all feeds"
  34. )
  35. // Parse parses command line arguments.
  36. func Parse() {
  37. var (
  38. err error
  39. flagInfo bool
  40. flagVersion bool
  41. flagMigrate bool
  42. flagFlushSessions bool
  43. flagCreateAdmin bool
  44. flagResetPassword bool
  45. flagResetFeedErrors bool
  46. flagResetFeedNextCheckAt bool
  47. flagDebugMode bool
  48. flagConfigFile string
  49. flagConfigDump bool
  50. flagHealthCheck string
  51. flagRefreshFeeds bool
  52. flagRunCleanupTasks bool
  53. flagExportUserFeeds string
  54. )
  55. flag.BoolVar(&flagInfo, "info", false, flagInfoHelp)
  56. flag.BoolVar(&flagInfo, "i", false, flagInfoHelp)
  57. flag.BoolVar(&flagVersion, "version", false, flagVersionHelp)
  58. flag.BoolVar(&flagVersion, "v", false, flagVersionHelp)
  59. flag.BoolVar(&flagMigrate, "migrate", false, flagMigrateHelp)
  60. flag.BoolVar(&flagFlushSessions, "flush-sessions", false, flagFlushSessionsHelp)
  61. flag.BoolVar(&flagCreateAdmin, "create-admin", false, flagCreateAdminHelp)
  62. flag.BoolVar(&flagResetPassword, "reset-password", false, flagResetPasswordHelp)
  63. flag.BoolVar(&flagResetFeedErrors, "reset-feed-errors", false, flagResetFeedErrorsHelp)
  64. flag.BoolVar(&flagResetFeedNextCheckAt, "reset-feed-next-check-at", false, flagResetNextCheckAtHelp)
  65. flag.BoolVar(&flagDebugMode, "debug", false, flagDebugModeHelp)
  66. flag.StringVar(&flagConfigFile, "config-file", "", flagConfigFileHelp)
  67. flag.StringVar(&flagConfigFile, "c", "", flagConfigFileHelp)
  68. flag.BoolVar(&flagConfigDump, "config-dump", false, flagConfigDumpHelp)
  69. flag.StringVar(&flagHealthCheck, "healthcheck", "", flagHealthCheckHelp)
  70. flag.BoolVar(&flagRefreshFeeds, "refresh-feeds", false, flagRefreshFeedsHelp)
  71. flag.BoolVar(&flagRunCleanupTasks, "run-cleanup-tasks", false, flagRunCleanupTasksHelp)
  72. flag.StringVar(&flagExportUserFeeds, "export-user-feeds", "", flagExportUserFeedsHelp)
  73. flag.Parse()
  74. cfg := config.NewConfigParser()
  75. if flagConfigFile != "" {
  76. config.Opts, err = cfg.ParseFile(flagConfigFile)
  77. if err != nil {
  78. printErrorAndExit(err)
  79. }
  80. }
  81. config.Opts, err = cfg.ParseEnvironmentVariables()
  82. if err != nil {
  83. printErrorAndExit(err)
  84. }
  85. if oauth2Provider := config.Opts.OAuth2Provider(); oauth2Provider != "" {
  86. if oauth2Provider != "oidc" && oauth2Provider != "google" {
  87. printErrorAndExit(fmt.Errorf(`unsupported OAuth2 provider: %q (Possible values are "google" or "oidc")`, oauth2Provider))
  88. }
  89. }
  90. if config.Opts.DisableLocalAuth() {
  91. switch {
  92. case config.Opts.OAuth2Provider() == "" && config.Opts.AuthProxyHeader() == "":
  93. 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"))
  94. case config.Opts.OAuth2Provider() != "" && !config.Opts.IsOAuth2UserCreationAllowed():
  95. printErrorAndExit(errors.New("DISABLE_LOCAL_AUTH is enabled and an OAUTH2_PROVIDER is configured, but OAUTH2_USER_CREATION is not enabled"))
  96. case config.Opts.AuthProxyHeader() != "" && !config.Opts.IsAuthProxyUserCreationAllowed():
  97. printErrorAndExit(errors.New("DISABLE_LOCAL_AUTH is enabled and an AUTH_PROXY_HEADER is configured, but AUTH_PROXY_USER_CREATION is not enabled"))
  98. }
  99. }
  100. if config.Opts.AuthProxyHeader() != "" {
  101. if len(config.Opts.TrustedReverseProxyNetworks()) == 0 {
  102. printErrorAndExit(errors.New("TRUSTED_REVERSE_PROXY_NETWORKS must be configured when AUTH_PROXY_HEADER is used"))
  103. }
  104. }
  105. if flagConfigDump {
  106. fmt.Print(config.Opts)
  107. return
  108. }
  109. if flagDebugMode {
  110. config.Opts.SetLogLevel("debug")
  111. }
  112. logFile := config.Opts.LogFile()
  113. var logFileHandler io.Writer
  114. switch logFile {
  115. case "stdout":
  116. logFileHandler = os.Stdout
  117. case "stderr":
  118. logFileHandler = os.Stderr
  119. default:
  120. logFileHandler, err = os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
  121. if err != nil {
  122. printErrorAndExit(fmt.Errorf("unable to open log file: %v", err))
  123. }
  124. defer logFileHandler.(*os.File).Close()
  125. }
  126. if err := InitializeDefaultLogger(config.Opts.LogLevel(), logFileHandler, config.Opts.LogFormat(), config.Opts.LogDateTime()); err != nil {
  127. printErrorAndExit(err)
  128. }
  129. if flagHealthCheck != "" {
  130. doHealthCheck(flagHealthCheck)
  131. return
  132. }
  133. if flagInfo {
  134. info()
  135. return
  136. }
  137. if flagVersion {
  138. fmt.Println(version.Version)
  139. return
  140. }
  141. if config.Opts.IsDefaultDatabaseURL() {
  142. slog.Info("The default value for DATABASE_URL is used")
  143. }
  144. if err := static.GenerateBinaryBundles(); err != nil {
  145. printErrorAndExit(fmt.Errorf("unable to generate binary files bundle: %v", err))
  146. }
  147. if err := static.GenerateStylesheetsBundles(); err != nil {
  148. printErrorAndExit(fmt.Errorf("unable to generate stylesheets bundle: %v", err))
  149. }
  150. if err := static.GenerateJavascriptBundles(config.Opts.WebAuthn()); err != nil {
  151. printErrorAndExit(fmt.Errorf("unable to generate javascript bundle: %v", err))
  152. }
  153. db, err := database.NewConnectionPool(
  154. config.Opts.DatabaseURL(),
  155. config.Opts.DatabaseMinConns(),
  156. config.Opts.DatabaseMaxConns(),
  157. config.Opts.DatabaseConnectionLifetime(),
  158. )
  159. if err != nil {
  160. printErrorAndExit(fmt.Errorf("unable to connect to database: %v", err))
  161. }
  162. defer db.Close()
  163. store := storage.NewStorage(db)
  164. if err := store.Ping(); err != nil {
  165. printErrorAndExit(err)
  166. }
  167. if flagMigrate {
  168. if err := database.Migrate(db); err != nil {
  169. printErrorAndExit(err)
  170. }
  171. return
  172. }
  173. if flagResetFeedErrors {
  174. if err := store.ResetFeedErrors(); err != nil {
  175. printErrorAndExit(err)
  176. }
  177. return
  178. }
  179. if flagResetFeedNextCheckAt {
  180. if err := store.ResetNextCheckAt(); err != nil {
  181. printErrorAndExit(err)
  182. }
  183. return
  184. }
  185. if flagExportUserFeeds != "" {
  186. exportUserFeeds(store, flagExportUserFeeds)
  187. return
  188. }
  189. if flagFlushSessions {
  190. flushSessions(store)
  191. return
  192. }
  193. if flagCreateAdmin {
  194. createAdminUserFromInteractiveTerminal(store)
  195. return
  196. }
  197. if flagResetPassword {
  198. resetPassword(store)
  199. return
  200. }
  201. // Run migrations and start the daemon.
  202. if config.Opts.RunMigrations() {
  203. if err := database.Migrate(db); err != nil {
  204. printErrorAndExit(err)
  205. }
  206. }
  207. if err := database.IsSchemaUpToDate(db); err != nil {
  208. printErrorAndExit(err)
  209. }
  210. if config.Opts.CreateAdmin() {
  211. createAdminUserFromEnvironmentVariables(store)
  212. }
  213. if config.Opts.HasHTTPClientProxiesConfigured() {
  214. slog.Info("Initializing proxy rotation", slog.Int("proxies_count", len(config.Opts.HTTPClientProxies())))
  215. proxyrotator.ProxyRotatorInstance, err = proxyrotator.NewProxyRotator(config.Opts.HTTPClientProxies())
  216. if err != nil {
  217. printErrorAndExit(fmt.Errorf("unable to initialize proxy rotator: %v", err))
  218. }
  219. }
  220. if flagRefreshFeeds {
  221. refreshFeeds(store)
  222. return
  223. }
  224. if flagRunCleanupTasks {
  225. runCleanupTasks(store)
  226. return
  227. }
  228. startDaemon(store)
  229. }
  230. func printErrorAndExit(err error) {
  231. fmt.Fprintln(os.Stderr, err)
  232. os.Exit(1)
  233. }