package cmdimport ("context""fmt""os""strings""time""github.com/navidrome/navidrome/conf""github.com/navidrome/navidrome/db""github.com/navidrome/navidrome/log""github.com/spf13/cobra")var (backupCount intbackupDir stringforce boolrestorePath string)func init() {rootCmd.AddCommand(backupRoot)backupCmd.Flags().StringVarP(&backupDir, "backup-dir", "d", "", "directory to manually make backup")backupRoot.AddCommand(backupCmd)pruneCmd.Flags().StringVarP(&backupDir, "backup-dir", "d", "", "directory holding Navidrome backups")pruneCmd.Flags().IntVarP(&backupCount, "keep-count", "k", -1, "specify the number of backups to keep. 0 remove ALL backups, and negative values mean to use the default from configuration")pruneCmd.Flags().BoolVarP(&force, "force", "f", false, "bypass warning when backup count is zero")backupRoot.AddCommand(pruneCmd)restoreCommand.Flags().StringVarP(&restorePath, "backup-file", "b", "", "path of backup database to restore")restoreCommand.Flags().BoolVarP(&force, "force", "f", false, "bypass restore warning")_ = restoreCommand.MarkFlagRequired("backup-file")backupRoot.AddCommand(restoreCommand)}var (backupRoot = &cobra.Command{Use: "backup",Aliases: []string{"bkp"},Short: "Create, restore and prune database backups",Long: "Create, restore and prune database backups",}backupCmd = &cobra.Command{Use: "create",Short: "Create a backup database",Long: "Manually backup Navidrome database. This will ignore BackupCount",Run: func(cmd *cobra.Command, _ []string) {runBackup(cmd.Context())},}pruneCmd = &cobra.Command{Use: "prune",Short: "Prune database backups",Long: "Manually prune database backups according to backup rules",Run: func(cmd *cobra.Command, _ []string) {runPrune(cmd.Context())},}restoreCommand = &cobra.Command{Use: "restore",Short: "Restore Navidrome database",Long: "Restore Navidrome database from a backup. This must be done offline",Run: func(cmd *cobra.Command, _ []string) {runRestore(cmd.Context())},})func runBackup(ctx context.Context) {if backupDir != "" {conf.Server.Backup.Path = conf.NewDir(backupDir)}idx := strings.LastIndex(conf.Server.DbPath, "?")var path stringif idx == -1 {path = conf.Server.DbPath} else {path = conf.Server.DbPath[:idx]}if _, err := os.Stat(path); os.IsNotExist(err) {log.Fatal("No existing database", "path", path)return}start := time.Now()path, err := db.Backup(ctx)if err != nil {log.Fatal("Error backing up database", "backup path", conf.Server.BasePath, err)}elapsed := time.Since(start)log.Info("Backup complete", "elapsed", elapsed, "path", path)}func runPrune(ctx context.Context) {if backupDir != "" {conf.Server.Backup.Path = conf.NewDir(backupDir)}if backupCount != -1 {conf.Server.Backup.Count = backupCount}if conf.Server.Backup.Count == 0 && !force {fmt.Println("Warning: pruning ALL backups")fmt.Printf("Please enter YES (all caps) to continue: ")var input string_, err := fmt.Scanln(&input)if input != "YES" || err != nil {log.Warn("Prune cancelled")return}}idx := strings.LastIndex(conf.Server.DbPath, "?")var path stringif idx == -1 {path = conf.Server.DbPath} else {path = conf.Server.DbPath[:idx]}if _, err := os.Stat(path); os.IsNotExist(err) {log.Fatal("No existing database", "path", path)return}start := time.Now()count, err := db.Prune(ctx)if err != nil {log.Fatal("Error pruning up database", "backup path", conf.Server.BasePath, err)}elapsed := time.Since(start)log.Info("Prune complete", "elapsed", elapsed, "successfully pruned", count)}func runRestore(ctx context.Context) {idx := strings.LastIndex(conf.Server.DbPath, "?")var path stringif idx == -1 {path = conf.Server.DbPath} else {path = conf.Server.DbPath[:idx]}if _, err := os.Stat(path); os.IsNotExist(err) {log.Fatal("No existing database", "path", path)return}if !force {fmt.Println("Warning: restoring the Navidrome database should only be done offline, especially if your backup is very old.")fmt.Printf("Please enter YES (all caps) to continue: ")var input string_, err := fmt.Scanln(&input)if input != "YES" || err != nil {log.Warn("Restore cancelled")return}}start := time.Now()err := db.Restore(ctx, restorePath)if err != nil {log.Fatal("Error restoring database", "backup path", conf.Server.BasePath, err)}elapsed := time.Since(start)log.Info("Restore complete", "elapsed", elapsed)}