Skip to content

Commit

Permalink
Add custom retention
Browse files Browse the repository at this point in the history
  • Loading branch information
thekashifmalik committed Jul 7, 2024
1 parent 8a25f42 commit 3d5a69f
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 9 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ rincr prune myserver:~/backups/mydata
> **Note**: When pruning data you have to provide the path to the actual backup destination which includes the target
> name `mydata`.
If you want to override the default retention rules, you can use the options:

```bash
rincr --prune --hourly=12 --daily=15 --monthly=6 --yearly=5 ~/mydata myserver:~/backups
```

### Restoring Files

To restore files from a backed-up repository, we can use:
Expand Down
24 changes: 24 additions & 0 deletions internal/args/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package args

import (
"fmt"
"strconv"
"strings"
)

Expand Down Expand Up @@ -34,3 +35,26 @@ func Parse(args []string) (*Parsed, error) {
func (p *Parsed) LeftShift() {
p.Params = p.Params[1:]
}

func (p *Parsed) GetOption(name string) (string, bool) {
prefix := fmt.Sprintf("--%v=", name)
for _, option := range p.Options {
value, ok := strings.CutPrefix(option, prefix)
if ok {
return value, true
}
}
return "", false
}

func (p *Parsed) GetOptionInt(name string) (int, bool) {
value, ok := p.GetOption(name)
if !ok {
return 0, false
}
converted, err := strconv.Atoi(value)
if err != nil {
return 0, false
}
return converted, true
}
27 changes: 26 additions & 1 deletion internal/backup/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ type Command struct {
Sources []string
Destination string
Prune bool
Hourly int
Daily int
Monthly int
Yearly int
}

func Parse(args *args.Parsed) (*Command, error) {
Expand All @@ -35,10 +39,31 @@ func ParseRoot(args *args.Parsed) (*Command, error) {
}
sources := args.Params[:numParams-1]
destination := args.Params[numParams-1]

hourly, ok := args.GetOptionInt("hourly")
if !ok {
hourly = 24
}
daily, ok := args.GetOptionInt("daily")
if !ok {
daily = 30
}
monthly, ok := args.GetOptionInt("monthly")
if !ok {
monthly = 12
}
yearly, ok := args.GetOptionInt("yearly")
if !ok {
yearly = 10
}
return &Command{
Sources: sources,
Destination: destination,
Prune: slices.Contains(args.Options, "--prune"),
Hourly: hourly,
Daily: daily,
Monthly: monthly,
Yearly: yearly,
}, nil
}

Expand Down Expand Up @@ -72,7 +97,7 @@ func (a *Command) Run() error {
if a.Prune {
// TODO: Replace Destination with Repository.
repo := repository.NewRepository(destinationTarget.Path)
err := prune.Prune(repo, destinationTarget, currentTime)
err := prune.Prune(repo, destinationTarget, currentTime, a.Hourly, a.Daily, a.Monthly, a.Yearly)
if err != nil {
return err
}
Expand Down
26 changes: 25 additions & 1 deletion internal/prune/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,39 @@ import (

type Command struct {
DestinationTargets []string
Hourly int
Daily int
Monthly int
Yearly int
}

func Parse(args *args.Parsed) (*Command, error) {
args.LeftShift()
if len(args.Params) < 1 {
return nil, fmt.Errorf("No destination targets provided")
}
hourly, ok := args.GetOptionInt("hourly")
if !ok {
hourly = 24
}
daily, ok := args.GetOptionInt("daily")
if !ok {
daily = 30
}
monthly, ok := args.GetOptionInt("monthly")
if !ok {
monthly = 12
}
yearly, ok := args.GetOptionInt("yearly")
if !ok {
yearly = 10
}
return &Command{
DestinationTargets: args.Params,
Hourly: hourly,
Daily: daily,
Monthly: monthly,
Yearly: yearly,
}, nil
}

Expand All @@ -33,7 +57,7 @@ func (c *Command) Run() error {
continue
}
destinationTarget := internal.ParseDestination(destinationTarget)
err := Prune(repo, destinationTarget, currentTime)
err := Prune(repo, destinationTarget, currentTime, c.Hourly, c.Daily, c.Monthly, c.Yearly)
if err != nil {
return err
}
Expand Down
24 changes: 17 additions & 7 deletions internal/prune/prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,23 @@ import (
"github.com/thekashifmalik/rincr/internal/repository"
)

func Prune(repository *repository.Repository, destination *internal.Destination, currentTime time.Time) error {
func Prune(
repository *repository.Repository,
destination *internal.Destination,
currentTime time.Time,
hourly, daily, monthly, yearly int,
) error {
existingBackups, err := repository.GetBackupTimes()
if err != nil {
return err
}
// Go through time buckets, keeping only the oldest backup from each bucket.
fmt.Printf("> Pruning backups in: %v\n", destination.Path+internal.BACKUPS_DIR_PATH)
pruned, checkedTill := pruneStage(existingBackups, roundToHour(currentTime.Add(-time.Hour)), destination, 23, time.Hour)
pruned, checkedTill = pruneStage(pruned, roundToDay(checkedTill), destination, 30, 24*time.Hour)
pruned, checkedTill = pruneMonthly(pruned, roundToMonth(checkedTill), destination, 12)
pruned, checkedTill = pruneYearly(pruned, roundToYear(checkedTill), destination, 10)
pruned, checkedTill := pruneStage(existingBackups, roundToHour(currentTime.Add(-time.Hour)), destination, hourly-1, time.Hour)
pruned, checkedTill = pruneStage(pruned, roundToDay(checkedTill), destination, daily, 24*time.Hour)
pruned, checkedTill = pruneMonthly(pruned, roundToMonth(checkedTill), destination, monthly)
pruned, checkedTill = pruneYearly(pruned, roundToYear(checkedTill), destination, yearly)
deleteBackups(pruned, destination)
return nil
}

Expand Down Expand Up @@ -95,7 +101,12 @@ func pruneBucket(existingBackups []time.Time, bucketTime time.Time, destination
}
// Sort the backups and keep only the oldest one
slices.SortFunc(bucket, func(a, b time.Time) int { return a.Compare(b) })
for _, backupTime := range bucket[1:] {
deleteBackups(bucket[1:], destination)
return unseen
}

func deleteBackups(backups []time.Time, destination *internal.Destination) {
for _, backupTime := range backups {
fmt.Printf("> Pruning: %v\n", backupTime)
// TODO: Handle any errors here
if destination.RemoteHost == "" {
Expand All @@ -105,5 +116,4 @@ func pruneBucket(existingBackups []time.Time, bucketTime time.Time, destination
exec.Command("ssh", destination.RemoteHost, "rm", "-rf", remotePath).Run()
}
}
return unseen
}

0 comments on commit 3d5a69f

Please sign in to comment.