etc.git


etc

~ scripts and random things
git clone https://github.com/djnnvx/etc.git
log | files | refs | archive

backup-crashes.sh (3081B)

      1 #!/bin/bash
      2 # backup-crashes.sh — archive unique AFL++ crashes with deduplication.
      3 #
      4 # Intended to run as a cron job or in a background loop:
      5 #   crontab -e
      6 #   */15 * * * *  /path/to/usbip/backup-crashes.sh >> /tmp/crash-backup.log 2>&1
      7 #
      8 # Or in a loop from another terminal:
      9 #   while sleep 900; do bash backup-crashes.sh; done
     10 #
     11 # What it does:
     12 #   1. Scans output/*/crashes/ for crash files newer than .last_backup stamp
     13 #   2. Deduplicates by sha256 (avoids archiving the same crash twice)
     14 #   3. Compresses unique new crashes into backups/crashes_YYYYMMDD_HHMMSS.tar.gz
     15 #   4. Updates .last_backup stamp
     16 
     17 set -euo pipefail
     18 
     19 SCRIPT_DIR="$(cd "$(dirname "${0}")" && pwd)"
     20 cd "${SCRIPT_DIR}"
     21 
     22 OUTPUT_DIR="${SCRIPT_DIR}/output"
     23 BACKUP_DIR="${SCRIPT_DIR}/backups"
     24 STAMP_FILE="${SCRIPT_DIR}/.last_backup"
     25 SEEN_HASHES="${SCRIPT_DIR}/.seen_crash_hashes"
     26 
     27 log()  { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
     28 
     29 mkdir -p "${BACKUP_DIR}"
     30 touch "${STAMP_FILE}" "${SEEN_HASHES}"
     31 
     32 # ── find new crash files ──────────────────────────────────────────────────────
     33 TMPLIST="$(mktemp)"
     34 trap "rm -f ${TMPLIST}" EXIT
     35 
     36 # Collect crash files from all AFL++ fuzzer subdirs
     37 find "${OUTPUT_DIR}" -path "*/crashes/id:*" -newer "${STAMP_FILE}" \
     38     -not -name "README.txt" \
     39     2>/dev/null > "${TMPLIST}" || true
     40 
     41 TOTAL=$(wc -l < "${TMPLIST}")
     42 if [[ "${TOTAL}" -eq 0 ]]; then
     43     log "No new crashes since last backup."
     44     exit 0
     45 fi
     46 
     47 log "Found ${TOTAL} new crash file(s) since last backup."
     48 
     49 # ── deduplicate by sha256 ─────────────────────────────────────────────────────
     50 TMPSTAGE="$(mktemp -d)"
     51 trap "rm -rf ${TMPSTAGE} ${TMPLIST}" EXIT
     52 
     53 NEW=0
     54 while IFS= read -r crash; do
     55     hash="$(sha256sum "${crash}" | cut -d' ' -f1)"
     56     if ! grep -qF "${hash}" "${SEEN_HASHES}" 2>/dev/null; then
     57         cp "${crash}" "${TMPSTAGE}/"
     58         echo "${hash}" >> "${SEEN_HASHES}"
     59         NEW=$((NEW + 1))
     60     fi
     61 done < "${TMPLIST}"
     62 
     63 if [[ "${NEW}" -eq 0 ]]; then
     64     log "All ${TOTAL} new files were duplicates. Nothing to archive."
     65     touch "${STAMP_FILE}"
     66     exit 0
     67 fi
     68 
     69 # ── create archive ────────────────────────────────────────────────────────────
     70 ARCHIVE="${BACKUP_DIR}/crashes_$(date +%Y%m%d_%H%M%S).tar.gz"
     71 tar -czf "${ARCHIVE}" -C "${TMPSTAGE}" .
     72 
     73 SIZE="$(du -sh "${ARCHIVE}" | cut -f1)"
     74 log "Archived ${NEW} unique crash(es) → ${ARCHIVE} (${SIZE})"
     75 
     76 # Update stamp AFTER successful archive
     77 touch "${STAMP_FILE}"
     78 
     79 # ── prune old backups (keep last 50) ─────────────────────────────────────────
     80 ls -t "${BACKUP_DIR}"/crashes_*.tar.gz 2>/dev/null | tail -n +51 | while read old; do
     81     rm -f "${old}"
     82     log "Pruned old backup: $(basename "${old}")"
     83 done
     84 
     85 exit 0