diff --git a/graphql/documents/queries/settings/metadata.graphql b/graphql/documents/queries/settings/metadata.graphql index 048d287e0..05dd6d04c 100644 --- a/graphql/documents/queries/settings/metadata.graphql +++ b/graphql/documents/queries/settings/metadata.graphql @@ -12,5 +12,6 @@ query SystemStatus { databasePath appSchema status + configPath } } diff --git a/graphql/schema/types/metadata.graphql b/graphql/schema/types/metadata.graphql index ac8185982..6c492fdeb 100644 --- a/graphql/schema/types/metadata.graphql +++ b/graphql/schema/types/metadata.graphql @@ -116,6 +116,7 @@ enum SystemStatusEnum { type SystemStatus { databaseSchema: Int databasePath: String + configPath: String appSchema: Int! status: SystemStatusEnum! } diff --git a/pkg/manager/config/config.go b/pkg/manager/config/config.go index 2da527ce2..440a1fded 100644 --- a/pkg/manager/config/config.go +++ b/pkg/manager/config/config.go @@ -141,7 +141,9 @@ func (e MissingConfigError) Error() string { return fmt.Sprintf("missing the following mandatory settings: %s", strings.Join(e.missingFields, ", ")) } -type Instance struct{} +type Instance struct { + isNewSystem bool +} var instance *Instance @@ -152,6 +154,10 @@ func GetInstance() *Instance { return instance } +func (i *Instance) IsNewSystem() bool { + return i.isNewSystem +} + func (i *Instance) SetConfigFile(fn string) { viper.SetConfigFile(fn) } @@ -687,29 +693,26 @@ func (i *Instance) Validate() error { return nil } -func (i *Instance) setDefaultValues() { +func (i *Instance) setDefaultValues() error { viper.SetDefault(ParallelTasks, parallelTasksDefault) viper.SetDefault(PreviewSegmentDuration, previewSegmentDurationDefault) viper.SetDefault(PreviewSegments, previewSegmentsDefault) viper.SetDefault(PreviewExcludeStart, previewExcludeStartDefault) viper.SetDefault(PreviewExcludeEnd, previewExcludeEndDefault) - // #1356 - only set these defaults once config file exists - if i.GetConfigFile() != "" { - viper.SetDefault(Database, i.GetDefaultDatabaseFilePath()) + viper.SetDefault(Database, i.GetDefaultDatabaseFilePath()) - // Set generated to the metadata path for backwards compat - viper.SetDefault(Generated, viper.GetString(Metadata)) + // Set generated to the metadata path for backwards compat + viper.SetDefault(Generated, viper.GetString(Metadata)) - // Set default scrapers and plugins paths - viper.SetDefault(ScrapersPath, i.GetDefaultScrapersPath()) - viper.SetDefault(PluginsPath, i.GetDefaultPluginsPath()) - viper.WriteConfig() - } + // Set default scrapers and plugins paths + viper.SetDefault(ScrapersPath, i.GetDefaultScrapersPath()) + viper.SetDefault(PluginsPath, i.GetDefaultPluginsPath()) + return viper.WriteConfig() } // SetInitialConfig fills in missing required config fields -func (i *Instance) SetInitialConfig() { +func (i *Instance) SetInitialConfig() error { // generate some api keys const apiKeyLength = 32 @@ -723,5 +726,9 @@ func (i *Instance) SetInitialConfig() { i.Set(SessionStoreKey, sessionStoreKey) } - i.setDefaultValues() + return i.setDefaultValues() +} + +func (i *Instance) FinalizeSetup() { + i.isNewSystem = false } diff --git a/pkg/manager/config/init.go b/pkg/manager/config/init.go index a2b98cf0b..8b6c2ff0e 100644 --- a/pkg/manager/config/init.go +++ b/pkg/manager/config/init.go @@ -1,6 +1,7 @@ package config import ( + "fmt" "net" "os" "sync" @@ -24,8 +25,22 @@ func Initialize() (*Instance, error) { instance = &Instance{} flags := initFlags() - err = initConfig(flags) + if err = initConfig(flags); err != nil { + return + } + initEnvs() + + if instance.isNewSystem { + if instance.Validate() == nil { + // system has been initialised by the environment + instance.isNewSystem = false + } + } + + if !instance.isNewSystem { + err = instance.SetInitialConfig() + } }) return instance, err } @@ -34,26 +49,47 @@ func initConfig(flags flagStruct) error { // The config file is called config. Leave off the file extension. viper.SetConfigName("config") - if flagConfigFileExists, _ := utils.FileExists(flags.configFilePath); flagConfigFileExists { - viper.SetConfigFile(flags.configFilePath) - } viper.AddConfigPath(".") // Look for config in the working directory viper.AddConfigPath("$HOME/.stash") // Look for the config in the home directory - // for Docker compatibility, if STASH_CONFIG_FILE is set, then touch the - // given filename + configFile := "" envConfigFile := os.Getenv("STASH_CONFIG_FILE") - if envConfigFile != "" { - utils.Touch(envConfigFile) - viper.SetConfigFile(envConfigFile) + + if flags.configFilePath != "" { + configFile = flags.configFilePath + } else if envConfigFile != "" { + configFile = envConfigFile + } + + if configFile != "" { + viper.SetConfigFile(configFile) + + // if file does not exist, assume it is a new system + if exists, _ := utils.FileExists(configFile); !exists { + instance.isNewSystem = true + + // ensure we can write to the file + if err := utils.Touch(configFile); err != nil { + return fmt.Errorf(`could not write to provided config path "%s": %s`, configFile, err.Error()) + } else { + // remove the file + os.Remove(configFile) + } + + return nil + } } err := viper.ReadInConfig() // Find and read the config file - // continue, but set an error to be handled by caller + // if not found, assume its a new system + if _, isMissing := err.(viper.ConfigFileNotFoundError); isMissing { + instance.isNewSystem = true + return nil + } else if err != nil { + return err + } - instance.SetInitialConfig() - - return err + return nil } func initFlags() flagStruct { diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go index df3b3d50d..4aa05dcb4 100644 --- a/pkg/manager/manager.go +++ b/pkg/manager/manager.go @@ -49,6 +49,11 @@ func Initialize() *singleton { once.Do(func() { _ = utils.EnsureDir(paths.GetStashHomeDirectory()) cfg, err := config.Initialize() + + if err != nil { + panic(fmt.Sprintf("error initializing configuration: %s", err.Error())) + } + initLog() instance = &singleton{ @@ -59,8 +64,7 @@ func Initialize() *singleton { TxnManager: sqlite.NewTransactionManager(), } - cfgFile := cfg.GetConfigFile() - if cfgFile != "" { + if !cfg.IsNewSystem() { logger.Infof("using config file: %s", cfg.GetConfigFile()) if err == nil { @@ -75,7 +79,11 @@ func Initialize() *singleton { } } } else { - logger.Warn("config file not found. Assuming new system...") + cfgFile := cfg.GetConfigFile() + if cfgFile != "" { + cfgFile = cfgFile + " " + } + logger.Warnf("config file %snot found. Assuming new system...", cfgFile) } initFFMPEG() @@ -235,6 +243,8 @@ func (s *singleton) Setup(input models.SetupInput) error { return fmt.Errorf("error initializing the database: %s", err.Error()) } + s.Config.FinalizeSetup() + return nil } @@ -283,8 +293,9 @@ func (s *singleton) GetSystemStatus() *models.SystemStatus { dbSchema := int(database.Version()) dbPath := database.DatabasePath() appSchema := int(database.AppSchemaVersion()) + configFile := s.Config.GetConfigFile() - if s.Config.GetConfigFile() == "" { + if s.Config.IsNewSystem() { status = models.SystemStatusEnumSetup } else if dbSchema < appSchema { status = models.SystemStatusEnumNeedsMigration @@ -295,5 +306,6 @@ func (s *singleton) GetSystemStatus() *models.SystemStatus { DatabasePath: &dbPath, AppSchema: appSchema, Status: status, + ConfigPath: &configFile, } } diff --git a/ui/v2.5/src/components/Setup/Setup.tsx b/ui/v2.5/src/components/Setup/Setup.tsx index 82465ee8f..a80fee0cd 100644 --- a/ui/v2.5/src/components/Setup/Setup.tsx +++ b/ui/v2.5/src/components/Setup/Setup.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { Alert, Button, @@ -27,6 +27,12 @@ export const Setup: React.FC = () => { const { data: systemStatus, loading: statusLoading } = useSystemStatus(); + useEffect(() => { + if (systemStatus?.systemStatus.configPath) { + setConfigLocation(systemStatus.systemStatus.configPath); + } + }, [systemStatus]); + const discordLink = ( Discord @@ -59,6 +65,38 @@ export const Setup: React.FC = () => { setStep(step + 1); } + function renderWelcomeSpecificConfig() { + return ( + <> +
+

Welcome to Stash

+

+ If you're reading this, then Stash couldn't find the + configuration file specified at the command line or the environment. + This wizard will guide you through the process of setting up a new + configuration. +

+

+ Stash will use the following configuration file path:{" "} + {configLocation} +

+

+ When you're ready to proceed with setting up a new system, + click Next. +

+
+ +
+
+ +
+
+ + ); + } + function renderWelcome() { return ( <> @@ -433,8 +471,6 @@ export const Setup: React.FC = () => { return renderSuccess(); } - const steps = [renderWelcome, renderSetPaths, renderConfirm, renderFinish]; - // only display setup wizard if system is not setup if (statusLoading) { return ; @@ -450,6 +486,12 @@ export const Setup: React.FC = () => { return ; } + const welcomeStep = + systemStatus && systemStatus.systemStatus.configPath !== "" + ? renderWelcomeSpecificConfig + : renderWelcome; + const steps = [welcomeStep, renderSetPaths, renderConfirm, renderFinish]; + return ( {maybeRenderGeneratedSelectDialog()}