| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
- // SPDX-License-Identifier: Apache-2.0
- package cli // import "miniflux.app/v2/internal/cli"
- import (
- "errors"
- "flag"
- "fmt"
- "io"
- "log/slog"
- "os"
- "miniflux.app/v2/internal/config"
- "miniflux.app/v2/internal/database"
- "miniflux.app/v2/internal/proxyrotator"
- "miniflux.app/v2/internal/storage"
- "miniflux.app/v2/internal/ui/static"
- "miniflux.app/v2/internal/version"
- )
- const (
- flagInfoHelp = "Show build information"
- flagVersionHelp = "Show application version"
- flagMigrateHelp = "Run SQL migrations"
- flagFlushSessionsHelp = "Flush all sessions (disconnect users)"
- flagCreateAdminHelp = "Create an admin user from an interactive terminal"
- flagResetPasswordHelp = "Reset user password"
- flagResetFeedErrorsHelp = "Clear all feed errors for all users"
- flagDebugModeHelp = "Show debug logs"
- flagConfigFileHelp = "Load configuration file"
- flagConfigDumpHelp = "Print parsed configuration values"
- flagHealthCheckHelp = `Perform a health check on the given endpoint (the value "auto" tries to guess the health check endpoint).`
- flagRefreshFeedsHelp = "Refresh a batch of feeds and exit"
- flagRunCleanupTasksHelp = "Run cleanup tasks (delete old sessions and archive old entries)"
- flagExportUserFeedsHelp = "Export user feeds (provide the username as argument)"
- flagResetNextCheckAtHelp = "Reset the next check time for all feeds"
- )
- // Parse parses command line arguments.
- func Parse() {
- var (
- err error
- flagInfo bool
- flagVersion bool
- flagMigrate bool
- flagFlushSessions bool
- flagCreateAdmin bool
- flagResetPassword bool
- flagResetFeedErrors bool
- flagResetFeedNextCheckAt bool
- flagDebugMode bool
- flagConfigFile string
- flagConfigDump bool
- flagHealthCheck string
- flagRefreshFeeds bool
- flagRunCleanupTasks bool
- flagExportUserFeeds string
- )
- flag.BoolVar(&flagInfo, "info", false, flagInfoHelp)
- flag.BoolVar(&flagInfo, "i", false, flagInfoHelp)
- flag.BoolVar(&flagVersion, "version", false, flagVersionHelp)
- flag.BoolVar(&flagVersion, "v", false, flagVersionHelp)
- flag.BoolVar(&flagMigrate, "migrate", false, flagMigrateHelp)
- flag.BoolVar(&flagFlushSessions, "flush-sessions", false, flagFlushSessionsHelp)
- flag.BoolVar(&flagCreateAdmin, "create-admin", false, flagCreateAdminHelp)
- flag.BoolVar(&flagResetPassword, "reset-password", false, flagResetPasswordHelp)
- flag.BoolVar(&flagResetFeedErrors, "reset-feed-errors", false, flagResetFeedErrorsHelp)
- flag.BoolVar(&flagResetFeedNextCheckAt, "reset-feed-next-check-at", false, flagResetNextCheckAtHelp)
- flag.BoolVar(&flagDebugMode, "debug", false, flagDebugModeHelp)
- flag.StringVar(&flagConfigFile, "config-file", "", flagConfigFileHelp)
- flag.StringVar(&flagConfigFile, "c", "", flagConfigFileHelp)
- flag.BoolVar(&flagConfigDump, "config-dump", false, flagConfigDumpHelp)
- flag.StringVar(&flagHealthCheck, "healthcheck", "", flagHealthCheckHelp)
- flag.BoolVar(&flagRefreshFeeds, "refresh-feeds", false, flagRefreshFeedsHelp)
- flag.BoolVar(&flagRunCleanupTasks, "run-cleanup-tasks", false, flagRunCleanupTasksHelp)
- flag.StringVar(&flagExportUserFeeds, "export-user-feeds", "", flagExportUserFeedsHelp)
- flag.Parse()
- cfg := config.NewConfigParser()
- if flagConfigFile != "" {
- config.Opts, err = cfg.ParseFile(flagConfigFile)
- if err != nil {
- printErrorAndExit(err)
- }
- }
- config.Opts, err = cfg.ParseEnvironmentVariables()
- if err != nil {
- printErrorAndExit(err)
- }
- if oauth2Provider := config.Opts.OAuth2Provider(); oauth2Provider != "" {
- if oauth2Provider != "oidc" && oauth2Provider != "google" {
- printErrorAndExit(fmt.Errorf(`unsupported OAuth2 provider: %q (Possible values are "google" or "oidc")`, oauth2Provider))
- }
- }
- if config.Opts.DisableLocalAuth() {
- switch {
- case config.Opts.OAuth2Provider() == "" && config.Opts.AuthProxyHeader() == "":
- 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"))
- case config.Opts.OAuth2Provider() != "" && !config.Opts.IsOAuth2UserCreationAllowed():
- printErrorAndExit(errors.New("DISABLE_LOCAL_AUTH is enabled and an OAUTH2_PROVIDER is configured, but OAUTH2_USER_CREATION is not enabled"))
- case config.Opts.AuthProxyHeader() != "" && !config.Opts.IsAuthProxyUserCreationAllowed():
- printErrorAndExit(errors.New("DISABLE_LOCAL_AUTH is enabled and an AUTH_PROXY_HEADER is configured, but AUTH_PROXY_USER_CREATION is not enabled"))
- }
- }
- if config.Opts.AuthProxyHeader() != "" {
- if len(config.Opts.TrustedReverseProxyNetworks()) == 0 {
- printErrorAndExit(errors.New("TRUSTED_REVERSE_PROXY_NETWORKS must be configured when AUTH_PROXY_HEADER is used"))
- }
- }
- if flagConfigDump {
- fmt.Print(config.Opts)
- return
- }
- if flagDebugMode {
- config.Opts.SetLogLevel("debug")
- }
- logFile := config.Opts.LogFile()
- var logFileHandler io.Writer
- switch logFile {
- case "stdout":
- logFileHandler = os.Stdout
- case "stderr":
- logFileHandler = os.Stderr
- default:
- logFileHandler, err = os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
- if err != nil {
- printErrorAndExit(fmt.Errorf("unable to open log file: %v", err))
- }
- defer logFileHandler.(*os.File).Close()
- }
- if err := InitializeDefaultLogger(config.Opts.LogLevel(), logFileHandler, config.Opts.LogFormat(), config.Opts.LogDateTime()); err != nil {
- printErrorAndExit(err)
- }
- if flagHealthCheck != "" {
- doHealthCheck(flagHealthCheck)
- return
- }
- if flagInfo {
- info()
- return
- }
- if flagVersion {
- fmt.Println(version.Version)
- return
- }
- if config.Opts.IsDefaultDatabaseURL() {
- slog.Info("The default value for DATABASE_URL is used")
- }
- if err := static.GenerateBinaryBundles(); err != nil {
- printErrorAndExit(fmt.Errorf("unable to generate binary files bundle: %v", err))
- }
- if err := static.GenerateStylesheetsBundles(); err != nil {
- printErrorAndExit(fmt.Errorf("unable to generate stylesheets bundle: %v", err))
- }
- if err := static.GenerateJavascriptBundles(config.Opts.WebAuthn()); err != nil {
- printErrorAndExit(fmt.Errorf("unable to generate javascript bundle: %v", err))
- }
- db, err := database.NewConnectionPool(
- config.Opts.DatabaseURL(),
- config.Opts.DatabaseMinConns(),
- config.Opts.DatabaseMaxConns(),
- config.Opts.DatabaseConnectionLifetime(),
- )
- if err != nil {
- printErrorAndExit(fmt.Errorf("unable to connect to database: %v", err))
- }
- defer db.Close()
- store := storage.NewStorage(db)
- if err := store.Ping(); err != nil {
- printErrorAndExit(err)
- }
- if flagMigrate {
- if err := database.Migrate(db); err != nil {
- printErrorAndExit(err)
- }
- return
- }
- if flagResetFeedErrors {
- if err := store.ResetFeedErrors(); err != nil {
- printErrorAndExit(err)
- }
- return
- }
- if flagResetFeedNextCheckAt {
- if err := store.ResetNextCheckAt(); err != nil {
- printErrorAndExit(err)
- }
- return
- }
- if flagExportUserFeeds != "" {
- exportUserFeeds(store, flagExportUserFeeds)
- return
- }
- if flagFlushSessions {
- flushSessions(store)
- return
- }
- if flagCreateAdmin {
- createAdminUserFromInteractiveTerminal(store)
- return
- }
- if flagResetPassword {
- resetPassword(store)
- return
- }
- // Run migrations and start the daemon.
- if config.Opts.RunMigrations() {
- if err := database.Migrate(db); err != nil {
- printErrorAndExit(err)
- }
- }
- if err := database.IsSchemaUpToDate(db); err != nil {
- printErrorAndExit(err)
- }
- if config.Opts.CreateAdmin() {
- createAdminUserFromEnvironmentVariables(store)
- }
- if config.Opts.HasHTTPClientProxiesConfigured() {
- slog.Info("Initializing proxy rotation", slog.Int("proxies_count", len(config.Opts.HTTPClientProxies())))
- proxyrotator.ProxyRotatorInstance, err = proxyrotator.NewProxyRotator(config.Opts.HTTPClientProxies())
- if err != nil {
- printErrorAndExit(fmt.Errorf("unable to initialize proxy rotator: %v", err))
- }
- }
- if flagRefreshFeeds {
- refreshFeeds(store)
- return
- }
- if flagRunCleanupTasks {
- runCleanupTasks(store)
- return
- }
- startDaemon(store)
- }
- func printErrorAndExit(err error) {
- fmt.Fprintln(os.Stderr, err)
- os.Exit(1)
- }
|