From 5465665c264280548d558552cc4043b61452cbcf Mon Sep 17 00:00:00 2001 From: Keith Solomon Date: Sun, 28 Dec 2025 11:13:48 -0600 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9E=20fix:=20Update=20backup=20script?= =?UTF-8?q?=20for=20configurable=20backup=20retention?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/files/gitea/forge-b2-backup.sh | 71 +++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/assets/files/gitea/forge-b2-backup.sh b/assets/files/gitea/forge-b2-backup.sh index 9ba2971..73acbf0 100644 --- a/assets/files/gitea/forge-b2-backup.sh +++ b/assets/files/gitea/forge-b2-backup.sh @@ -4,7 +4,11 @@ set -euo pipefail # Paths — adjust if your layout changes GITEA_DIR="/gitea/gitea" BACKUP_DIR="/gitea/backups" -CONTAINER="gitea" +CONTAINER="Gitea" + +# Retention +KEEP_COUNT=7 +B2_REMOTE="B2:SoloForge-backup" mkdir -p "$BACKUP_DIR" @@ -22,9 +26,68 @@ mv "$GITEA_DIR/$dump_file" "$BACKUP_DIR/$dump_file" echo "[backup] Dump created at $BACKUP_DIR/$dump_file" -# Optional: Upload to Backblaze B2 via rclone -# Make sure you configured a remote named 'B2' -rclone copy "$BACKUP_DIR/$dump_file" B2:soloforge-backups +# Upload to Backblaze B2 via rclone +rclone copy "$BACKUP_DIR/$dump_file" "$B2_REMOTE" echo "[backup] Uploaded $dump_file to Backblaze B2" + +# ----------------------- +# Cleanup: keep latest N +# ----------------------- + +echo "[cleanup] Keeping newest $KEEP_COUNT local backups in $BACKUP_DIR" + +mapfile -t localBackups < <(ls -1t "$BACKUP_DIR"/gitea-dump-*.zip 2>/dev/null || true) + +if (( ${#localBackups[@]} > KEEP_COUNT )); then + for oldPath in "${localBackups[@]:KEEP_COUNT}"; do + oldFile="$(basename "$oldPath")" + + # Extra safety: only delete files matching our exact dump pattern + if [[ "$oldFile" =~ ^gitea-dump-[0-9]{8}-[0-9]{6}\.zip$ ]]; then + echo "[cleanup] Deleting local: $oldPath" + rm -f -- "$oldPath" + else + echo "[cleanup] Skipping unexpected local filename (won't delete): $oldPath" >&2 + fi + done +else + echo "[cleanup] Local backups <= $KEEP_COUNT, nothing to delete." +fi + +echo "[cleanup] Keeping newest $KEEP_COUNT backups in B2 ($B2_REMOTE)" + +# Safety guard: require remote to be in the form "REMOTE:bucket" +# (bucket root only, no trailing slash/path) +if ! [[ "$B2_REMOTE" =~ ^[^:]+:[^/]+$ ]]; then + echo "[cleanup] Refusing remote cleanup: B2_REMOTE must be bucket-root like 'B2:SoloForge-backup' (got: $B2_REMOTE)" >&2 + exit 1 +fi + +# Remote prune: list only root-level filenames, newest-first (timestamped name makes this safe). +# rclone lsf on a bucket root returns immediate entries only (non-recursive by default). +mapfile -t remoteBackups < <( + rclone lsf "$B2_REMOTE" \ + --files-only \ + --max-depth 1 \ + --include "gitea-dump-*.zip" 2>/dev/null \ + | sed 's:/$::' \ + | sort -r +) + +if (( ${#remoteBackups[@]} > KEEP_COUNT )); then + for old in "${remoteBackups[@]:KEEP_COUNT}"; do + # Extra safety: ensure we only ever delete matching root-level dump zips + if [[ "$old" =~ ^gitea-dump-[0-9]{8}-[0-9]{6}\.zip$ ]]; then + echo "[cleanup] Deleting remote: $old" + rclone deletefile "$B2_REMOTE/$old" + else + echo "[cleanup] Skipping unexpected remote filename (won't delete): $old" >&2 + fi + done +else + echo "[cleanup] Remote backups <= $KEEP_COUNT, nothing to delete." +fi + echo "[backup] All done." +Update backup sctip