diff options
Diffstat (limited to 'custom/zfs-snap-prune')
| -rwxr-xr-x | custom/zfs-snap-prune | 208 |
1 files changed, 0 insertions, 208 deletions
diff --git a/custom/zfs-snap-prune b/custom/zfs-snap-prune deleted file mode 100755 index 762ff99..0000000 --- a/custom/zfs-snap-prune +++ /dev/null @@ -1,208 +0,0 @@ -#!/bin/bash -# zfs-snap-prune - Prune old ZFS snapshots with hybrid retention policy -# -# Retention Policy: -# - Always keep the N most recent snapshots (default: 20) -# - Delete snapshots beyond N only if older than MAX_AGE (default: 180 days) -# - Never delete genesis snapshot -# -# Usage: -# zfs-snap-prune [OPTIONS] -# -# Options: -# --dry-run Show what would be deleted without deleting -# --verbose Show decision for every snapshot -# --quiet Suppress non-error output -# --test Use mock data from stdin instead of real ZFS -# --help Show this help message -# -# Environment variables: -# POOL_NAME - ZFS pool name (default: zroot) -# ROOT_DATASET - Root dataset path (default: ROOT/default) -# KEEP_COUNT - Number of recent snapshots to always keep (default: 20) -# MAX_AGE_DAYS - Delete older snapshots beyond KEEP_COUNT (default: 180) -# NOW_OVERRIDE - Override current timestamp for testing (epoch seconds) - -set -e - -# Configuration (can be overridden by environment) -POOL_NAME="${POOL_NAME:-zroot}" -ROOT_DATASET="${ROOT_DATASET:-ROOT/default}" -KEEP_COUNT="${KEEP_COUNT:-20}" -MAX_AGE_DAYS="${MAX_AGE_DAYS:-180}" - -FULL_DATASET="${POOL_NAME}/${ROOT_DATASET}" - -# Flags -DRY_RUN=false -VERBOSE=false -QUIET=false -TEST_MODE=false - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -usage() { - sed -n '2,/^$/p' "$0" | sed 's/^# \?//' - exit 0 -} - -info() { - [[ "$QUIET" == "true" ]] && return - echo -e "${GREEN}[INFO]${NC} $1" -} - -verbose() { - [[ "$VERBOSE" != "true" ]] && return - echo -e "${BLUE}[VERBOSE]${NC} $1" -} - -warn() { - [[ "$QUIET" == "true" ]] && return - echo -e "${YELLOW}[WARN]${NC} $1" -} - -error() { - echo -e "${RED}[ERROR]${NC} $1" >&2 - exit 1 -} - -# Parse arguments -while [[ $# -gt 0 ]]; do - case "$1" in - --dry-run) - DRY_RUN=true - shift - ;; - --verbose) - VERBOSE=true - shift - ;; - --quiet) - QUIET=true - shift - ;; - --test) - TEST_MODE=true - shift - ;; - --help|-h) - usage - ;; - *) - error "Unknown option: $1" - ;; - esac -done - -# Check if running as root (skip in test mode) -if [[ "$TEST_MODE" != "true" ]] && [[ $EUID -ne 0 ]]; then - error "This script must be run as root" -fi - -# Get current timestamp (can be overridden for testing) -NOW="${NOW_OVERRIDE:-$(date +%s)}" -MAX_AGE_SECONDS=$((MAX_AGE_DAYS * 24 * 60 * 60)) -CUTOFF_TIME=$((NOW - MAX_AGE_SECONDS)) - -info "Pruning snapshots for ${FULL_DATASET}" -info "Policy: Keep ${KEEP_COUNT} recent, delete if older than ${MAX_AGE_DAYS} days" -[[ "$DRY_RUN" == "true" ]] && info "DRY RUN - no changes will be made" - -# Get snapshots - either from ZFS or stdin (test mode) -# Expected format: snapshot_name<TAB>creation_date_string -# Example: zroot/ROOT/default@pre-pacman_2025-01-15 Wed Jan 15 10:30 2025 -if [[ "$TEST_MODE" == "true" ]]; then - # Read mock data from stdin - SNAPSHOTS=$(cat | tac) -else - # Query real ZFS - sorted by creation (oldest first), then reversed for newest first - SNAPSHOTS=$(zfs list -H -t snapshot -o name,creation -s creation -r "$FULL_DATASET" 2>/dev/null | \ - grep "^${FULL_DATASET}@" | \ - tac) || true -fi - -if [[ -z "$SNAPSHOTS" ]]; then - info "No snapshots found" - exit 0 -fi - -# Count snapshots -TOTAL=$(echo "$SNAPSHOTS" | wc -l) -info "Found ${TOTAL} snapshots" - -# Track results -DELETED=0 -KEPT=0 -POSITION=0 - -# Process each snapshot -while IFS=$'\t' read -r snapshot creation_str; do - [[ -z "$snapshot" ]] && continue - - POSITION=$((POSITION + 1)) - SNAP_NAME="${snapshot##*@}" - - # Parse creation time - if [[ "$TEST_MODE" == "true" ]]; then - # In test mode, creation_str is epoch seconds - SNAP_TIME="$creation_str" - else - # In real mode, parse date string - SNAP_TIME=$(date -d "$creation_str" +%s 2>/dev/null || echo "0") - fi - - AGE_DAYS=$(( (NOW - SNAP_TIME) / 86400 )) - - # Decision logic - if [[ $POSITION -le $KEEP_COUNT ]]; then - # Always keep the first KEEP_COUNT snapshots (most recent) - verbose "KEEP: ${SNAP_NAME} (position ${POSITION}/${KEEP_COUNT}, ${AGE_DAYS} days old) - within keep count" - KEPT=$((KEPT + 1)) - elif [[ "$SNAP_NAME" == "genesis" ]]; then - # Never delete genesis - verbose "KEEP: ${SNAP_NAME} (position ${POSITION}, ${AGE_DAYS} days old) - genesis protected" - KEPT=$((KEPT + 1)) - elif [[ $SNAP_TIME -ge $CUTOFF_TIME ]]; then - # Not old enough to delete - verbose "KEEP: ${SNAP_NAME} (position ${POSITION}, ${AGE_DAYS} days old) - younger than ${MAX_AGE_DAYS} days" - KEPT=$((KEPT + 1)) - else - # Delete: beyond keep count AND older than max age - if [[ "$DRY_RUN" == "true" ]]; then - info "WOULD DELETE: ${SNAP_NAME} (position ${POSITION}, ${AGE_DAYS} days old)" - DELETED=$((DELETED + 1)) - elif [[ "$TEST_MODE" == "true" ]]; then - # Test mode: simulate deletion (don't actually call zfs) - verbose "DELETE: ${SNAP_NAME} (position ${POSITION}, ${AGE_DAYS} days old)" - DELETED=$((DELETED + 1)) - else - verbose "DELETE: ${SNAP_NAME} (position ${POSITION}, ${AGE_DAYS} days old)" - if zfs destroy "$snapshot" 2>/dev/null; then - DELETED=$((DELETED + 1)) - else - warn "Failed to delete ${snapshot}" - fi - fi - fi -done <<< "$SNAPSHOTS" - -# Summary -info "Summary: ${KEPT} kept, ${DELETED} deleted" - -# Regenerate GRUB menu if we deleted anything (skip in dry-run and test modes) -if [[ $DELETED -gt 0 ]] && [[ "$DRY_RUN" != "true" ]] && [[ "$TEST_MODE" != "true" ]]; then - if [[ -x /usr/local/bin/grub-zfs-snap ]]; then - info "Regenerating GRUB menu..." - /usr/local/bin/grub-zfs-snap - fi -fi - -# Exit with special code for testing (number of deleted) -if [[ "$TEST_MODE" == "true" ]]; then - echo "RESULT:kept=${KEPT},deleted=${DELETED}" -fi |
