mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 20:34:37 +03:00
Fix initial setup issue issues (#1380)
* Refactor initial setup behaviour * Adjust wizard
This commit is contained in:
@@ -12,5 +12,6 @@ query SystemStatus {
|
|||||||
databasePath
|
databasePath
|
||||||
appSchema
|
appSchema
|
||||||
status
|
status
|
||||||
|
configPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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!
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,29 +693,26 @@ 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
|
viper.SetDefault(Database, i.GetDefaultDatabaseFilePath())
|
||||||
if i.GetConfigFile() != "" {
|
|
||||||
viper.SetDefault(Database, i.GetDefaultDatabaseFilePath())
|
|
||||||
|
|
||||||
// Set generated to the metadata path for backwards compat
|
// Set generated to the metadata path for backwards compat
|
||||||
viper.SetDefault(Generated, viper.GetString(Metadata))
|
viper.SetDefault(Generated, viper.GetString(Metadata))
|
||||||
|
|
||||||
// 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.isNewSystem = true
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
instance.SetInitialConfig()
|
return nil
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func initFlags() flagStruct {
|
func initFlags() flagStruct {
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Stash will use the following configuration file path:{" "}
|
||||||
|
<code>{configLocation}</code>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
When you'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()}
|
||||||
|
|||||||
Reference in New Issue
Block a user