From 823fa82d0092ac1baeadbe92186135903217aad6 Mon Sep 17 00:00:00 2001 From: liuxhit <23427350+liuxhit@users.noreply.github.com> Date: Sun, 25 Jun 2023 12:32:32 +0800 Subject: [PATCH] docker: support retaining at least a few files in backup retention --- docker/runtime/backup-init.sh | 64 ++++++++++++++++++++++++++++++-- docker/runtime/backup-rotator.sh | 8 ++-- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/docker/runtime/backup-init.sh b/docker/runtime/backup-init.sh index f4e21a1b5..7fb99fb87 100644 --- a/docker/runtime/backup-init.sh +++ b/docker/runtime/backup-init.sh @@ -9,8 +9,9 @@ mkdir -p "/etc/crontabs" chown git:git /backup chmod 2770 /backup -# [string] BACKUP_INTERVAL Period expression -# [string] BACKUP_RETENTION Period expression +# [string] BACKUP_INTERVAL Period expression +# [string] BACKUP_RETENTION Period expression(Deprecated for forward compatibility) +# [int] BACKUP_RETENTION_NUM Number of surviving backup items if [ -z "${BACKUP_INTERVAL}" ]; then echo "Backup disabled: BACKUP_INTERVAL has not been found" 1>&2 exit 1 @@ -21,6 +22,12 @@ if [ -z "${BACKUP_RETENTION}" ]; then BACKUP_RETENTION='7d' fi +if [ -z "${BACKUP_RETENTION_NUM}" ]; then + echo "Backup retention number is not defined, will calculate its value later" 1>&2 +else + echo "Backup retention number is given: ${BACKUP_RETENTION_NUM}, will check its value later" 1>&2 +fi + # Parse BACKUP_INTERVAL environment variable and generate appropriate cron expression. Backup cron task will be run as scheduled. # Expected format: nu (n - number, u - unit) (eg. 3d means 3 days) # Supported units: h - hours, d - days, M - months @@ -104,6 +111,43 @@ parse_generate_retention_expression() { echo "${FIND_TIME_EXPR} +${TIME_INTERVAL:-7}" } +# util function: convert expression to minutes, ignore input check +# Expected format: nu (n - number, u - unit) (eg. 3d means 3 days) +# Supported units: m - minutes, h - hours, d - days, M - months +parse_expression_minutes() { + TIME_EXPRESSION="${1:-7d}" + # shellcheck disable=SC2001 + TIME_INTERVAL=$(echo "${TIME_EXPRESSION}" | sed -e 's/[mhdM]$//') + # shellcheck disable=SC2001 + TIME_UNIT=$(echo "${TIME_EXPRESSION}" | sed -e 's/^[0-9]\+//') + + MINUTE_MULTIPLE="1" + if [ "${TIME_UNIT}" = "m" ]; then + MINUTE_MULTIPLE="1" + elif [ "${TIME_UNIT}" = "h" ]; then + MINUTE_MULTIPLE="60" + elif [ "${TIME_UNIT}" = "d" ]; then + MINUTE_MULTIPLE="1440" + elif [ "${TIME_UNIT}" = "M" ]; then + MINUTE_MULTIPLE="43200" + else + echo "Parse error: expression is invalid: ${TIME_EXPRESSION}" 1>&2 + exit 1 + fi + + echo "$(( TIME_INTERVAL * MINUTE_MULTIPLE ))" + +} + +# Giving BACKUP_INTERVAL and BACKUP_RETENTION, calculate the value of BACKUP_RETENTION_NUM we expected. +# Using BACKUP_RETENTION_NUM directly if BACKUP_RETENTION_NUM is given, ignoring other inputs. +calc_surviving_backups_number() { + # shellcheck disable=SC2086 # Since the input of these two variables has been verified as valid above + CALC_BACKUP_RETENTION_NUM="$(( $(parse_expression_minutes ${BACKUP_RETENTION}) / $(parse_expression_minutes ${BACKUP_INTERVAL}) ))" + + echo "${BACKUP_RETENTION_NUM:-${CALC_BACKUP_RETENTION_NUM}}" +} + add_backup_cronjob() { CRONTAB_USER="${1:-git}" CRONTAB_FILE="/etc/crontabs/${CRONTAB_USER}" @@ -129,6 +173,9 @@ add_backup_cronjob() { CRONTAB_USER=$(awk -v val="${PUID}" -F ":" '$3==val{print $1}' /etc/passwd) +# ================================================================================ +# Keep these lines for the purpose of checking input parameters +# ================================================================================ # Up to this point, it was desirable that interpreter handles the command errors and halts execution upon any error. # From now, we handle the errors our self. set +e @@ -138,7 +185,18 @@ if [ -z "${RETENTION_EXPRESSION}" ]; then echo "Couldn't generate backup retention expression. Aborting backup setup" 1>&2 exit 1 fi +# ================================================================================ + +RETENTION_NUM="$(calc_surviving_backups_number)" + +if echo "${RETENTION_NUM}" | grep -qE '^[0-9]+$'; then + echo "use RETENTION_NUM: ${RETENTION_NUM}" +else + echo "RETENTION_NUM(${RETENTION_NUM}) is not a valid number, please check the input:" 1>&2 + echo "input vars: BACKUP_INTERVAL=${BACKUP_INTERVAL} BACKUP_RETENTION=${BACKUP_RETENTION} BACKUP_RETENTION_NUM=${BACKUP_RETENTION_NUM}" 1>&2 + exit 1 +fi # Backup rotator cron will run every 5 minutes -add_backup_cronjob "${CRONTAB_USER}" "*/5 * * * *" "/app/gogs/docker/runtime/backup-rotator.sh" "'${BACKUP_PATH}' '${RETENTION_EXPRESSION}'" +add_backup_cronjob "${CRONTAB_USER}" "*/5 * * * *" "/app/gogs/docker/runtime/backup-rotator.sh" "'${BACKUP_PATH}' '${RETENTION_NUM}'" add_backup_cronjob "${CRONTAB_USER}" "$(parse_generate_cron_expression)" "/app/gogs/docker/runtime/backup-job.sh" "'${BACKUP_PATH}'" diff --git a/docker/runtime/backup-rotator.sh b/docker/runtime/backup-rotator.sh index 3b6d10abf..b9dad4830 100644 --- a/docker/runtime/backup-rotator.sh +++ b/docker/runtime/backup-rotator.sh @@ -1,10 +1,10 @@ #!/usr/bin/env sh # This is very simple, yet effective backup rotation script. -# Using find command, all files that are older than BACKUP_RETENTION_DAYS are accumulated and deleted using rm. +# All files that are older than the latest RETENTION_NUM backup files are accumulated and deleted using rm. main() { BACKUP_PATH="${1:-}" - FIND_EXPRESSION="${2:-mtime +7}" + RETENTION_NUM="${2:-1}" if [ -z "${BACKUP_PATH}" ]; then echo "Error: Required argument missing BACKUP_PATH" 1>&2 @@ -21,8 +21,8 @@ main() { exit 1 fi - # shellcheck disable=SC2086 - find "${BACKUP_PATH}/" -type f -name "gogs-backup-*.zip" -${FIND_EXPRESSION} -print -exec rm "{}" + + # shellcheck disable=SC2012 # File name is expected + ls -1t "${BACKUP_PATH}/"gogs-backup-*.zip | tail -n +"$(( RETENTION_NUM + 1 ))" | xargs -n10 --no-run-if-empty rm } main "$@"