mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
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:
parent
ad7fbce5f7
commit
d274f86390
12 changed files with 78 additions and 14 deletions
|
|
@ -5,6 +5,7 @@ fragment ConfigGeneralData on ConfigGeneralResult {
|
||||||
excludeImage
|
excludeImage
|
||||||
}
|
}
|
||||||
databasePath
|
databasePath
|
||||||
|
backupDirectoryPath
|
||||||
generatedPath
|
generatedPath
|
||||||
metadataPath
|
metadataPath
|
||||||
scrapersPath
|
scrapersPath
|
||||||
|
|
|
||||||
|
|
@ -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"""
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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.",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue