mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
Backups: Rename "backup-index" config option to "backup-database" #4243
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
@@ -36,7 +36,7 @@ export class ConfigOptions extends Model {
|
||||
OriginalsLimit: 0,
|
||||
Workers: 0,
|
||||
WakeupInterval: 0,
|
||||
BackupIndex: false,
|
||||
BackupDatabase: false,
|
||||
DisableWebDAV: config.values.disable.webdav,
|
||||
DisableSettings: config.values.disable.settings,
|
||||
DisablePlaces: config.values.disable.places,
|
||||
|
||||
@@ -37,9 +37,9 @@
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.BackupIndex"
|
||||
v-model="settings.BackupDatabase"
|
||||
:disabled="busy"
|
||||
class="ma-0 pa-0 input-backup-index"
|
||||
class="ma-0 pa-0 input-backup-database"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Database Backups')"
|
||||
:hint="$gettext('Create index backups based on the configured schedule.')"
|
||||
|
||||
@@ -14,16 +14,16 @@ import (
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
)
|
||||
|
||||
const backupDescription = "A user-defined filename or - for stdout can be passed as the first argument. " +
|
||||
"The -i parameter can be omitted in this case.\n" +
|
||||
" Make sure to run the command with exec -T when using Docker to prevent log messages from being sent to stdout.\n" +
|
||||
" The index backup and album file paths are automatically detected if not specified explicitly."
|
||||
const backupDescription = `A custom filename for the database backup (or - to send the backup to stdout) can optionally be passed as argument.
|
||||
The --database flag can be omitted in this case. When using Docker, please run the docker command with the -T flag
|
||||
to prevent log messages from being sent to stdout. If nothing else is specified, the database and album backup paths
|
||||
will be automatically determined based on the current configuration.`
|
||||
|
||||
// BackupCommand configures the command name, flags, and action.
|
||||
var BackupCommand = cli.Command{
|
||||
Name: "backup",
|
||||
Description: backupDescription,
|
||||
Usage: "Creates an index database dump and/or album YAML file backups",
|
||||
Usage: "Creates an index database backup and/or album YAML backup files",
|
||||
ArgsUsage: "[filename]",
|
||||
Flags: backupFlags,
|
||||
Action: backupAction,
|
||||
@@ -32,27 +32,27 @@ var BackupCommand = cli.Command{
|
||||
var backupFlags = []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
Usage: "replace existing index backup files",
|
||||
Usage: "replace the index database backup file, if it exists",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "albums, a",
|
||||
Usage: "export album metadata to YAML files located in the backup path",
|
||||
Usage: "create or update album YAML backup files in the album backup path",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "albums-path",
|
||||
Usage: "custom album backup `PATH`",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "index, i",
|
||||
Usage: "create index database backup (sent to stdout if - is passed as first argument)",
|
||||
Name: "database, index, i",
|
||||
Usage: "create a database backup in the database backup path with the date as filename, or write it to the specified file (stdout if - is passed as filename)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "index-path",
|
||||
Usage: "custom index backup `PATH`",
|
||||
Name: "database-path, index-path",
|
||||
Usage: "custom database backup `PATH`",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "retain, r",
|
||||
Usage: "`NUMBER` of index backups to keep (-1 to keep all)",
|
||||
Usage: "`NUMBER` of database backups to keep (-1 to keep all)",
|
||||
Value: config.DefaultBackupRetain,
|
||||
},
|
||||
}
|
||||
@@ -61,14 +61,14 @@ var backupFlags = []cli.Flag{
|
||||
func backupAction(ctx *cli.Context) error {
|
||||
// Use command argument as backup file name.
|
||||
fileName := ctx.Args().First()
|
||||
backupPath := ctx.String("index-path")
|
||||
backupIndex := ctx.Bool("index") || fileName != "" || backupPath != ""
|
||||
databasePath := ctx.String("database-path")
|
||||
backupDatabase := ctx.Bool("database") || fileName != "" || databasePath != ""
|
||||
albumsPath := ctx.String("albums-path")
|
||||
backupAlbums := ctx.Bool("albums") || albumsPath != ""
|
||||
force := ctx.Bool("force")
|
||||
retain := ctx.Int("retain")
|
||||
|
||||
if !backupIndex && !backupAlbums {
|
||||
if !backupDatabase && !backupAlbums {
|
||||
return cli.ShowSubcommandHelp(ctx)
|
||||
}
|
||||
|
||||
@@ -86,30 +86,30 @@ func backupAction(ctx *cli.Context) error {
|
||||
conf.RegisterDb()
|
||||
defer conf.Shutdown()
|
||||
|
||||
if backupIndex {
|
||||
// If empty, use default backup file name.
|
||||
if backupDatabase {
|
||||
// Use default if no explicit filename was provided.
|
||||
if fileName == "" {
|
||||
if !fs.PathWritable(backupPath) {
|
||||
if backupPath != "" {
|
||||
log.Warnf("custom index backup path not writable, using default")
|
||||
if !fs.PathWritable(databasePath) {
|
||||
if databasePath != "" {
|
||||
log.Warnf("backup: specified database backup path not writable, using default backup path")
|
||||
}
|
||||
|
||||
backupPath = conf.BackupIndexPath()
|
||||
databasePath = conf.BackupDatabasePath()
|
||||
}
|
||||
|
||||
backupFile := time.Now().UTC().Format("2006-01-02") + ".sql"
|
||||
fileName = filepath.Join(backupPath, backupFile)
|
||||
fileName = filepath.Join(databasePath, backupFile)
|
||||
}
|
||||
|
||||
if err = photoprism.BackupIndex(backupPath, fileName, fileName == "-", force, retain); err != nil {
|
||||
return fmt.Errorf("failed to create index backup: %w", err)
|
||||
if err = photoprism.BackupDatabase(databasePath, fileName, fileName == "-", force, retain); err != nil {
|
||||
return fmt.Errorf("failed to create database backup: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if backupAlbums {
|
||||
if !fs.PathWritable(albumsPath) {
|
||||
if albumsPath != "" {
|
||||
log.Warnf("album files path not writable, using default")
|
||||
log.Warnf("backup: specified album backup path not writable, using default backup path")
|
||||
}
|
||||
|
||||
albumsPath = conf.BackupAlbumsPath()
|
||||
@@ -118,7 +118,7 @@ func backupAction(ctx *cli.Context) error {
|
||||
if count, backupErr := photoprism.BackupAlbums(albumsPath, true); backupErr != nil {
|
||||
return backupErr
|
||||
} else {
|
||||
log.Infof("exported %s", english.Plural(count, "album", "albums"))
|
||||
log.Infof("backup: saved %s", english.Plural(count, "album backup", "album backups"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,15 +13,15 @@ import (
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
)
|
||||
|
||||
const restoreDescription = "A user-defined filename or - for stdin can be passed as the first argument. " +
|
||||
"The -i parameter can be omitted in this case.\n" +
|
||||
" The index backup and album file paths are automatically detected if not specified explicitly."
|
||||
const restoreDescription = `A custom filename for the database backup (or - to read the backup from stdin) can optionally be passed as argument.
|
||||
The --database flag can be omitted in this case. If nothing else is specified, the database and album backup paths
|
||||
will be automatically determined based on the current configuration.`
|
||||
|
||||
// RestoreCommand configures the command name, flags, and action.
|
||||
var RestoreCommand = cli.Command{
|
||||
Name: "restore",
|
||||
Description: restoreDescription,
|
||||
Usage: "Restores the index from a database dump and/or album YAML file backups",
|
||||
Usage: "Restores the index database and/or album metadata from a backup",
|
||||
ArgsUsage: "[filename]",
|
||||
Flags: restoreFlags,
|
||||
Action: restoreAction,
|
||||
@@ -30,37 +30,37 @@ var RestoreCommand = cli.Command{
|
||||
var restoreFlags = []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
Usage: "replace existing index schema and data",
|
||||
Usage: "replace the index database with the backup, if it already exists",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "albums, a",
|
||||
Usage: "restore albums from YAML files located in the backup path",
|
||||
Usage: "restore albums from the YAML backup files found in the album backup path",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "albums-path",
|
||||
Usage: "custom album backup `PATH`",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "index, i",
|
||||
Usage: "restore index from the specified file or the most recent file in the backup path (from stdin if - is passed as first argument)",
|
||||
Name: "database, index, i",
|
||||
Usage: "restore the index database from the specified file (stdin if - is passed as filename), or the most recent backup found in the database backup path",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "index-path",
|
||||
Usage: "custom index backup `PATH`",
|
||||
Name: "database-path, index-path",
|
||||
Usage: "custom database backup `PATH`",
|
||||
},
|
||||
}
|
||||
|
||||
// restoreAction restores a database backup.
|
||||
func restoreAction(ctx *cli.Context) error {
|
||||
// Use command argument as backup file name.
|
||||
indexFileName := ctx.Args().First()
|
||||
indexPath := ctx.String("index-path")
|
||||
restoreIndex := ctx.Bool("index") || indexFileName != "" || indexPath != ""
|
||||
databaseFile := ctx.Args().First()
|
||||
databasePath := ctx.String("database-path")
|
||||
restoreDatabase := ctx.Bool("database") || databaseFile != "" || databasePath != ""
|
||||
force := ctx.Bool("force")
|
||||
albumsPath := ctx.String("albums-path")
|
||||
restoreAlbums := ctx.Bool("albums") || albumsPath != ""
|
||||
|
||||
if !restoreIndex && !restoreAlbums {
|
||||
if !restoreDatabase && !restoreAlbums {
|
||||
return cli.ShowSubcommandHelp(ctx)
|
||||
}
|
||||
|
||||
@@ -78,17 +78,18 @@ func restoreAction(ctx *cli.Context) error {
|
||||
conf.RegisterDb()
|
||||
defer conf.Shutdown()
|
||||
|
||||
// Restore index from specified file?
|
||||
if !restoreIndex {
|
||||
// Restore database from backup dump?
|
||||
if !restoreDatabase {
|
||||
// Do nothing.
|
||||
} else if err = photoprism.RestoreIndex(indexPath, indexFileName, indexFileName == "-", force); err != nil {
|
||||
} else if err = photoprism.RestoreDatabase(databasePath, databaseFile, databaseFile == "-", force); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infoln("migrating index database schema")
|
||||
log.Infoln("restore: migrating index database schema")
|
||||
|
||||
conf.InitDb()
|
||||
|
||||
// Restore albums from YAML backup files?
|
||||
if restoreAlbums {
|
||||
get.SetConfig(conf)
|
||||
|
||||
@@ -97,21 +98,21 @@ func restoreAction(ctx *cli.Context) error {
|
||||
}
|
||||
|
||||
if !fs.PathExists(albumsPath) {
|
||||
log.Warnf("album files path %s not found", clean.Log(albumsPath))
|
||||
log.Warnf("restore: album files path %s not found", clean.Log(albumsPath))
|
||||
} else {
|
||||
log.Infof("restoring albums from %s", clean.Log(albumsPath))
|
||||
log.Infof("restore: restoring albums from %s", clean.Log(albumsPath))
|
||||
|
||||
if count, err := photoprism.RestoreAlbums(albumsPath, true); err != nil {
|
||||
return err
|
||||
} else {
|
||||
log.Infof("restored %s from YAML files", english.Plural(count, "album", "albums"))
|
||||
log.Infof("restore: restored %s from YAML files", english.Plural(count, "album", "albums"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
elapsed := time.Since(start)
|
||||
|
||||
log.Infof("restored in %s", elapsed)
|
||||
log.Infof("completed in %s", elapsed)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -32,24 +32,6 @@ func (c *Config) BackupBasePath() string {
|
||||
return filepath.Join(c.StoragePath(), "backup")
|
||||
}
|
||||
|
||||
// BackupAlbumsPath returns the backup path for album YAML files.
|
||||
func (c *Config) BackupAlbumsPath() string {
|
||||
if dir := filepath.Join(c.StoragePath(), "albums"); fs.PathExists(dir) {
|
||||
return dir
|
||||
}
|
||||
|
||||
return c.BackupPath("albums")
|
||||
}
|
||||
|
||||
// BackupIndexPath returns the backup path for index database dumps.
|
||||
func (c *Config) BackupIndexPath() string {
|
||||
if driver := c.DatabaseDriver(); driver != "" {
|
||||
return c.BackupPath(driver)
|
||||
}
|
||||
|
||||
return c.BackupPath("index")
|
||||
}
|
||||
|
||||
// BackupSchedule returns the backup schedule in cron format, e.g. "0 12 * * *" for daily at noon.
|
||||
func (c *Config) BackupSchedule() string {
|
||||
if c.options.BackupSchedule == "" {
|
||||
@@ -73,9 +55,18 @@ func (c *Config) BackupRetain() int {
|
||||
return c.options.BackupRetain
|
||||
}
|
||||
|
||||
// BackupIndex checks if SQL database dumps should be created based on the configured schedule.
|
||||
func (c *Config) BackupIndex() bool {
|
||||
return c.options.BackupIndex
|
||||
// BackupDatabase checks if index database backups should be created based on the configured schedule.
|
||||
func (c *Config) BackupDatabase() bool {
|
||||
return c.options.BackupDatabase
|
||||
}
|
||||
|
||||
// BackupDatabasePath returns the backup path for index database dumps.
|
||||
func (c *Config) BackupDatabasePath() string {
|
||||
if driver := c.DatabaseDriver(); driver != "" {
|
||||
return c.BackupPath(driver)
|
||||
}
|
||||
|
||||
return c.BackupPath("index")
|
||||
}
|
||||
|
||||
// BackupAlbums checks if album YAML file backups should be created based on the configured schedule.
|
||||
@@ -83,6 +74,15 @@ func (c *Config) BackupAlbums() bool {
|
||||
return c.options.BackupAlbums
|
||||
}
|
||||
|
||||
// BackupAlbumsPath returns the backup path for album YAML files.
|
||||
func (c *Config) BackupAlbumsPath() string {
|
||||
if dir := filepath.Join(c.StoragePath(), "albums"); fs.PathExists(dir) {
|
||||
return dir
|
||||
}
|
||||
|
||||
return c.BackupPath("albums")
|
||||
}
|
||||
|
||||
// DisableBackups checks if creating and updating sidecar YAML files should be disabled.
|
||||
func (c *Config) DisableBackups() bool {
|
||||
if !c.SidecarWritable() {
|
||||
|
||||
@@ -16,16 +16,6 @@ func TestConfig_BackupBasePath(t *testing.T) {
|
||||
assert.Contains(t, c.BackupBasePath(), "/storage/testdata/backup")
|
||||
}
|
||||
|
||||
func TestConfig_BackupAlbumsPath(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
assert.Contains(t, c.BackupAlbumsPath(), "/albums")
|
||||
}
|
||||
|
||||
func TestConfig_BackupIndexPath(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
assert.Contains(t, c.BackupIndexPath(), "/storage/testdata/backup/sqlite")
|
||||
}
|
||||
|
||||
func TestConfig_BackupSchedule(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
assert.Equal(t, DefaultBackupSchedule, c.BackupSchedule())
|
||||
@@ -36,13 +26,18 @@ func TestConfig_BackupRetain(t *testing.T) {
|
||||
assert.Equal(t, DefaultBackupRetain, c.BackupRetain())
|
||||
}
|
||||
|
||||
func TestConfig_BackupIndex(t *testing.T) {
|
||||
func TestConfig_BackupDatabase(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
assert.True(t, c.BackupIndex())
|
||||
c.options.BackupIndex = false
|
||||
assert.False(t, c.BackupIndex())
|
||||
c.options.BackupIndex = true
|
||||
assert.True(t, c.BackupIndex())
|
||||
assert.True(t, c.BackupDatabase())
|
||||
c.options.BackupDatabase = false
|
||||
assert.False(t, c.BackupDatabase())
|
||||
c.options.BackupDatabase = true
|
||||
assert.True(t, c.BackupDatabase())
|
||||
}
|
||||
|
||||
func TestConfig_BackupDatabasePath(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
assert.Contains(t, c.BackupDatabasePath(), "/storage/testdata/backup/sqlite")
|
||||
}
|
||||
|
||||
func TestConfig_BackupAlbums(t *testing.T) {
|
||||
@@ -55,6 +50,11 @@ func TestConfig_BackupAlbums(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestConfig_BackupAlbumsPath(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
assert.Contains(t, c.BackupAlbumsPath(), "/albums")
|
||||
}
|
||||
|
||||
func TestConfig_DisableBackups(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
assert.False(t, c.DisableBackups())
|
||||
|
||||
@@ -158,7 +158,7 @@ var Flags = CliFlags{
|
||||
}}, {
|
||||
Flag: cli.BoolFlag{
|
||||
Name: "sidecar-yaml",
|
||||
Usage: "export picture metadata to YAML sidecar files",
|
||||
Usage: "save picture metadata to YAML sidecar files",
|
||||
EnvVar: EnvVar("SIDECAR_YAML"),
|
||||
}, DocDefault: "true"}, {
|
||||
Flag: cli.StringFlag{
|
||||
@@ -204,13 +204,13 @@ var Flags = CliFlags{
|
||||
EnvVar: EnvVar("BACKUP_RETAIN"),
|
||||
}}, {
|
||||
Flag: cli.BoolFlag{
|
||||
Name: "backup-index",
|
||||
Usage: "create index database backups based on the configured schedule",
|
||||
EnvVar: EnvVar("BACKUP_INDEX"),
|
||||
Name: "backup-database",
|
||||
Usage: "create index backups based on the configured schedule",
|
||||
EnvVar: EnvVar("BACKUP_DATABASE"),
|
||||
}, DocDefault: "true"}, {
|
||||
Flag: cli.BoolFlag{
|
||||
Name: "backup-albums",
|
||||
Usage: "export album metadata to YAML backup files",
|
||||
Usage: "save album metadata to YAML backup files",
|
||||
EnvVar: EnvVar("BACKUP_ALBUMS"),
|
||||
}, DocDefault: "true"}, {
|
||||
Flag: cli.IntFlag{
|
||||
|
||||
@@ -60,7 +60,7 @@ type Options struct {
|
||||
BackupPath string `yaml:"BackupPath" json:"-" flag:"backup-path"`
|
||||
BackupSchedule string `yaml:"BackupSchedule" json:"BackupSchedule" flag:"backup-schedule"`
|
||||
BackupRetain int `yaml:"BackupRetain" json:"BackupRetain" flag:"backup-retain"`
|
||||
BackupIndex bool `yaml:"BackupIndex" json:"BackupIndex" flag:"backup-index" default:"true"`
|
||||
BackupDatabase bool `yaml:"BackupDatabase" json:"BackupDatabase" flag:"backup-database" default:"true"`
|
||||
BackupAlbums bool `yaml:"BackupAlbums" json:"BackupAlbums" flag:"backup-albums" default:"true"`
|
||||
IndexWorkers int `yaml:"IndexWorkers" json:"IndexWorkers" flag:"index-workers"`
|
||||
IndexSchedule string `yaml:"IndexSchedule" json:"IndexSchedule" flag:"index-schedule"`
|
||||
@@ -216,7 +216,7 @@ func NewOptions(ctx *cli.Context) *Options {
|
||||
|
||||
// Enable database backups and YAML exports by default.
|
||||
c.SidecarYaml = true
|
||||
c.BackupIndex = true
|
||||
c.BackupDatabase = true
|
||||
c.BackupAlbums = true
|
||||
|
||||
// Load defaults from YAML file?
|
||||
|
||||
@@ -81,8 +81,8 @@ func (c *Config) Report() (rows [][]string, cols []string) {
|
||||
{"backup-path", c.BackupBasePath()},
|
||||
{"backup-schedule", c.BackupSchedule()},
|
||||
{"backup-retain", fmt.Sprintf("%d", c.BackupRetain())},
|
||||
{"backup-index", fmt.Sprintf("%t", c.BackupIndex())},
|
||||
{"backup-index-path", c.BackupIndexPath()},
|
||||
{"backup-database", fmt.Sprintf("%t", c.BackupDatabase())},
|
||||
{"backup-database-path", c.BackupDatabasePath()},
|
||||
{"backup-albums", fmt.Sprintf("%t", c.BackupAlbums())},
|
||||
{"backup-albums-path", c.BackupAlbumsPath()},
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ func BackupAlbums(backupPath string, force bool) (count int, err error) {
|
||||
backupPath = Config().BackupAlbumsPath()
|
||||
}
|
||||
|
||||
log.Infof("exporting album metadata to YAML backup files")
|
||||
log.Debugf("album backups will be created in %s", clean.Log(backupPath))
|
||||
log.Debugf("backup: album backups will be stored in %s", clean.Log(backupPath))
|
||||
log.Infof("backup: saving album metadata in YAML backup files")
|
||||
|
||||
var latest time.Time
|
||||
|
||||
|
||||
@@ -20,19 +20,23 @@ import (
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
)
|
||||
|
||||
var backupIndexMutex = sync.Mutex{}
|
||||
var backupDatabaseMutex = sync.Mutex{}
|
||||
|
||||
// BackupIndex creates an SQL backup dump with the specified file and path name.
|
||||
func BackupIndex(backupPath, fileName string, toStdOut, force bool, retain int) (err error) {
|
||||
// Make sure only one backup/restore operation is running at a time.
|
||||
backupIndexMutex.Lock()
|
||||
defer backupIndexMutex.Unlock()
|
||||
// BackupDatabase creates a database backup dump with the specified file and path name.
|
||||
func BackupDatabase(backupPath, fileName string, toStdOut, force bool, retain int) (err error) {
|
||||
// Ensure that only one database backup/restore operation is running at a time.
|
||||
backupDatabaseMutex.Lock()
|
||||
defer backupDatabaseMutex.Unlock()
|
||||
|
||||
// Backup action shown in logs.
|
||||
backupAction := "creating"
|
||||
|
||||
// Get configuration.
|
||||
c := Config()
|
||||
|
||||
if !toStdOut {
|
||||
if backupPath == "" {
|
||||
backupPath = c.BackupIndexPath()
|
||||
backupPath = c.BackupDatabasePath()
|
||||
}
|
||||
|
||||
// Create the backup path if it does not already exist.
|
||||
@@ -50,10 +54,12 @@ func BackupIndex(backupPath, fileName string, toStdOut, force bool, retain int)
|
||||
fileName = filepath.Join(backupPath, backupFile)
|
||||
}
|
||||
|
||||
log.Debugf("backup: database backups will be stored in %s", clean.Log(backupPath))
|
||||
|
||||
if _, err = os.Stat(fileName); err == nil && !force {
|
||||
return fmt.Errorf("%s already exists", clean.Log(filepath.Base(fileName)))
|
||||
} else if err == nil {
|
||||
log.Warnf("replacing existing index backup")
|
||||
backupAction = "replacing"
|
||||
}
|
||||
|
||||
// Create backup path if not exists.
|
||||
@@ -79,7 +85,7 @@ func BackupIndex(backupPath, fileName string, toStdOut, force bool, retain int)
|
||||
)
|
||||
case config.SQLite3:
|
||||
if !fs.FileExistsNotEmpty(c.DatabaseFile()) {
|
||||
return fmt.Errorf("sqlite database %s not found", clean.LogQuote(c.DatabaseFile()))
|
||||
return fmt.Errorf("sqlite database file %s not found", clean.LogQuote(c.DatabaseFile()))
|
||||
}
|
||||
|
||||
cmd = exec.Command(
|
||||
@@ -94,12 +100,12 @@ func BackupIndex(backupPath, fileName string, toStdOut, force bool, retain int)
|
||||
// Write to stdout or file.
|
||||
var f *os.File
|
||||
if toStdOut {
|
||||
log.Infof("writing index backup to stdout")
|
||||
log.Infof("backup: sending database backup to stdout")
|
||||
f = os.Stdout
|
||||
} else if f, err = os.OpenFile(fileName, os.O_TRUNC|os.O_RDWR|os.O_CREATE, fs.ModeFile); err != nil {
|
||||
return fmt.Errorf("failed to create %s: %s", clean.Log(fileName), err)
|
||||
return fmt.Errorf("failed to create %s (%s)", clean.Log(fileName), err)
|
||||
} else {
|
||||
log.Infof("creating index backup in %s", clean.Log(filepath.Base(fileName)))
|
||||
log.Infof("backup: %s database backup file %s", backupAction, clean.Log(filepath.Base(fileName)))
|
||||
defer f.Close()
|
||||
}
|
||||
|
||||
@@ -128,20 +134,20 @@ func BackupIndex(backupPath, fileName string, toStdOut, force bool, retain int)
|
||||
}
|
||||
|
||||
if len(files) == 0 {
|
||||
return fmt.Errorf("found no index backups files in %s", backupPath)
|
||||
return fmt.Errorf("found no database backup files in %s", backupPath)
|
||||
} else if len(files) <= retain {
|
||||
return nil
|
||||
}
|
||||
|
||||
sort.Strings(files)
|
||||
|
||||
log.Infof("retaining %s", english.Plural(retain, "index backup", "index backups"))
|
||||
log.Infof("backup: retaining %s", english.Plural(retain, "database backup", "database backups"))
|
||||
|
||||
for i := 0; i < len(files)-retain; i++ {
|
||||
if err = os.Remove(files[i]); err != nil {
|
||||
return err
|
||||
} else {
|
||||
log.Infof("removed old backup file %s", clean.Log(filepath.Base(files[i])))
|
||||
log.Infof("backup: removed database backup file %s", clean.Log(filepath.Base(files[i])))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,18 +20,18 @@ import (
|
||||
|
||||
const SqlBackupFileNamePattern = "[2-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9].sql"
|
||||
|
||||
// RestoreIndex restores the index from an SQL backup dump with the specified file and path name.
|
||||
func RestoreIndex(backupPath, fileName string, fromStdIn, force bool) (err error) {
|
||||
// Make sure only one backup/restore operation is running at a time.
|
||||
backupIndexMutex.Lock()
|
||||
defer backupIndexMutex.Unlock()
|
||||
// RestoreDatabase restores the database from a backup file with the specified path and name.
|
||||
func RestoreDatabase(backupPath, fileName string, fromStdIn, force bool) (err error) {
|
||||
// Ensure that only one database backup/restore operation is running at a time.
|
||||
backupDatabaseMutex.Lock()
|
||||
defer backupDatabaseMutex.Unlock()
|
||||
|
||||
c := Config()
|
||||
|
||||
// If empty, use default backup file name.
|
||||
if !fromStdIn && fileName == "" {
|
||||
if backupPath == "" {
|
||||
backupPath = c.BackupIndexPath()
|
||||
backupPath = c.BackupDatabasePath()
|
||||
}
|
||||
|
||||
files, globErr := filepath.Glob(filepath.Join(regexp.QuoteMeta(backupPath), SqlBackupFileNamePattern))
|
||||
@@ -41,7 +41,7 @@ func RestoreIndex(backupPath, fileName string, fromStdIn, force bool) (err error
|
||||
}
|
||||
|
||||
if len(files) == 0 {
|
||||
return fmt.Errorf("found no backups files in %s", backupPath)
|
||||
return fmt.Errorf("found no database backup files in %s", backupPath)
|
||||
}
|
||||
|
||||
sort.Strings(files)
|
||||
@@ -49,7 +49,7 @@ func RestoreIndex(backupPath, fileName string, fromStdIn, force bool) (err error
|
||||
fileName = files[len(files)-1]
|
||||
|
||||
if !fs.FileExistsNotEmpty(fileName) {
|
||||
return fmt.Errorf("no backup found in %s", filepath.Base(fileName))
|
||||
return fmt.Errorf("no database backup found in %s", filepath.Base(fileName))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,9 +62,9 @@ func RestoreIndex(backupPath, fileName string, fromStdIn, force bool) (err error
|
||||
if counts.Photos == 0 {
|
||||
// Do nothing;
|
||||
} else if !force {
|
||||
return fmt.Errorf("found existing index with %d pictures, backup will not be restored", counts.Photos)
|
||||
return fmt.Errorf("found an existing index with %d pictures, backup will not be restored", counts.Photos)
|
||||
} else {
|
||||
log.Warnf("replacing the existing index with %d pictures", counts.Photos)
|
||||
log.Warnf("restore: existing index with %d pictures will be replaced", counts.Photos)
|
||||
}
|
||||
|
||||
tables := entity.Entities
|
||||
@@ -84,7 +84,7 @@ func RestoreIndex(backupPath, fileName string, fromStdIn, force bool) (err error
|
||||
c.DatabaseName(),
|
||||
)
|
||||
case config.SQLite3:
|
||||
log.Infoln("dropping existing tables")
|
||||
log.Infoln("restore: dropping existing sqlite database tables")
|
||||
tables.Drop(c.Db())
|
||||
cmd = exec.Command(
|
||||
c.SqliteBin(),
|
||||
@@ -97,12 +97,12 @@ func RestoreIndex(backupPath, fileName string, fromStdIn, force bool) (err error
|
||||
// Read from stdin or file.
|
||||
var f *os.File
|
||||
if fromStdIn {
|
||||
log.Infof("restoring index from stdin")
|
||||
log.Infof("restore: restoring database backup from stdin")
|
||||
f = os.Stdin
|
||||
} else if f, err = os.OpenFile(fileName, os.O_RDONLY, 0); err != nil {
|
||||
return fmt.Errorf("failed to open %s: %s", clean.Log(fileName), err)
|
||||
} else {
|
||||
log.Infof("restoring index from %s", clean.Log(fileName))
|
||||
log.Infof("restore: restoring database backup from %s", clean.Log(filepath.Base(fileName)))
|
||||
defer f.Close()
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ func RestoreIndex(backupPath, fileName string, fromStdIn, force bool) (err error
|
||||
|
||||
// Run restore command.
|
||||
if cmdErr := cmd.Run(); cmdErr != nil {
|
||||
log.Errorf("failed to restore index")
|
||||
log.Errorf("restore: failed to restore database backup")
|
||||
|
||||
if errStr := strings.TrimSpace(stderr.String()); errStr != "" {
|
||||
return errors.New(errStr)
|
||||
@@ -27,13 +27,13 @@ func NewBackup(conf *config.Config) *Backup {
|
||||
|
||||
// StartScheduled starts a scheduled run of the backup worker based on the current configuration.
|
||||
func (w *Backup) StartScheduled() {
|
||||
if err := w.Start(w.conf.BackupIndex(), w.conf.BackupAlbums(), true, w.conf.BackupRetain()); err != nil {
|
||||
if err := w.Start(w.conf.BackupDatabase(), w.conf.BackupAlbums(), true, w.conf.BackupRetain()); err != nil {
|
||||
log.Errorf("scheduler: %s (backup)", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Start creates index and album backups based on the current configuration.
|
||||
func (w *Backup) Start(index, albums bool, force bool, retain int) (err error) {
|
||||
func (w *Backup) Start(database, albums bool, force bool, retain int) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("backup: %s (worker panic)\nstack: %s", r, debug.Stack())
|
||||
@@ -42,7 +42,7 @@ func (w *Backup) Start(index, albums bool, force bool, retain int) (err error) {
|
||||
}()
|
||||
|
||||
// Return if no backups should be created.
|
||||
if !index && !albums {
|
||||
if !database && !albums {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -56,12 +56,12 @@ func (w *Backup) Start(index, albums bool, force bool, retain int) (err error) {
|
||||
// Start creating backups.
|
||||
start := time.Now()
|
||||
|
||||
// Create index database backup.
|
||||
if index {
|
||||
backupPath := w.conf.BackupIndexPath()
|
||||
// Create database backup.
|
||||
if database {
|
||||
databasePath := w.conf.BackupDatabasePath()
|
||||
|
||||
if err = photoprism.BackupIndex(backupPath, "", false, force, retain); err != nil {
|
||||
log.Errorf("backup: %s (index)", err)
|
||||
if err = photoprism.BackupDatabase(databasePath, "", false, force, retain); err != nil {
|
||||
log.Errorf("backup: %s (database)", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,23 +69,23 @@ func (w *Backup) Start(index, albums bool, force bool, retain int) (err error) {
|
||||
return errors.New("canceled")
|
||||
}
|
||||
|
||||
// Create album YAML file backup.
|
||||
// Create albums backup.
|
||||
if albums {
|
||||
albumsBackupPath := w.conf.BackupAlbumsPath()
|
||||
albumsPath := w.conf.BackupAlbumsPath()
|
||||
|
||||
if count, backupErr := photoprism.BackupAlbums(albumsBackupPath, false); backupErr != nil {
|
||||
log.Errorf("backup: %s (album)", backupErr.Error())
|
||||
if count, backupErr := photoprism.BackupAlbums(albumsPath, false); backupErr != nil {
|
||||
log.Errorf("backup: %s (albums)", backupErr.Error())
|
||||
} else if count > 0 {
|
||||
log.Infof("exported %s", english.Plural(count, "album", "albums"))
|
||||
log.Infof("backup: saved %s", english.Plural(count, "album backup", "album backups"))
|
||||
}
|
||||
}
|
||||
|
||||
// Update time when worker was last executed.
|
||||
// Remember time when worker was executed.
|
||||
w.lastRun = entity.TimeStamp()
|
||||
|
||||
elapsed := time.Since(start)
|
||||
|
||||
// Show success message.
|
||||
// Log success message.
|
||||
log.Infof("backup: completed in %s", elapsed)
|
||||
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user