The closing summary still named the old parser's variables, so every run wrote its row and then died on the print, and Sentry was told the night failed. It reads the shared parser's counts now. The check-ins become one at the end with the run's duration: a start and a finish seconds apart can reach Sentry in either order, and a start that lands second is never closed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwFKMuQnPAEHJ5yA1q4orh
52 lines
2.0 KiB
Bash
Executable File
52 lines
2.0 KiB
Bash
Executable File
#!/bin/bash
|
|
# Nightly ledger backup to DigitalOcean Spaces, beside wizwar's in the same bucket.
|
|
# current/ - exact mirror of /var/lib/waving-hands
|
|
# snapshots/ - one dated copy per day, pruned after 90 days
|
|
# Ledgers are append-only JSONL; the duel server never needs stopping.
|
|
# Sentry Crons check-in: /root/.waving-hands-sentry-cron holds the check-in
|
|
# URL (absent file = no check-ins). SCHEDULE must match /etc/cron.d/waving-hands.
|
|
set -u
|
|
SCHEDULE="23 7 * * *"
|
|
BUCKET="kestrel-wizwar-backups"
|
|
PREFIX="waving-hands"
|
|
SRC="/var/lib/waving-hands"
|
|
LOG="/var/log/waving-hands/backup.log"
|
|
STAMP=$(date +%Y-%m-%d)
|
|
mkdir -p "$(dirname "$LOG")"
|
|
CRON_URL=$(cat /root/.waving-hands-sentry-cron 2>/dev/null || true)
|
|
# One check-in, when the run ends, carrying its duration: a start and a
|
|
# finish sent seconds apart can reach Sentry in either order, and a start
|
|
# that lands second is never closed. A run that never ends sends nothing,
|
|
# and Sentry marks the night missed.
|
|
START=$SECONDS
|
|
checkin() {
|
|
[ -n "$CRON_URL" ] && curl -sf -o /dev/null -X POST -H "Content-Type: application/json" \
|
|
-d "{\"status\":\"$1\",\"duration\":$((SECONDS - START)),\"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"
|
|
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
|