2020-12-11 12:46:28 +01:00
package commands
import (
"bytes"
"context"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"time"
2021-10-07 13:00:32 +02:00
"github.com/dustin/go-humanize/english"
2021-09-22 19:33:41 +02:00
"github.com/urfave/cli"
2020-12-17 18:24:55 +01:00
2021-09-22 19:33:41 +02:00
"github.com/photoprism/photoprism/internal/config"
2020-12-17 18:24:55 +01:00
"github.com/photoprism/photoprism/internal/photoprism"
2021-09-22 19:33:41 +02:00
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/fs"
2021-12-14 20:01:39 +01:00
"github.com/photoprism/photoprism/pkg/sanitize"
2020-12-11 12:46:28 +01:00
)
// BackupCommand configures the backup cli command.
var BackupCommand = cli . Command {
2021-04-19 13:48:46 +02:00
Name : "backup" ,
2021-10-07 13:00:32 +02:00
Usage : "Creates index database dumps and optional YAML album backups" ,
2021-10-07 13:27:49 +02:00
UsageText : ` A custom database SQL dump FILENAME may be passed as first argument. Use - for stdout. The backup paths will be detected automatically if not provided. ` ,
2021-04-19 13:48:46 +02:00
Flags : backupFlags ,
Action : backupAction ,
2020-12-11 12:46:28 +01:00
}
var backupFlags = [ ] cli . Flag {
cli . BoolFlag {
Name : "force, f" ,
2021-10-07 13:00:32 +02:00
Usage : "replace existing backup files" ,
2020-12-11 12:46:28 +01:00
} ,
2020-12-17 18:24:55 +01:00
cli . BoolFlag {
Name : "albums, a" ,
2021-10-07 13:17:16 +02:00
Usage : "create YAML album backups" ,
2020-12-17 18:24:55 +01:00
} ,
2021-04-19 13:48:46 +02:00
cli . StringFlag {
Name : "albums-path" ,
2021-10-07 13:17:16 +02:00
Usage : "custom albums backup `PATH`" ,
2021-04-19 13:48:46 +02:00
} ,
2020-12-17 18:24:55 +01:00
cli . BoolFlag {
Name : "index, i" ,
2021-10-07 13:00:32 +02:00
Usage : "create index database SQL dump" ,
2021-04-19 13:48:46 +02:00
} ,
cli . StringFlag {
Name : "index-path" ,
2021-10-07 13:17:16 +02:00
Usage : "custom database backup `PATH`" ,
2020-12-17 18:24:55 +01:00
} ,
2020-12-11 12:46:28 +01:00
}
2020-12-11 13:52:34 +01:00
// backupAction creates a database backup.
2020-12-11 12:46:28 +01:00
func backupAction ( ctx * cli . Context ) error {
2021-04-19 13:48:46 +02:00
// Use command argument as backup file name.
indexFileName := ctx . Args ( ) . First ( )
indexPath := ctx . String ( "index-path" )
backupIndex := ctx . Bool ( "index" ) || indexFileName != "" || indexPath != ""
albumsPath := ctx . String ( "albums-path" )
backupAlbums := ctx . Bool ( "albums" ) || albumsPath != ""
if ! backupIndex && ! backupAlbums {
fmt . Printf ( "OPTIONS:\n" )
2020-12-17 18:24:55 +01:00
for _ , flag := range backupFlags {
2021-04-19 13:48:46 +02:00
fmt . Printf ( " %s\n" , flag . String ( ) )
2020-12-17 18:24:55 +01:00
}
return nil
}
2020-12-11 12:46:28 +01:00
start := time . Now ( )
conf := config . NewConfig ( ctx )
_ , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
if err := conf . Init ( ) ; err != nil {
return err
}
2021-04-19 13:48:46 +02:00
if backupIndex {
2020-12-17 18:24:55 +01:00
// If empty, use default backup file name.
2021-04-19 13:48:46 +02:00
if indexFileName == "" {
if ! fs . PathWritable ( indexPath ) {
if indexPath != "" {
log . Warnf ( "custom index backup path not writable, using default" )
}
indexPath = filepath . Join ( conf . BackupPath ( ) , conf . DatabaseDriver ( ) )
}
2020-12-17 18:24:55 +01:00
backupFile := time . Now ( ) . UTC ( ) . Format ( "2006-01-02" ) + ".sql"
2021-04-19 13:48:46 +02:00
indexFileName = filepath . Join ( indexPath , backupFile )
2020-12-17 18:24:55 +01:00
}
2020-12-11 12:46:28 +01:00
2021-04-19 13:48:46 +02:00
if indexFileName != "-" {
if _ , err := os . Stat ( indexFileName ) ; err == nil && ! ctx . Bool ( "force" ) {
return fmt . Errorf ( "backup file already exists: %s" , indexFileName )
} else if err == nil {
log . Warnf ( "replacing existing backup file" )
}
2020-12-11 12:46:28 +01:00
2021-04-19 13:48:46 +02:00
// Create backup directory if not exists.
if dir := filepath . Dir ( indexFileName ) ; dir != "." {
if err := os . MkdirAll ( dir , os . ModePerm ) ; err != nil {
return err
}
2020-12-17 18:24:55 +01:00
}
2020-12-11 12:46:28 +01:00
2021-12-14 20:01:39 +01:00
log . Infof ( "backing up database to %s" , sanitize . Log ( indexFileName ) )
2021-04-19 13:48:46 +02:00
}
2020-12-17 18:24:55 +01:00
var cmd * exec . Cmd
switch conf . DatabaseDriver ( ) {
case config . MySQL , config . MariaDB :
cmd = exec . Command (
conf . MysqldumpBin ( ) ,
2021-02-16 08:59:57 +01:00
"--protocol" , "tcp" ,
2020-12-17 18:24:55 +01:00
"-h" , conf . DatabaseHost ( ) ,
"-P" , conf . DatabasePortString ( ) ,
"-u" , conf . DatabaseUser ( ) ,
"-p" + conf . DatabasePassword ( ) ,
conf . DatabaseName ( ) ,
)
2021-12-09 07:47:23 +01:00
case config . SQLite3 :
2020-12-17 18:24:55 +01:00
cmd = exec . Command (
conf . SqliteBin ( ) ,
conf . DatabaseDsn ( ) ,
".dump" ,
)
default :
return fmt . Errorf ( "unsupported database type: %s" , conf . DatabaseDriver ( ) )
}
2020-12-11 12:46:28 +01:00
2020-12-17 18:24:55 +01:00
// Fetch command output.
var out bytes . Buffer
var stderr bytes . Buffer
cmd . Stdout = & out
cmd . Stderr = & stderr
// Run backup command.
if err := cmd . Run ( ) ; err != nil {
if stderr . String ( ) != "" {
return errors . New ( stderr . String ( ) )
}
}
2020-12-11 12:46:28 +01:00
2021-04-19 13:48:46 +02:00
if indexFileName == "-" {
// Return output via stdout.
fmt . Println ( out . String ( ) )
} else {
// Write output to file.
2021-10-06 07:10:50 +02:00
if err := os . WriteFile ( indexFileName , [ ] byte ( out . String ( ) ) , os . ModePerm ) ; err != nil {
2021-04-19 13:48:46 +02:00
return err
}
2020-12-11 12:46:28 +01:00
}
}
2021-04-19 13:48:46 +02:00
if backupAlbums {
2020-12-17 18:24:55 +01:00
service . SetConfig ( conf )
conf . InitDb ( )
2021-04-19 13:48:46 +02:00
if ! fs . PathWritable ( albumsPath ) {
if albumsPath != "" {
2021-10-07 13:17:16 +02:00
log . Warnf ( "albums backup path not writable, using default" )
2021-04-19 13:48:46 +02:00
}
albumsPath = conf . AlbumsPath ( )
}
2021-12-14 20:01:39 +01:00
log . Infof ( "backing up albums to %s" , sanitize . Log ( albumsPath ) )
2021-04-20 08:40:39 +02:00
2021-04-19 13:48:46 +02:00
if count , err := photoprism . BackupAlbums ( albumsPath , true ) ; err != nil {
2020-12-17 18:24:55 +01:00
return err
} else {
2021-10-07 13:17:16 +02:00
log . Infof ( "created %s" , english . Plural ( count , "YAML album backup" , "YAML album backups" ) )
2020-12-17 18:24:55 +01:00
}
2020-12-11 12:46:28 +01:00
}
elapsed := time . Since ( start )
2021-10-02 15:34:41 +02:00
log . Infof ( "backup completed in %s" , elapsed )
2020-12-11 12:46:28 +01:00
conf . Shutdown ( )
return nil
}