#!/usr/bin/env bash # rotate-snapshots.sh — create today's daily snapshot + prune snapshots # older than N days. Idempotent: safe to run repeatedly on the same day. # # Snapshots created by this script are named `daily-YYYY-MM-DD`. Only # snapshots matching that prefix are candidates for pruning — hand-created # snapshots (release-anchor, pre-migration, etc.) are never touched. # # Env / args: # CLAWSTOR_BIN path to claw-store binary (default: ~/clawstor-deploy/claw-store) # CLAWSTOR_CONFIG path to config.toml (default: ~/clawstor-deploy/config.toml) # RETAIN_DAYS snapshots older than this many days get pruned (default: 30) # DRY_RUN=1 print what would happen, no side effects set -euo pipefail CLAWSTOR_BIN=${CLAWSTOR_BIN:-$HOME/clawstor-deploy/claw-store} CLAWSTOR_CONFIG=${CLAWSTOR_CONFIG:-$HOME/clawstor-deploy/config.toml} RETAIN_DAYS=${RETAIN_DAYS:-30} DRY_RUN=${DRY_RUN:-0} if [ ! -x "$CLAWSTOR_BIN" ]; then echo "error: $CLAWSTOR_BIN not executable" >&2 exit 1 fi CS="$CLAWSTOR_BIN --config $CLAWSTOR_CONFIG" TODAY=$(date +%Y-%m-%d) NAME="daily-$TODAY" echo "── rotate-snapshots ────────────────────────────────" echo "today: $NAME" echo "retention: $RETAIN_DAYS days" # Create today's snapshot. Idempotent: `cluster-snapshot-create` errors # if the name already exists — we treat that as success. if [ "$DRY_RUN" = "1" ]; then echo "dry-run: would create snapshot $NAME" else if $CS cluster-snapshot-create --name "$NAME" 2>&1 | tail -6; then echo "created: $NAME" else echo "already exists (idempotent): $NAME" fi fi # Compute cutoff — POSIX date arithmetic (no GNU date extensions needed # because we do the math on the epoch integer). NOW_EPOCH=$(date +%s) CUTOFF_EPOCH=$((NOW_EPOCH - RETAIN_DAYS * 86400)) # Snapshot list format: # CREATED_AT NAME BLOBS SIZE # 1784046443 daily-2026-07-14 4 224 # So awk field 1 = epoch, field 2 = name. PRUNE_LIST=$($CS cluster-snapshot-list 2>/dev/null | awk -v cutoff="$CUTOFF_EPOCH" ' NR > 4 && $2 ~ /^daily-/ && $1 < cutoff { print $2 } ') echo if [ -z "$PRUNE_LIST" ]; then echo "nothing to prune" else echo "prune candidates (older than $RETAIN_DAYS days):" echo "$PRUNE_LIST" | sed 's/^/ /' if [ "$DRY_RUN" = "1" ]; then echo "dry-run: no snapshots deleted" else echo "$PRUNE_LIST" | while read -r snap; do $CS cluster-snapshot-delete --name "$snap" 2>&1 | tail -1 done fi fi echo "────────────────────────────────────────────────────"