Fix initial setup issue issues (#1380)

* Refactor initial setup behaviour
* Adjust wizard
This commit is contained in:
WithoutPants
2021-05-13 22:15:21 +10:00
committed by GitHub
parent 5a37e6cf52
commit e0623eb302
6 changed files with 133 additions and 34 deletions

View File

@@ -12,5 +12,6 @@ query SystemStatus {
databasePath databasePath
appSchema appSchema
status status
configPath
} }
} }

View File

@@ -116,6 +116,7 @@ enum SystemStatusEnum {
type SystemStatus { type SystemStatus {
databaseSchema: Int databaseSchema: Int
databasePath: String databasePath: String
configPath: String
appSchema: Int! appSchema: Int!
status: SystemStatusEnum! status: SystemStatusEnum!
} }

View File

@@ -141,7 +141,9 @@ func (e MissingConfigError) Error() string {
return fmt.Sprintf("missing the following mandatory settings: %s", strings.Join(e.missingFields, ", ")) return fmt.Sprintf("missing the following mandatory settings: %s", strings.Join(e.missingFields, ", "))
} }
type Instance struct{} type Instance struct {
isNewSystem bool
}
var instance *Instance var instance *Instance
@@ -152,6 +154,10 @@ func GetInstance() *Instance {
return instance return instance
} }
func (i *Instance) IsNewSystem() bool {
return i.isNewSystem
}
func (i *Instance) SetConfigFile(fn string) { func (i *Instance) SetConfigFile(fn string) {
viper.SetConfigFile(fn) viper.SetConfigFile(fn)
} }
@@ -687,15 +693,13 @@ func (i *Instance) Validate() error {
return nil return nil
} }
func (i *Instance) setDefaultValues() { func (i *Instance) setDefaultValues() error {
viper.SetDefault(ParallelTasks, parallelTasksDefault) viper.SetDefault(ParallelTasks, parallelTasksDefault)
viper.SetDefault(PreviewSegmentDuration, previewSegmentDurationDefault) viper.SetDefault(PreviewSegmentDuration, previewSegmentDurationDefault)
viper.SetDefault(PreviewSegments, previewSegmentsDefault) viper.SetDefault(PreviewSegments, previewSegmentsDefault)
viper.SetDefault(PreviewExcludeStart, previewExcludeStartDefault) viper.SetDefault(PreviewExcludeStart, previewExcludeStartDefault)
viper.SetDefault(PreviewExcludeEnd, previewExcludeEndDefault) 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 // Set generated to the metadata path for backwards compat
@@ -704,12 +708,11 @@ func (i *Instance) setDefaultValues() {
// Set default scrapers and plugins paths // Set default scrapers and plugins paths
viper.SetDefault(ScrapersPath, i.GetDefaultScrapersPath()) viper.SetDefault(ScrapersPath, i.GetDefaultScrapersPath())
viper.SetDefault(PluginsPath, i.GetDefaultPluginsPath()) viper.SetDefault(PluginsPath, i.GetDefaultPluginsPath())
viper.WriteConfig() return viper.WriteConfig()
}
} }
// SetInitialConfig fills in missing required config fields // SetInitialConfig fills in missing required config fields
func (i *Instance) SetInitialConfig() { func (i *Instance) SetInitialConfig() error {
// generate some api keys // generate some api keys
const apiKeyLength = 32 const apiKeyLength = 32
@@ -723,5 +726,9 @@ func (i *Instance) SetInitialConfig() {
i.Set(SessionStoreKey, sessionStoreKey) i.Set(SessionStoreKey, sessionStoreKey)
} }
i.setDefaultValues() return i.setDefaultValues()
}
func (i *Instance) FinalizeSetup() {
i.isNewSystem = false
} }

View File

@@ -1,6 +1,7 @@
package config package config
import ( import (
"fmt"
"net" "net"
"os" "os"
"sync" "sync"
@@ -24,8 +25,22 @@ func Initialize() (*Instance, error) {
instance = &Instance{} instance = &Instance{}
flags := initFlags() flags := initFlags()
err = initConfig(flags) if err = initConfig(flags); err != nil {
return
}
initEnvs() 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 return instance, err
} }
@@ -34,26 +49,47 @@ func initConfig(flags flagStruct) error {
// The config file is called config. Leave off the file extension. // The config file is called config. Leave off the file extension.
viper.SetConfigName("config") 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(".") // Look for config in the working directory
viper.AddConfigPath("$HOME/.stash") // Look for the config in the home 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 configFile := ""
// given filename
envConfigFile := os.Getenv("STASH_CONFIG_FILE") envConfigFile := os.Getenv("STASH_CONFIG_FILE")
if envConfigFile != "" {
utils.Touch(envConfigFile) if flags.configFilePath != "" {
viper.SetConfigFile(envConfigFile) 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 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.SetInitialConfig() instance.isNewSystem = true
return nil
} else if err != nil {
return err return err
}
return nil
} }
func initFlags() flagStruct { func initFlags() flagStruct {

View File

@@ -49,6 +49,11 @@ func Initialize() *singleton {
once.Do(func() { once.Do(func() {
_ = utils.EnsureDir(paths.GetStashHomeDirectory()) _ = utils.EnsureDir(paths.GetStashHomeDirectory())
cfg, err := config.Initialize() cfg, err := config.Initialize()
if err != nil {
panic(fmt.Sprintf("error initializing configuration: %s", err.Error()))
}
initLog() initLog()
instance = &singleton{ instance = &singleton{
@@ -59,8 +64,7 @@ func Initialize() *singleton {
TxnManager: sqlite.NewTransactionManager(), TxnManager: sqlite.NewTransactionManager(),
} }
cfgFile := cfg.GetConfigFile() if !cfg.IsNewSystem() {
if cfgFile != "" {
logger.Infof("using config file: %s", cfg.GetConfigFile()) logger.Infof("using config file: %s", cfg.GetConfigFile())
if err == nil { if err == nil {
@@ -75,7 +79,11 @@ func Initialize() *singleton {
} }
} }
} else { } 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() initFFMPEG()
@@ -235,6 +243,8 @@ func (s *singleton) Setup(input models.SetupInput) error {
return fmt.Errorf("error initializing the database: %s", err.Error()) return fmt.Errorf("error initializing the database: %s", err.Error())
} }
s.Config.FinalizeSetup()
return nil return nil
} }
@@ -283,8 +293,9 @@ func (s *singleton) GetSystemStatus() *models.SystemStatus {
dbSchema := int(database.Version()) dbSchema := int(database.Version())
dbPath := database.DatabasePath() dbPath := database.DatabasePath()
appSchema := int(database.AppSchemaVersion()) appSchema := int(database.AppSchemaVersion())
configFile := s.Config.GetConfigFile()
if s.Config.GetConfigFile() == "" { if s.Config.IsNewSystem() {
status = models.SystemStatusEnumSetup status = models.SystemStatusEnumSetup
} else if dbSchema < appSchema { } else if dbSchema < appSchema {
status = models.SystemStatusEnumNeedsMigration status = models.SystemStatusEnumNeedsMigration
@@ -295,5 +306,6 @@ func (s *singleton) GetSystemStatus() *models.SystemStatus {
DatabasePath: &dbPath, DatabasePath: &dbPath,
AppSchema: appSchema, AppSchema: appSchema,
Status: status, Status: status,
ConfigPath: &configFile,
} }
} }

View File

@@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { import {
Alert, Alert,
Button, Button,
@@ -27,6 +27,12 @@ export const Setup: React.FC = () => {
const { data: systemStatus, loading: statusLoading } = useSystemStatus(); const { data: systemStatus, loading: statusLoading } = useSystemStatus();
useEffect(() => {
if (systemStatus?.systemStatus.configPath) {
setConfigLocation(systemStatus.systemStatus.configPath);
}
}, [systemStatus]);
const discordLink = ( const discordLink = (
<a href="https://discord.gg/2TsNFKt" target="_blank" rel="noreferrer"> <a href="https://discord.gg/2TsNFKt" target="_blank" rel="noreferrer">
Discord Discord
@@ -59,6 +65,38 @@ export const Setup: React.FC = () => {
setStep(step + 1); setStep(step + 1);
} }
function renderWelcomeSpecificConfig() {
return (
<>
<section>
<h2 className="mb-5">Welcome to Stash</h2>
<p className="lead text-center">
If you&apos;re reading this, then Stash couldn&apos;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.
</p>
<p>
Stash will use the following configuration file path:{" "}
<code>{configLocation}</code>
</p>
<p>
When you&apos;re ready to proceed with setting up a new system,
click Next.
</p>
</section>
<section className="mt-5">
<div className="d-flex justify-content-center">
<Button variant="primary mx-2 p-5" onClick={() => next()}>
Next
</Button>
</div>
</section>
</>
);
}
function renderWelcome() { function renderWelcome() {
return ( return (
<> <>
@@ -433,8 +471,6 @@ export const Setup: React.FC = () => {
return renderSuccess(); return renderSuccess();
} }
const steps = [renderWelcome, renderSetPaths, renderConfirm, renderFinish];
// only display setup wizard if system is not setup // only display setup wizard if system is not setup
if (statusLoading) { if (statusLoading) {
return <LoadingIndicator />; return <LoadingIndicator />;
@@ -450,6 +486,12 @@ export const Setup: React.FC = () => {
return <LoadingIndicator />; return <LoadingIndicator />;
} }
const welcomeStep =
systemStatus && systemStatus.systemStatus.configPath !== ""
? renderWelcomeSpecificConfig
: renderWelcome;
const steps = [welcomeStep, renderSetPaths, renderConfirm, renderFinish];
return ( return (
<Container> <Container>
{maybeRenderGeneratedSelectDialog()} {maybeRenderGeneratedSelectDialog()}