Initial commit: backup + mirror automation for self-hosted Gitea

Includes:
- gitea-backups/bin/backup.sh (per-push bundle + DB snapshot to local + S3)
- gitea-backups/bin/install-hooks.sh (idempotent post-receive shim installer)
- gitea-backups/bin/retention.sh (count-based retention: keep newest 7 dates)
- gitea-mirror/bin/auto-mirror.sh (Gitea -> GitHub push mirror automation,
  hardened against Gitea outages)
- crontab.txt (reference for the 3 cron entries)
- README.md (architecture, layout, bootstrap)
This commit is contained in:
2026-05-09 06:02:09 +00:00
commit a181625c89
7 changed files with 496 additions and 0 deletions
+79
View File
@@ -0,0 +1,79 @@
#!/usr/bin/env bash
# Daily retention sweep — keeps the newest KEEP_DAYS distinct calendar
# dates of backups, on BOTH local disk and S3, regardless of how old
# those dates are. So a long quiet period leaves backups intact instead
# of letting them age out.
#
# Idempotent. Safe to run on a cron.
set -uo pipefail
CONFIG_DIR="/home/ubuntu/gitea-backups"
LOG="${CONFIG_DIR}/logs/retention.log"
KEEP_DAYS=7
S3_REMOTE="s3"
S3_BUCKET="toqqer-gitea-backup"
RCLONE_CONF="/home/ubuntu/.config/rclone/rclone.conf"
QUIET=0
[[ "${1:-}" == "--quiet" ]] && QUIET=1
log() { printf '%s %s\n' "$(date -u +%FT%TZ)" "$*" >> "$LOG"; }
say() { [[ $QUIET -eq 0 ]] && echo "$*"; log "$*"; }
# ---- S3: keep newest KEEP_DAYS top-level YYYY-MM-DD folders ----
mapfile -t s3_dates < <(
rclone --config "$RCLONE_CONF" lsd "${S3_REMOTE}:${S3_BUCKET}/" 2>/dev/null \
| awk '{print $NF}' \
| grep -E '^[0-9]{4}-[0-9]{2}-[0-9]{2}$' \
| sort
)
n=${#s3_dates[@]}
if (( n <= KEEP_DAYS )); then
say "S3: ${n} date-folder(s) present — all kept (limit ${KEEP_DAYS})"
else
to_delete=$(( n - KEEP_DAYS ))
deleted=0
for ((i=0; i<to_delete; i++)); do
d="${s3_dates[i]}"
if rclone --config "$RCLONE_CONF" purge "${S3_REMOTE}:${S3_BUCKET}/${d}" 2>>"$LOG"; then
say "S3: deleted ${d}/"
deleted=$((deleted+1))
else
say "S3: FAIL deleting ${d}/ (see log)"
fi
done
say "S3: kept newest ${KEEP_DAYS} date-folder(s), deleted ${deleted} older"
fi
# ---- LOCAL: same semantics on /home/ubuntu/gitea-backups/{repos,db}/ ----
# All filenames are date-prefixed (YYYY-MM-DDTHH-MM-SSZ...), so we group by
# the leading date and keep files only from the newest KEEP_DAYS dates seen.
mapfile -t local_dates < <(
{
find "${CONFIG_DIR}/repos" -type f -name '*.bundle' 2>/dev/null
find "${CONFIG_DIR}/db" -type f -name '*.db.gz' 2>/dev/null
} | grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}' | sort -u
)
ln=${#local_dates[@]}
if (( ln <= KEEP_DAYS )); then
say "LOCAL: ${ln} date(s) present — all kept (limit ${KEEP_DAYS})"
else
keep_from=$(( ln - KEEP_DAYS ))
keep_set="$(printf '%s\n' "${local_dates[@]:$keep_from}")"
deleted_files=0
while IFS= read -r f; do
[[ -z "$f" ]] && continue
fd=$(grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}' <<< "$(basename "$f")" | head -1)
if ! grep -qx "$fd" <<< "$keep_set"; then
rm -f "$f" && deleted_files=$((deleted_files+1))
fi
done < <(
find "${CONFIG_DIR}/repos" -type f -name '*.bundle' 2>/dev/null
find "${CONFIG_DIR}/db" -type f -name '*.db.gz' 2>/dev/null
)
find "${CONFIG_DIR}/repos" -mindepth 2 -type d -empty -delete 2>>"$LOG" || true
say "LOCAL: kept newest ${KEEP_DAYS} date(s), deleted ${deleted_files} file(s)"
fi
exit 0