From 3183bf5267ad264932bc9fcc89dbe2fc7e35ac37 Mon Sep 17 00:00:00 2001 From: Vassiliy Yegorov Date: Tue, 1 Sep 2026 12:09:19 +0700 Subject: [PATCH] Add KEEP_LAST retention policy for backups Add KEEP_LAST environment variable to always preserve the N most recent backups regardless of age, preventing data loss when backups haven't been created for extended periods. Defaults to 10 copies. --- .env.example | 1 + README.md | 14 ++++++++++++++ VERSION | 2 +- docker-compose.yml | 1 + scripts/deleteold.sh | 32 ++++++++++++++++++++++++++++---- 5 files changed, 45 insertions(+), 5 deletions(-) mode change 100644 => 100755 scripts/deleteold.sh diff --git a/.env.example b/.env.example index 6bf9e6a..85ff94c 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,7 @@ DB_FILE=/data/db.sqlite CRONTAB="00 06 * * *" DELETE_AFTER=10 +KEEP_LAST=10 TZ=Asia/Novosibirsk MINIO_PATH=myminio://sqlite/master/ MINIO_ACCOUNT_ID=account diff --git a/README.md b/README.md index abfa8f9..18f0ac2 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ services: - DB_FILE - CRONTAB - DELETE_AFTER + - KEEP_LAST - TZ - MINIO_PATH - MINIO_ACCOUNT_ID @@ -28,6 +29,7 @@ services: DB_FILE=/data/db.sqlite CRONTAB="00 06 * * *" DELETE_AFTER=10 +KEEP_LAST=10 TZ=Asia/Novosibirsk MINIO_PATH=myminio://sqlite/master/ MINIO_ACCOUNT_ID=account @@ -43,6 +45,18 @@ HEALTHCHECK_UUID=00000000-0000-0000-0000-000000000000 1. myminio - системно! не менять 2. проверьте настройки локации, не должно быть пусто 3. DELETE_AFTER - в днях +4. KEEP_LAST - минимальное число копий, которые сохраняются всегда + +## Ротация + +Удаление старых бекапов работает по двум условиям одновременно: + +1. бекап старше `DELETE_AFTER` дней +2. и при этом не входит в `KEEP_LAST` самых свежих копий + +То есть последние `KEEP_LAST` бекапов не удаляются никогда, даже если они старше `DELETE_AFTER` дней. Это защищает от потери всех копий, если контейнер долго стоял и новые бекапы не создавались. + +`KEEP_LAST` по умолчанию `10`, если переменная не задана. ## Healthchecks diff --git a/VERSION b/VERSION index 867e524..cb174d5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.0 \ No newline at end of file +1.2.1 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index a0960bc..9d8e058 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,6 +12,7 @@ services: - DB_FILE - CRONTAB - DELETE_AFTER + - KEEP_LAST - TZ - MINIO_PATH - MINIO_ACCOUNT_ID diff --git a/scripts/deleteold.sh b/scripts/deleteold.sh old mode 100644 new mode 100755 index b30664a..2d4957d --- a/scripts/deleteold.sh +++ b/scripts/deleteold.sh @@ -2,14 +2,38 @@ set -u -# Seconds since epoch for current time +# Retention: delete backups older than DELETE_AFTER days, but always keep +# at least KEEP_LAST most recent copies regardless of their age. DATE_NOW=$(date +%s) DEST="${MINIO_PATH%/}/" +KEEP_LAST="${KEEP_LAST:-10}" -/scripts/minio_uploader.sh list "$DEST" | grep "sqlite_" | while read -r LINE +case "$KEEP_LAST" in + ''|*[!0-9]*) echo "WARN: KEEP_LAST is not a number ('$KEEP_LAST'), falling back to 10"; KEEP_LAST=10 ;; +esac + +LIST=$(mktemp) +trap 'rm -f "$LIST"' EXIT + +# `rclone ls` prints " ". Filenames are sqlite_.tar.gz, +# so a reverse lexicographic sort is a reverse chronological sort. +/scripts/minio_uploader.sh list "$DEST" \ + | awk '{ print $2 }' \ + | grep "^sqlite_" \ + | sort -r > "$LIST" + +TOTAL=$(wc -l < "$LIST" | tr -d ' ') +echo "Found $TOTAL backups, keeping at least $KEEP_LAST most recent, max age $DELETE_AFTER days" + +if [ "$TOTAL" -le "$KEEP_LAST" ]; then + echo "Nothing to rotate: $TOTAL backups <= KEEP_LAST=$KEEP_LAST" + exit 0 +fi + +# Only the tail beyond the protected window is a candidate for deletion. +tail -n "+$((KEEP_LAST + 1))" "$LIST" | while read -r BACKUP_FILENAME do - # `rclone ls` prints " " - BACKUP_FILENAME=$(echo "$LINE" | awk '{ print $2 }') + [ -n "$BACKUP_FILENAME" ] || continue BACKUP_DATE=$(echo "$BACKUP_FILENAME" | awk 'BEGIN { FS = "[_-]" } ; { printf "%s-%s-%s",$2,$3,$4 }')