Add backup directory path setting (#2953)

* add backup directory path setting
* Don't default backup path
* handle migration backup path input when given filename or path

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
7dJx1qP 2022-09-29 20:00:50 -04:00 committed by GitHub
parent ad7fbce5f7
commit d274f86390
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 78 additions and 14 deletions

View file

@ -5,6 +5,7 @@ fragment ConfigGeneralData on ConfigGeneralResult {
excludeImage excludeImage
} }
databasePath databasePath
backupDirectoryPath
generatedPath generatedPath
metadataPath metadataPath
scrapersPath scrapersPath

View file

@ -37,6 +37,8 @@ input ConfigGeneralInput {
stashes: [StashConfigInput!] stashes: [StashConfigInput!]
"""Path to the SQLite database""" """Path to the SQLite database"""
databasePath: String databasePath: String
"""Path to backup directory"""
backupDirectoryPath: String
"""Path to generated files""" """Path to generated files"""
generatedPath: String generatedPath: String
"""Path to import/export files""" """Path to import/export files"""
@ -116,6 +118,8 @@ type ConfigGeneralResult {
stashes: [StashConfig!]! stashes: [StashConfig!]!
"""Path to the SQLite database""" """Path to the SQLite database"""
databasePath: String! databasePath: String!
"""Path to backup directory"""
backupDirectoryPath: String!
"""Path to generated files""" """Path to generated files"""
generatedPath: String! generatedPath: String!
"""Path to import/export files""" """Path to import/export files"""

View file

@ -84,6 +84,15 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input ConfigGen
c.Set(config.Database, input.DatabasePath) c.Set(config.Database, input.DatabasePath)
} }
existingBackupDirectoryPath := c.GetBackupDirectoryPath()
if input.BackupDirectoryPath != nil && existingBackupDirectoryPath != *input.BackupDirectoryPath {
if err := validateDir(config.BackupDirectoryPath, *input.BackupDirectoryPath, false); err != nil {
return makeConfigGeneralResult(), err
}
c.Set(config.BackupDirectoryPath, input.BackupDirectoryPath)
}
existingGeneratedPath := c.GetGeneratedPath() existingGeneratedPath := c.GetGeneratedPath()
if input.GeneratedPath != nil && existingGeneratedPath != *input.GeneratedPath { if input.GeneratedPath != nil && existingGeneratedPath != *input.GeneratedPath {
if err := validateDir(config.Generated, *input.GeneratedPath, false); err != nil { if err := validateDir(config.Generated, *input.GeneratedPath, false); err != nil {

View file

@ -124,7 +124,13 @@ func (r *mutationResolver) BackupDatabase(ctx context.Context, input BackupDatab
backupPath = f.Name() backupPath = f.Name()
f.Close() f.Close()
} else { } else {
backupPath = database.DatabaseBackupPath() backupDirectoryPath := mgr.Config.GetBackupDirectoryPathOrDefault()
if backupDirectoryPath != "" {
if err := fsutil.EnsureDir(backupDirectoryPath); err != nil {
return nil, fmt.Errorf("could not create backup directory %v: %w", backupDirectoryPath, err)
}
}
backupPath = database.DatabaseBackupPath(backupDirectoryPath)
} }
err := database.Backup(backupPath) err := database.Backup(backupPath)
@ -141,7 +147,7 @@ func (r *mutationResolver) BackupDatabase(ctx context.Context, input BackupDatab
baseURL, _ := ctx.Value(BaseURLCtxKey).(string) baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
fn := filepath.Base(database.DatabaseBackupPath()) fn := filepath.Base(database.DatabaseBackupPath(""))
ret := baseURL + "/downloads/" + downloadHash + "/" + fn ret := baseURL + "/downloads/" + downloadHash + "/" + fn
return &ret, nil return &ret, nil
} else { } else {

View file

@ -85,6 +85,7 @@ func makeConfigGeneralResult() *ConfigGeneralResult {
return &ConfigGeneralResult{ return &ConfigGeneralResult{
Stashes: config.GetStashPaths(), Stashes: config.GetStashPaths(),
DatabasePath: config.GetDatabasePath(), DatabasePath: config.GetDatabasePath(),
BackupDirectoryPath: config.GetBackupDirectoryPath(),
GeneratedPath: config.GetGeneratedPath(), GeneratedPath: config.GetGeneratedPath(),
MetadataPath: config.GetMetadataPath(), MetadataPath: config.GetMetadataPath(),
ConfigFilePath: config.GetConfigFile(), ConfigFilePath: config.GetConfigFile(),

View file

@ -28,6 +28,7 @@ var officialBuild string
const ( const (
Stash = "stash" Stash = "stash"
Cache = "cache" Cache = "cache"
BackupDirectoryPath = "backup_directory_path"
Generated = "generated" Generated = "generated"
Metadata = "metadata" Metadata = "metadata"
Downloads = "downloads" Downloads = "downloads"
@ -525,6 +526,19 @@ func (i *Instance) GetDatabasePath() string {
return i.getString(Database) return i.getString(Database)
} }
func (i *Instance) GetBackupDirectoryPath() string {
return i.getString(BackupDirectoryPath)
}
func (i *Instance) GetBackupDirectoryPathOrDefault() string {
ret := i.GetBackupDirectoryPath()
if ret == "" {
return i.GetConfigPath()
}
return ret
}
func (i *Instance) GetJWTSignKey() []byte { func (i *Instance) GetJWTSignKey() []byte {
return []byte(i.getString(JWTSignKey)) return []byte(i.getString(JWTSignKey))
} }
@ -1351,6 +1365,7 @@ func (i *Instance) setDefaultValues(write bool) error {
// Set default scrapers and plugins paths // Set default scrapers and plugins paths
i.main.SetDefault(ScrapersPath, defaultScrapersPath) i.main.SetDefault(ScrapersPath, defaultScrapersPath)
i.main.SetDefault(PluginsPath, defaultPluginsPath) i.main.SetDefault(PluginsPath, defaultPluginsPath)
if write { if write {
return i.main.WriteConfig() return i.main.WriteConfig()
} }

View file

@ -26,6 +26,7 @@ func TestConcurrentConfigAccess(t *testing.T) {
i.GetConfigFile() i.GetConfigFile()
i.GetConfigPath() i.GetConfigPath()
i.GetDefaultDatabaseFilePath() i.GetDefaultDatabaseFilePath()
i.Set(BackupDirectoryPath, i.GetBackupDirectoryPath())
i.GetStashPaths() i.GetStashPaths()
_ = i.ValidateStashBoxes(nil) _ = i.ValidateStashBoxes(nil)
_ = i.Validate() _ = i.Validate()

View file

@ -645,7 +645,14 @@ func (s *Manager) Migrate(ctx context.Context, input MigrateInput) error {
// migration fails // migration fails
backupPath := input.BackupPath backupPath := input.BackupPath
if backupPath == "" { if backupPath == "" {
backupPath = database.DatabaseBackupPath() backupPath = database.DatabaseBackupPath(s.Config.GetBackupDirectoryPath())
} else {
// check if backup path is a filename or path
// filename goes into backup directory, path is kept as is
filename := filepath.Base(backupPath)
if backupPath == filename {
backupPath = filepath.Join(s.Config.GetBackupDirectoryPathOrDefault(), filename)
}
} }
// perform database backup // perform database backup

View file

@ -7,6 +7,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"path/filepath"
"sync" "sync"
"time" "time"
@ -253,8 +254,14 @@ func (db *Database) DatabasePath() string {
return db.dbPath return db.dbPath
} }
func (db *Database) DatabaseBackupPath() string { func (db *Database) DatabaseBackupPath(backupDirectoryPath string) string {
return fmt.Sprintf("%s.%d.%s", db.dbPath, db.schemaVersion, time.Now().Format("20060102_150405")) fn := fmt.Sprintf("%s.%d.%s", db.dbPath, db.schemaVersion, time.Now().Format("20060102_150405"))
if backupDirectoryPath != "" {
return filepath.Join(backupDirectoryPath, fn)
}
return fn
} }
func (db *Database) Version() uint { func (db *Database) Version() uint {

View file

@ -155,6 +155,14 @@ export const SettingsConfigurationPanel: React.FC = () => {
value={general.pythonPath ?? undefined} value={general.pythonPath ?? undefined}
onChange={(v) => saveGeneral({ pythonPath: v })} onChange={(v) => saveGeneral({ pythonPath: v })}
/> />
<StringSetting
id="backup-directory-path"
headingID="config.general.backup_directory_path.heading"
subHeadingID="config.general.backup_directory_path.description"
value={general.backupDirectoryPath ?? undefined}
onChange={(v) => saveGeneral({ backupDirectoryPath: v })}
/>
</SettingSection> </SettingSection>
<SettingSection headingID="config.general.hashing"> <SettingSection headingID="config.general.hashing">

View file

@ -5,6 +5,7 @@ After migrating, please run a scan on your entire library to populate missing da
* Import/export schema has changed and is incompatible with the previous version. * Import/export schema has changed and is incompatible with the previous version.
### ✨ New Features ### ✨ New Features
* Add backup location configuration setting. ([#2953](https://github.com/stashapp/stash/pull/2953))
* Allow overriding UI localisation strings. ([#2837](https://github.com/stashapp/stash/pull/2837)) * Allow overriding UI localisation strings. ([#2837](https://github.com/stashapp/stash/pull/2837))
* Populate name from query field when creating new performer/studio/tag/gallery. ([#2701](https://github.com/stashapp/stash/pull/2701)) * Populate name from query field when creating new performer/studio/tag/gallery. ([#2701](https://github.com/stashapp/stash/pull/2701))
* Added support for identical files. Identical files are assigned to the same scene/gallery/image and can be viewed in File Info. ([#2676](https://github.com/stashapp/stash/pull/2676)) * Added support for identical files. Identical files are assigned to the same scene/gallery/image and can be viewed in File Info. ([#2676](https://github.com/stashapp/stash/pull/2676))

View file

@ -244,6 +244,10 @@
"username": "Username", "username": "Username",
"username_desc": "Username to access Stash. Leave blank to disable user authentication" "username_desc": "Username to access Stash. Leave blank to disable user authentication"
}, },
"backup_directory_path": {
"description": "Directory location for SQLite database file backups",
"heading": "Backup Directory Path"
},
"cache_location": "Directory location of the cache", "cache_location": "Directory location of the cache",
"cache_path_head": "Cache Path", "cache_path_head": "Cache Path",
"calculate_md5_and_ohash_desc": "Calculate MD5 checksum in addition to oshash. Enabling will cause initial scans to be slower. File naming hash must be set to oshash to disable MD5 calculation.", "calculate_md5_and_ohash_desc": "Calculate MD5 checksum in addition to oshash. Enabling will cause initial scans to be slower. File naming hash must be set to oshash to disable MD5 calculation.",