#!/usr/bin/env bash set -euo pipefail # Paths — adjust if your layout changes GITEA_DIR="/gitea/gitea" BACKUP_DIR="/gitea/backups" CONTAINER="Gitea" # Retention KEEP_COUNT=7 B2_REMOTE="B2:SoloForge-backup" mkdir -p "$BACKUP_DIR" ts="$(date +%Y%m%d-%H%M%S)" dump_file="gitea-dump-$ts.zip" echo "[backup] Starting backup: $dump_file" # Run dump INSIDE container docker exec -u 1000 "$CONTAINER" sh -lc \ "gitea dump -c /data/gitea/conf/app.ini --file /data/$dump_file" # Move dump from container-mounted volume to backup dir mv "$GITEA_DIR/$dump_file" "$BACKUP_DIR/$dump_file" echo "[backup] Dump created at $BACKUP_DIR/$dump_file" # 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