Skip to content

Commit

Permalink
support for backups to multiple repositories
Browse files Browse the repository at this point in the history
  • Loading branch information
fgma authored May 5, 2019
1 parent acb2aaf commit 75c1225
Show file tree
Hide file tree
Showing 17 changed files with 339 additions and 155 deletions.
25 changes: 17 additions & 8 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ System requirements

Rester is implemented in golang and should run on all platforms restic supports. It only needs a recent version of restic.

Right now it is only tested running on linux.
Right now it is only tested running on linux and basic functionality on windows.

Install
-------
Expand Down Expand Up @@ -102,7 +102,7 @@ In addition to restic's forget command this will also run restic's prune command
rester check-age
to check your backups ages. If you need to restore data you can use regular restic commands to do so or just mount a repository:
to check your backups ages. If everything is ok, restic will just exit with a exit code of 0 and no output. If you need to restore data you can use regular restic commands to do so or just mount a repository:

.. code-block:: shell
Expand Down Expand Up @@ -150,6 +150,16 @@ An overview of all available commands:
Use "./rester [command] --help" for more information about a command.
$
The commands ``backup`` and ``check-age`` support an advanced syntax for selecting backups to use:

.. code-block:: shell
rester backup my-configured-backup/one-of-its-repos another-backup
This will run the backup ``my-configured-backup`` to repository ``one-of-its-repos`` and backup ``another-backup`` to all its configured repositories.


.. _configuration:

Configuration
Expand All @@ -173,7 +183,7 @@ Repositories
To actually backup data at least one repository has to be configured. Rester supports all repository formats restic supports.

name
A unique name to refer to this repository.
A unique name to refer to this repository. Invalid characters: " ", "/".

url
The URL of the repository as passed to restic. For details on the format have a look at into restic's manual.
Expand Down Expand Up @@ -225,7 +235,6 @@ handler
If the commands start with a ``~`` sign it is expanded to the user's home directory. Additionally some special variables inside the commands are replaced with the appropriate values to automatically customize commands:

- {{.BackupName}}
- {{.BackupRepository}}
- {{.RepositoryName}}
- {{.RepositoryURL}}

Expand All @@ -241,7 +250,7 @@ Backups
=======

name
A unique name to refer to this backup.
A unique name to refer to this backup. Invalid characters: " ", "/".

repository
The name of the repository to backup to as specified in the repositories section of the configuration.
Expand Down Expand Up @@ -368,8 +377,8 @@ Example configuration
],
"backups": [
{
"name": "/home/user",
"repository": "minio-backup",
"name": "home",
"repositories": [ "minio-backup" ],
"data": [
"/home/user/"
],
Expand All @@ -382,7 +391,7 @@ Example configuration
},
{
"name": "crontab",
"repository": "minio-backup",
"repositories": [ "minio-backup" ],
"data_stdin_command": "crontab -l",
"stdin_filename": "crontab.txt",
"one_file_system": true,
Expand Down
3 changes: 1 addition & 2 deletions TODO
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
- allow one backup to multiple repositories
- make consistent concerning commands like age, backup, check
- add verbose flag to see what's going on
- implement template based backups
- check exit codes for consistency
40 changes: 22 additions & 18 deletions cmd/age.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var ageCmd = &cobra.Command{
fmt.Fprintln(w, "----\t----\t----------\t---")

now := time.Now()
exitCode := 0

for _, backup := range config.Backups {
data := ""
Expand All @@ -36,31 +37,34 @@ var ageCmd = &cobra.Command{
data = backup.DataStdinCommand
}

repository := config.GetRepositoryByName(backup.Repository)
for _, repo := range backup.Repositories {
repository := config.GetRepositoryByName(repo)

if repository == nil {
fmt.Fprintf(os.Stderr, "Repository %s is not a configured repository\n", backup.Repository)
os.Exit(1)
}
if repository == nil {
fmt.Fprintf(os.Stderr, "Repository %s is not a configured repository\n", repo)
os.Exit(1)
}

lastBackupTimestamp, err := restic.GetLastBackupTimestamp(backup, *repository)
lastBackupTimestamp, err := restic.GetLastBackupTimestamp(backup, *repository)

if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get age for backup %s: %s\n", backup.Name, err)
os.Exit(1)
}
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get age for backup %s: %s\n", backup.Name, err)
exitCode = 1
}

age := "-"
if (lastBackupTimestamp != time.Time{}) {
age = now.Sub(lastBackupTimestamp).String()
}
age := "-"
if (lastBackupTimestamp != time.Time{}) {
age = now.Sub(lastBackupTimestamp).String()
}

fmt.Fprintf(
w, "%s\t%s\t%s\t%s\n",
backup.Name, data, backup.Repository, age,
)
fmt.Fprintf(
w, "%s\t%s\t%s\t%s\n",
backup.Name, data, repository.Name, age,
)
}
}

w.Flush()
os.Exit(exitCode)
},
}
37 changes: 5 additions & 32 deletions cmd/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,11 @@ var backupCmd = &cobra.Command{
Long: `Run backups specified on the commandline or all if no backup is specified`,
Args: cobra.ArbitraryArgs,
Run: func(cmd *cobra.Command, args []string) {

ensureBackupsExist(args)

if len(args) == 0 {
for _, backup := range config.Backups {
runBackup(backup.Name)
}
} else {
for _, backupName := range args {
runBackup(backupName)
}
}

runForBackupConfigurations(args, runBackup)
},
}

func runBackup(backupName string) {
func runBackup(backupName string, repositoryName string) (int, error) {

backup := config.GetBackupByName(backupName)

Expand All @@ -42,31 +30,16 @@ func runBackup(backupName string) {
os.Exit(1)
}

repository := config.GetRepositoryByName(backup.Repository)
repository := config.GetRepositoryByName(repositoryName)

if repository == nil {
fmt.Fprintf(os.Stderr, "Repository %s is not a configured repository\n", backup.Repository)
fmt.Fprintf(os.Stderr, "Repository %s is not a configured repository\n", repositoryName)
os.Exit(1)
}

if err := restic.RunBackup(*backup, *repository); err != nil {
fmt.Fprintf(os.Stderr, "Backup %s failed to run: %s\n", backupName, err.Error())
}
}

func ensureBackupsExist(backups []string) {
for _, backupName := range backups {
isExistingBackup := false
for _, b := range config.Backups {
if b.Name == backupName {
isExistingBackup = true
break
}
}

if !isExistingBackup {
fmt.Fprintf(os.Stderr, "%s is not a configured backup\n", backupName)
os.Exit(1)
}
}
return 0, nil
}
4 changes: 2 additions & 2 deletions cmd/backups.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ var backupsCmd = &cobra.Command{

w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)

fmt.Fprintln(w, "name\tdata\trepository")
fmt.Fprintln(w, "name\tdata\trepositories")
fmt.Fprintln(w, "----\t----\t----------")

for _, backup := range config.Backups {
Expand All @@ -34,7 +34,7 @@ var backupsCmd = &cobra.Command{
}
fmt.Fprintf(
w, "%s\t%s\t%s\n",
backup.Name, data, backup.Repository,
backup.Name, data, strings.Join(backup.Repositories, ","),
)
}

Expand Down
42 changes: 7 additions & 35 deletions cmd/check_age.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,11 @@ var checkAgeCmd = &cobra.Command{
Long: `Check age of the given backups`,
Args: cobra.ArbitraryArgs,
Run: func(cmd *cobra.Command, args []string) {

ensureBackupsExist(args)

var error error = nil
var checkExitCode int = 0

if len(args) == 0 {
for _, backup := range config.Backups {
exitCode, err := runCheckAge(backup.Name)
if error == nil {
error = err
}
if exitCode > checkExitCode {
checkExitCode = exitCode
}
}
} else {
for _, backupName := range args {
exitCode, err := runCheckAge(backupName)
if error == nil {
error = err
}
if exitCode > checkExitCode {
checkExitCode = exitCode
}
}
}

os.Exit(checkExitCode)
runForBackupConfigurations(args, runCheckAge)
},
}

func runCheckAge(backupName string) (int, error) {
func runCheckAge(backupName string, repositoryName string) (int, error) {

backup := config.GetBackupByName(backupName)

Expand All @@ -58,24 +30,24 @@ func runCheckAge(backupName string) (int, error) {
os.Exit(1)
}

repository := config.GetRepositoryByName(backup.Repository)
repository := config.GetRepositoryByName(repositoryName)

if repository == nil {
fmt.Fprintf(os.Stderr, "Repository %s is not a configured repository\n", backup.Repository)
fmt.Fprintf(os.Stderr, "Repository %s is not a configured repository\n", repositoryName)
os.Exit(1)
}

limitWarn, limitError, err := restic.CheckAge(*backup, *repository)
exitCode := 0

if err != nil {
fmt.Fprintf(os.Stderr, "Error checking age for backup %s to repository %s\n", backup.Name, backup.Repository)
fmt.Fprintf(os.Stderr, "Error checking age for backup %s to repository %s\n", backup.Name, repository.Name)
exitCode = 1
} else if limitError {
fmt.Fprintf(os.Stderr, "Error limit reached for backup %s to repository %s\n", backup.Name, backup.Repository)
fmt.Fprintf(os.Stderr, "Error limit reached for backup %s to repository %s\n", backup.Name, repository.Name)
exitCode = 3
} else if limitWarn {
fmt.Fprintf(os.Stdout, "Warning limit reached for backup %s to repository %s\n", backup.Name, backup.Repository)
fmt.Fprintf(os.Stdout, "Warning limit reached for backup %s to repository %s\n", backup.Name, repository.Name)
exitCode = 2
}

Expand Down
10 changes: 5 additions & 5 deletions cmd/example_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ var exampleConfigCmd = &cobra.Command{
],
"backups": [
{
"name": "some data",
"repository": "backup-repo",
"name": "some_data",
"repositories": [ "backup-repo" ],
"data": [
"/home/testuser/pictures",
"/home/testuser/data"
Expand All @@ -51,7 +51,7 @@ var exampleConfigCmd = &cobra.Command{
},
{
"name": "crontab",
"repository": "backup-repo",
"repositories": [ "backup-repo" ],
"data_stdin_command": "crontab -l",
"stdin_filename": "crontab.txt",
"one_file_system": true,
Expand All @@ -68,8 +68,8 @@ var exampleConfigCmd = &cobra.Command{
_, err := internal.LoadFromReader(reader)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse example config: %s", err)
} else {
fmt.Println(exampleConfig)
}

fmt.Println(exampleConfig)
},
}
2 changes: 2 additions & 0 deletions cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ var initCmd = &cobra.Command{
Args: cobra.ArbitraryArgs,
Run: func(cmd *cobra.Command, args []string) {

ensureRepositoriesExist(args)

if len(args) == 0 {
for _, repository := range config.Repositories {
initRepository(repository.Name)
Expand Down
Loading

0 comments on commit 75c1225

Please sign in to comment.