#!/usr/bin/env bash # Nightly ledger backup to DigitalOcean Spaces, one prefix per game in the shared bucket. # current/ - exact mirror of /var/lib/__SLUG__ # snapshots/ - one dated copy per day, pruned after 90 days # Ledgers are append-only JSONL; the game server never needs stopping. # Sentry Crons check-in: /root/.__SLUG__-sentry-cron holds the check-in # URL (absent file = no check-ins). SCHEDULE must match /etc/cron.d/__SLUG__. set -u SCHEDULE="23 7 * * *" BUCKET="kestrel-wizwar-backups" PREFIX="__SLUG__" SRC="/var/lib/__SLUG__" LOG="/var/log/__SLUG__/backup.log" STAMP=$(date +%Y-%m-%d) mkdir -p "$(dirname "$LOG")" CRON_URL=$(cat /root/.__SLUG__-sentry-cron 2>/dev/null || true) checkin() { [ -n "$CRON_URL" ] && curl -sf -o /dev/null -X POST -H "Content-Type: application/json" \ -d "{\"status\":\"$1\",\"monitor_config\":{\"schedule\":{\"type\":\"crontab\",\"value\":\"$SCHEDULE\"},\"checkin_margin\":30,\"max_runtime\":10,\"timezone\":\"UTC\"}}" \ "$CRON_URL" || true } if [ ! -s /root/.config/rclone/rclone.conf ]; then echo "$(date -Is) skipped: rclone is not configured" >> "$LOG" exit 0 fi { echo "=== $(date -Is) backup start" checkin in_progress if rclone sync "$SRC" "spaces:$BUCKET/$PREFIX/current" --exclude 'rollup.jsonl' 2>&1 && rclone copy "$SRC" "spaces:$BUCKET/$PREFIX/snapshots/$STAMP" 2>&1; then checkin ok else checkin error fi CUTOFF=$(date -d "90 days ago" +%Y-%m-%d) rclone lsf "spaces:$BUCKET/$PREFIX/snapshots/" --dirs-only 2>/dev/null | \ while read -r d; do day="${d%/}" if [[ "$day" < "$CUTOFF" ]]; then echo "pruning snapshot $day" rclone purge "spaces:$BUCKET/$PREFIX/snapshots/$day" 2>&1 fi done echo "=== $(date -Is) backup done" } >> "$LOG" 2>&1