Skip to content

Instantly share code, notes, and snippets.

@gitricko
Created August 16, 2026 14:22
Show Gist options
  • Select an option

  • Save gitricko/a09593fd7c63b1c6e1f429bc5a07120e to your computer and use it in GitHub Desktop.

Select an option

Save gitricko/a09593fd7c63b1c6e1f429bc5a07120e to your computer and use it in GitHub Desktop.
Backup pre-flight checklist: analyze /config disk usage and safely clean caches before volume backup

Backup Pre-Check: /config Disk Analysis & Cleanup

Created: 2026-07-14 | Script: ~/.hermes/scripts/backup-precheck.sh

Purpose

Before volume-zipping /config for a backup, run this script to:

  1. Analyze — what's using space, broken down by folder
  2. Identify — which items are safe to delete vs must-keep
  3. Clean — automatically purge known disposables
  4. Shrink — reduce the backup archive size

Quick Start

# Read-only analysis (no changes)
~/.hermes/scripts/backup-precheck.sh

# Preview safe cleanup
~/.hermes/scripts/backup-precheck.sh --clean --dry-run

# Execute safe cleanup (caches, old backups, old logs)
~/.hermes/scripts/backup-precheck.sh --clean

# Aggressive mode (also purge browser caches + legacy archives)
~/.hermes/scripts/backup-precheck.sh --clean --aggressive --dry-run
# then without --dry-run to execute

Mode Reference

Mode Effect
(no args) Read-only: total size, top folders, deletion candidate list, estimated reclaim
--dry-run Combined with --clean: show what would be deleted without actually doing it
--clean Auto-safe: purges pip/uv/TypeScript caches, prunes OmniRoute DB backups (keep 3), rotates old Hermes logs
--clean --aggressive Adds Puppeteer cache, Chromium cache, and legacy_openclaw.zip to the cleanup

What Gets Cleaned

--clean (Safe - No Data Loss)

Item Typical Size Why Safe
~/.cache/pip 200-300 MB Pip re-downloads packages on next install
~/.cache/uv 40-60 MB UV re-downloads on next tool run
~/.cache/typescript 10-15 MB Auto-rebuilds on next TS compilation
.omniroute/db_backups/ 1.5-2.0 GB Keeps 3 newest (~250 MB), prunes rest
.hermes/logs/agent.log.2+ 5-15 MB Rotated log files, only current + 1 backup kept

--aggressive (Review First)

Item Typical Size Notes
~/.cache/puppeteer/ 300-500 MB Cached Chrome binary - re-downloads if TradingView MCP or other tool needs it
~/.cache/chromium/ 80-120 MB Browser disk cache - auto-rebuilds
legacy_openclaw.zip 26 MB Old migration archive - only delete if migration confirmed complete

Must-Keep Items (Not Targeted)

Folder Size Reason
.hermes/ ~800 MB Agent state DB, skills, bin, LSP
.mnemon/ ~23 MB Cross-session memory persistence
Documents/_code/ ~250 MB Git repos with uncommitted work
wiki/ ~44 MB Knowledge base
.local/share/claude/ ~480 MB Claude Desktop conversations
.local/share/code-server/ ~350 MB VS Code state and extensions
.local/lib/python3.14/ ~245 MB Python packages
.ollama/models/ ~260 MB Downloaded LLM models
.omniroute/storage.sqlite ~80 MB Active OmniRoute routing DB

Real-World Run (2026-07-14)

Before:  5.7 GB
After:   3.8 GB  (--clean only)
Savings: 1.9 GB  (33% reduction)

Breakdown:
  + Purged pip cache        (224 MB)
  + Purged uv cache          (49 MB)
  + Purged TypeScript cache  (11 MB)
  + Pruned OmniRoute DB backups  (1.9 GB -> 240 MB, kept 3)
  + Removed old Hermes log   (5.1 MB)

Script Location and Integration

  • Script: ~/.hermes/scripts/backup-precheck.sh
  • Suggested cadence: Before each volume backup
  • No cron needed - this is an on-demand pre-backup step

Backup Workflow

1.  backup-precheck.sh --clean --dry-run   # preview
2.  backup-precheck.sh --clean              # execute safe cleanup
3.  (optional) backup-precheck.sh --clean --aggressive --dry-run
4.  (optional) backup-precheck.sh --clean --aggressive
5.  df -h /config                             # verify space
6.  # Now proceed with volume zip backup
#!/usr/bin/env bash
# backup-precheck.sh — /config Disk Usage Analysis & Cleanup
# Usage: backup-precheck.sh [--clean] [--aggressive] [--dry-run]
set -eo pipefail
CONFIG="/config"
DRY_RUN=false
CLEAN=false
AGGRESSIVE=false
for arg in "$@"; do
case "$arg" in
--dry-run) DRY_RUN=true ;;
--clean) CLEAN=true ;;
--aggressive) AGGRESSIVE=true ;;
--help|-h)
echo "Usage: backup-precheck.sh [--clean] [--aggressive] [--dry-run]"
echo " (no args) Read-only disk analysis"
echo " --clean Safe cleanup (caches, backup rotation, old logs)"
echo " --aggressive Independently purge puppeteer/chromium cache + legacy archives"
echo " --dry-run Preview what would be deleted (works with --clean and/or --aggressive)"
exit 0 ;;
esac
done
# safe size helpers — filter stderr noise, always return clean output
du_b() {
local b
b=$(du -sb "$1" 2>&1 | grep -E '^[0-9]' | cut -f1 | head -1)
echo "${b:-0}"
}
du_h() {
local h
h=$(du -sh "$1" 2>&1 | grep -E '^[0-9.]' | cut -f1 | head -1)
echo "${h:-?}"
}
hr_size() {
local bytes=${1:-0}
[ "$bytes" -lt 0 ] 2>/dev/null && bytes=0
if [ "$bytes" -ge 1073741824 ]; then
echo "$(echo "scale=1; $bytes/1073741824" | bc)G"
elif [ "$bytes" -ge 1048576 ]; then
echo "$(echo "scale=1; $bytes/1048576" | bc)M"
elif [ "$bytes" -ge 1024 ]; then
echo "$(echo "scale=1; $bytes/1024" | bc)K"
else
echo "${bytes}B"
fi
}
echo "══════════════════════════════════════════"
echo " backup-precheck — $(date)"
echo "══════════════════════════════════════════"
TOTAL=$(du_b "$CONFIG")
echo " /config: $(hr_size $TOTAL)"
df -h "$CONFIG" 2>/dev/null | tail -1 | awk '{printf " FS: %s total, %s used, %s avail (%s)\n", $2, $3, $4, $5}'
echo ""
echo "=== Visible Folders ==="
du -sh "$CONFIG"/*/ 2>/dev/null | sort -rh | head -15 || true
echo ""
echo "=== Hidden Directories ==="
for d in "$CONFIG"/.*/; do
base=$(basename "$d")
[ "$base" = "." ] || [ "$base" = ".." ] && continue
[ -d "$d" ] || continue
sz=$(du_h "$d")
printf " %s %s\n" "$sz" "$base"
done | sort -rh || true
echo ""
echo "=== Large Files (>1MB, top-level) ==="
find "$CONFIG" -maxdepth 1 -type f -size +1M 2>/dev/null | while read -r f; do
printf " %s %s\n" "$(du_h "$f")" "$(basename "$f")"
done || true
# ── Deletion candidates ──
echo ""
echo "=== Deletion Candidates ==="
DB_BACKUP_DIR="$CONFIG/.omniroute/db_backups"
if [ -d "$DB_BACKUP_DIR" ]; then
BK_COUNT=$(find "$DB_BACKUP_DIR" -name '*.sqlite' 2>/dev/null | wc -l) || BK_COUNT=0
BK_SIZE=$(du_h "$DB_BACKUP_DIR")
echo " 🟡 OmniRoute DB backups: $BK_SIZE ($BK_COUNT files)"
if [ "$BK_COUNT" -gt 3 ]; then
newest3=$(ls -t "$DB_BACKUP_DIR"/*.sqlite 2>/dev/null | head -3 | tail -1) || true
if [ -n "$newest3" ] && [ -f "$newest3" ]; then
BK_KEEP_B=$(du_b "$newest3")
BK_TOTAL_B=$(du_b "$DB_BACKUP_DIR")
BK_RECLAIM=$(( BK_TOTAL_B - BK_KEEP_B * 3 ))
echo " → Keep 3 newest, reclaim ~$(hr_size $BK_RECLAIM)"
fi
fi
fi
for c in "$CONFIG/.cache/pip" "$CONFIG/.cache/uv" "$CONFIG/.cache/typescript"; do
[ -d "$c" ] || continue
echo " 🔵 $(basename $c) cache: $(du_h "$c") (safe to purge)"
done
[ -d "$CONFIG/.cache/puppeteer" ] && \
echo " ⚠️ Puppeteer cache: $(du_h "$CONFIG/.cache/puppeteer") (re-downloads on next use)"
[ -d "$CONFIG/.cache/chromium" ] && \
echo " ⚠️ Chromium cache: $(du_h "$CONFIG/.cache/chromium") (browser cache, auto-rebuilds)"
[ -d "$CONFIG/.omniroute/call_logs" ] && \
echo " 🔵 OmniRoute call logs: $(du_h "$CONFIG/.omniroute/call_logs") (clean old >30d)"
[ -f "$CONFIG/Documents/legacy_openclaw.zip" ] && \
echo " ❓ Legacy archive: $(du_h "$CONFIG/Documents/legacy_openclaw.zip") (review before delete)"
# ── Reclaim estimate (all arithmetic via intermediate vars) ──
echo ""
echo "=== Estimated Reclaimable ==="
SAFE_R=0
for d in "$CONFIG/.cache/pip" "$CONFIG/.cache/uv" "$CONFIG/.cache/typescript"; do
[ -d "$d" ] || continue
b=$(du_b "$d")
SAFE_R=$(( SAFE_R + b ))
done
if [ -d "$DB_BACKUP_DIR" ]; then
BC=$(find "$DB_BACKUP_DIR" -name '*.sqlite' | wc -l) || BC=0
if [ "$BC" -gt 3 ]; then
n3=$(ls -t "$DB_BACKUP_DIR"/*.sqlite 2>/dev/null | head -3 | tail -1) || true
if [ -n "$n3" ] && [ -f "$n3" ]; then
b_total=$(du_b "$DB_BACKUP_DIR")
b_one=$(du_b "$n3")
SAFE_R=$(( SAFE_R + b_total - b_one * 3 ))
fi
fi
fi
echo " Safe (--clean): $(hr_size $SAFE_R)"
echo " After safe clean: $(hr_size $(( TOTAL - SAFE_R )))"
AGGR_R=$SAFE_R
for d in "$CONFIG/.cache/puppeteer" "$CONFIG/.cache/chromium"; do
[ -d "$d" ] || continue
b=$(du_b "$d")
AGGR_R=$(( AGGR_R + b ))
done
[ -f "$CONFIG/Documents/legacy_openclaw.zip" ] && AGGR_R=$(( AGGR_R + $(du_b "$CONFIG/Documents/legacy_openclaw.zip") ))
if [ "$AGGR_R" -gt "$SAFE_R" ]; then
echo " +Aggressive extras: $(hr_size $(( AGGR_R - SAFE_R )))"
echo " After all cleanup: $(hr_size $(( TOTAL - AGGR_R )))"
fi
# ── Execute cleanup ──
# Helper: purge a cache directory if it exists (defined early so both --clean and --aggressive can use it)
clean_dir() {
label=$1; dir=$2
[ -d "$dir" ] || return 0
sz=$(du_h "$dir")
if $DRY_RUN; then
echo " [dry-run] Purge $label ($sz)"
else
rm -rf "$dir" 2>/dev/null || true
echo " ✅ Purged $label ($sz → 0)"
fi
}
if $CLEAN; then
echo ""
echo "=== Executing Cleanup ==="
clean_dir "pip cache" "$CONFIG/.cache/pip"
clean_dir "uv cache" "$CONFIG/.cache/uv"
clean_dir "TypeScript cache" "$CONFIG/.cache/typescript"
# DB backups: keep 3
if [ -d "$DB_BACKUP_DIR" ]; then
BC=$(find "$DB_BACKUP_DIR" -name '*.sqlite' | wc -l) || BC=0
if [ "$BC" -gt 3 ]; then
sz_before=$(du_h "$DB_BACKUP_DIR")
if $DRY_RUN; then
echo " [dry-run] Prune $(( BC - 3 )) old DB backups ($sz_before → keep 3)"
else
ls -t "$DB_BACKUP_DIR"/*.sqlite 2>/dev/null | tail -n +4 | while read -r old; do
rm -f "$old" "${old}-journal" "${old}-wal" "${old}-shm" 2>/dev/null || true
done
echo " ✅ Pruned DB backups ($sz_before → $(du_h "$DB_BACKUP_DIR"))"
fi
fi
fi
# Call logs >30 days
if [ -d "$CONFIG/.omniroute/call_logs" ]; then
find "$CONFIG/.omniroute/call_logs" -type f -name "????-??-??" 2>/dev/null | while read -r f; do
fname=$(basename "$f")
fepoch=$(date -d "$fname" +%s 2>/dev/null || echo 0)
[ "$fepoch" -eq 0 ] && continue
now_epoch=$(date +%s)
[ $(( (now_epoch - fepoch) / 86400 )) -le 30 ] && continue
if $DRY_RUN; then
echo " [dry-run] Remove old call log: $fname ($(du_h "$f"))"
else
rm -f "$f"
echo " ✅ Removed old call log: $fname"
fi
done || true
fi
# Old Hermes log rotas (agent.log.2+)
for lf in "$CONFIG/.hermes/logs"/agent.log.*; do
[ -f "$lf" ] || continue
idx="${lf##*.}"
[ "$idx" -ge 2 ] 2>/dev/null || continue
sz=$(du_h "$lf")
if $DRY_RUN; then
echo " [dry-run] Remove old log: $(basename "$lf") ($sz)"
else
rm -f "$lf"
echo " ✅ Removed old log: $(basename "$lf") ($sz)"
fi
done
fi
# Aggressive extras (independent of --clean)
if $AGGRESSIVE; then
clean_dir "Puppeteer cache" "$CONFIG/.cache/puppeteer"
clean_dir "Chromium cache" "$CONFIG/.cache/chromium"
if [ -f "$CONFIG/Documents/legacy_openclaw.zip" ]; then
sz=$(du_h "$CONFIG/Documents/legacy_openclaw.zip")
if $DRY_RUN; then
echo " [dry-run] Delete legacy_openclaw.zip ($sz)"
else
rm -f "$CONFIG/Documents/legacy_openclaw.zip"
echo " ✅ Deleted legacy_openclaw.zip ($sz)"
fi
fi
fi
# ── Final ──
echo ""
echo "══════════════════════════════════════════"
echo " /config final: $(du_h "$CONFIG")"
echo " Done — backup-precheck.sh --help"
echo "══════════════════════════════════════════"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment