diff --git a/README.md b/README.md index 4083a06..22caf78 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,11 @@ You can populate below template according to your requirements and use it as you # BACKUP_FILENAME="backup-%Y-%m-%dT%H-%M-%S.tar.gz" +# When storing local backups, a symlink to the latest backup can be created +# in case a value is given for this key. This has no effect on remote backups. + +# BACKUP_LATEST_SYMLINK="backup.latest.tar.gz" + ########### BACKUP STORAGE # The name of the remote bucket that should be used for storing backups. If @@ -437,6 +442,9 @@ services: # ... define other services using the `data` volume here backup: image: offen/docker-volume-backup:latest + environment: + BACKUP_FILENAME: backup-%Y-%m-%dT%H-%M-%S.tar.gz + BACKUP_LATEST_SYMLINK: backup-latest.tar.gz volumes: - data:/backup/my-app-backup:ro - /var/run/docker.sock:/var/run/docker.sock:ro diff --git a/cmd/backup/main.go b/cmd/backup/main.go index 3309a72..f294094 100644 --- a/cmd/backup/main.go +++ b/cmd/backup/main.go @@ -89,6 +89,7 @@ type script struct { type config struct { BackupSources string `split_words:"true" default:"/backup"` BackupFilename string `split_words:"true" default:"backup-%Y-%m-%dT%H-%M-%S.tar.gz"` + BackupLatestSymlink string `split_words:"true"` BackupArchive string `split_words:"true" default:"/archive"` BackupRetentionDays int32 `split_words:"true" default:"-1"` BackupPruningLeeway time.Duration `split_words:"true" default:"1m"` @@ -380,6 +381,16 @@ func (s *script) copyBackup() error { return fmt.Errorf("copyBackup: error copying file to local archive: %w", err) } s.logger.Infof("Stored copy of backup `%s` in local archive `%s`.", s.file, s.c.BackupArchive) + if s.c.BackupLatestSymlink != "" { + symlink := path.Join(s.c.BackupArchive, s.c.BackupLatestSymlink) + if _, err := os.Lstat(symlink); err == nil { + os.Remove(symlink) + } + if err := os.Symlink(name, symlink); err != nil { + return fmt.Errorf("copyBackup: error creating latest symlink: %w", err) + } + s.logger.Infof("Created/Updated symlink `%s` for latest backup.", s.c.BackupLatestSymlink) + } } return nil } @@ -497,7 +508,7 @@ func (s *script) pruneOldBackups() error { ) } - if fi.ModTime().Before(deadline) { + if fi.Mode() != os.ModeSymlink && fi.ModTime().Before(deadline) { matches = append(matches, candidate) } } diff --git a/test/compose/docker-compose.yml b/test/compose/docker-compose.yml index a61d106..b9ade86 100644 --- a/test/compose/docker-compose.yml +++ b/test/compose/docker-compose.yml @@ -24,6 +24,7 @@ services: AWS_ENDPOINT_PROTO: http AWS_S3_BUCKET_NAME: backup BACKUP_FILENAME: test.tar.gz + BACKUP_LATEST_SYMLINK: test.latest.tar.gz.gpg BACKUP_CRON_EXPRESSION: 0 0 5 31 2 ? BACKUP_RETENTION_DAYS: ${BACKUP_RETENTION_DAYS:-7} BACKUP_PRUNING_LEEWAY: 5s diff --git a/test/compose/run.sh b/test/compose/run.sh index a00142f..22fe8c7 100755 --- a/test/compose/run.sh +++ b/test/compose/run.sh @@ -18,6 +18,7 @@ docker run --rm -it \ echo "[TEST:PASS] Found relevant files in untared remote backup." +test -L ./local/test.latest.tar.gz.gpg echo 1234secret | gpg -d --yes --passphrase-fd 0 ./local/test.tar.gz.gpg > ./local/decrypted.tar.gz tar -xf ./local/decrypted.tar.gz -C /tmp && test -f /tmp/backup/app_data/offen.db rm ./local/decrypted.tar.gz