diff options
| -rw-r--r-- | README.org | 57 | ||||
| -rwxr-xr-x | build.sh | 7 | ||||
| -rwxr-xr-x | custom/install-archzfs | 46 | ||||
| -rwxr-xr-x | custom/zfs-snap-prune | 208 | ||||
| -rwxr-xr-x | scripts/test-zfs-snap-prune.sh | 303 |
5 files changed, 621 insertions, 0 deletions
@@ -21,6 +21,7 @@ manual module loading or package installation during the install process. - *Genesis Snapshot* - Automatic pristine-state snapshot after installation - *Rollback Script* - One-command factory reset via ~/root/rollback-to-genesis~ - *Pre-Pacman Snapshots* - Automatic snapshots before package operations +- *Snapshot Retention* - Automatic pruning keeps disk usage bounded - *GRUB Snapshot Boot* - Boot into any ZFS snapshot directly from GRUB menu - *NetworkManager* - WiFi configuration copied to installed system - *SSH Ready* - Optional SSH with root login for headless servers @@ -94,6 +95,7 @@ archzfs/ │ ├── install-archzfs # Interactive installation script │ ├── install-archzfs.conf.example # Example config for unattended install │ ├── grub-zfs-snap # ZFS snapshot GRUB menu generator +│ ├── zfs-snap-prune # Snapshot retention/pruning script │ ├── 40_zfs_snapshots # GRUB generator script (for /etc/grub.d/) │ └── zz-grub-zfs-snap.hook # Pacman hook for auto-regeneration ├── scripts/ @@ -509,6 +511,61 @@ zpool status zpool list #+END_SRC +** Snapshot Retention Policy + +The system automatically prunes old snapshots to prevent unbounded disk usage. + +*** Retention Rules + +| Rule | Description | +|------+-------------| +| Keep 20 most recent | Always preserved regardless of age | +| Delete if >180 days | Snapshots beyond position 20, older than 6 months | +| Genesis protected | Never deleted, regardless of position or age | + +*** How It Works + +Pruning runs automatically: +- *After every pacman operation* - Pre-pacman hook triggers prune after creating new snapshot +- *Daily via systemd timer* - Catches any missed pruning, syncs GRUB menu + +*** Manual Pruning + +#+BEGIN_SRC bash +# Preview what would be deleted +zfs-snap-prune --dry-run + +# Run with verbose output +zfs-snap-prune --verbose + +# Check current snapshots +zfs-snap-prune --help +#+END_SRC + +*** Customizing Retention + +Set environment variables before running: + +#+BEGIN_SRC bash +# Keep more snapshots (default: 20) +KEEP_COUNT=50 zfs-snap-prune + +# Shorter retention period (default: 180 days) +MAX_AGE_DAYS=90 zfs-snap-prune + +# Different pool/dataset +POOL_NAME=tank ROOT_DATASET=ROOT/arch zfs-snap-prune +#+END_SRC + +To change defaults permanently, edit ~/usr/local/bin/zfs-snap-prune~. + +*** Example: One Year of Use + +With weekly pacman updates (~52/year) plus genesis: +- Total snapshots created: ~53 +- Snapshots kept: ~27 (20 recent + ~6 within 180 days + genesis) +- Snapshots pruned: ~26 (older than 180 days, beyond position 20) + * Keeping Up-to-Date ** Kernel Updates on Installed Systems @@ -187,6 +187,10 @@ mkdir -p "$PROFILE_DIR/airootfs/usr/local/share/grub-zfs-snap" cp "$CUSTOM_DIR/40_zfs_snapshots" "$PROFILE_DIR/airootfs/usr/local/share/grub-zfs-snap/" cp "$CUSTOM_DIR/zz-grub-zfs-snap.hook" "$PROFILE_DIR/airootfs/usr/local/share/grub-zfs-snap/" +# Copy zfs-snap-prune for snapshot retention +info "Copying zfs-snap-prune..." +cp "$CUSTOM_DIR/zfs-snap-prune" "$PROFILE_DIR/airootfs/usr/local/bin/" + # Copy example config for unattended installs mkdir -p "$PROFILE_DIR/airootfs/root" cp "$CUSTOM_DIR/install-archzfs.conf.example" "$PROFILE_DIR/airootfs/root/" @@ -207,6 +211,9 @@ if grep -q "file_permissions=" "$PROFILE_DIR/profiledef.sh"; then /)/ i\ ["/usr/local/bin/grub-zfs-snap"]="0:0:755" }' "$PROFILE_DIR/profiledef.sh" sed -i '/^file_permissions=(/,/)/ { + /)/ i\ ["/usr/local/bin/zfs-snap-prune"]="0:0:755" + }' "$PROFILE_DIR/profiledef.sh" + sed -i '/^file_permissions=(/,/)/ { /)/ i\ ["/etc/shadow"]="0:0:400" }' "$PROFILE_DIR/profiledef.sh" fi diff --git a/custom/install-archzfs b/custom/install-archzfs index 660cf34..2cec709 100755 --- a/custom/install-archzfs +++ b/custom/install-archzfs @@ -1114,6 +1114,11 @@ if zfs snapshot "$DATASET@$SNAPSHOT_NAME"; then else echo "Warning: Failed to create snapshot" >&2 fi + +# Prune old snapshots (runs quietly, non-blocking) +if [[ -x /usr/local/bin/zfs-snap-prune ]]; then + /usr/local/bin/zfs-snap-prune --quiet & +fi EOF chmod +x /mnt/usr/local/bin/zfs-pre-snapshot @@ -1121,6 +1126,46 @@ EOF info "Pacman hook configured." } +configure_snapshot_retention() { + step "Configuring Snapshot Retention" + + # Copy the prune script + cp /usr/local/bin/zfs-snap-prune /mnt/usr/local/bin/zfs-snap-prune + chmod +x /mnt/usr/local/bin/zfs-snap-prune + + # Create systemd service for pruning + cat > /mnt/etc/systemd/system/zfs-snap-prune.service << 'EOF' +[Unit] +Description=Prune old ZFS snapshots +After=zfs.target + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/zfs-snap-prune --quiet +EOF + + # Create systemd timer for daily pruning + cat > /mnt/etc/systemd/system/zfs-snap-prune.timer << 'EOF' +[Unit] +Description=Daily ZFS snapshot pruning + +[Timer] +OnCalendar=daily +Persistent=true +RandomizedDelaySec=1h + +[Install] +WantedBy=timers.target +EOF + + # Enable the timer + arch-chroot /mnt systemctl enable zfs-snap-prune.timer + + info "Snapshot retention configured." + info "Policy: Keep 20 recent, delete if older than 180 days" + info "Genesis snapshot is always preserved." +} + copy_archsetup() { step "Installing archsetup Launcher" @@ -1299,6 +1344,7 @@ main() { configure_grub_zfs_snap configure_zfs_services configure_pacman_hook + configure_snapshot_retention copy_archsetup sync_efi_partitions create_genesis_snapshot diff --git a/custom/zfs-snap-prune b/custom/zfs-snap-prune new file mode 100755 index 0000000..762ff99 --- /dev/null +++ b/custom/zfs-snap-prune @@ -0,0 +1,208 @@ +#!/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 diff --git a/scripts/test-zfs-snap-prune.sh b/scripts/test-zfs-snap-prune.sh new file mode 100755 index 0000000..d59a7cc --- /dev/null +++ b/scripts/test-zfs-snap-prune.sh @@ -0,0 +1,303 @@ +#!/bin/bash +# test-zfs-snap-prune.sh - Comprehensive test suite for zfs-snap-prune +# +# Runs various scenarios with mock data to verify the pruning logic. +# No root or ZFS required - uses --test mode with mock data. + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PRUNE_SCRIPT="$SCRIPT_DIR/../custom/zfs-snap-prune" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +# Test counters - use temp files to avoid subshell issues with pipes +COUNTER_FILE=$(mktemp) +echo "0 0 0" > "$COUNTER_FILE" # run passed failed +trap "rm -f $COUNTER_FILE" EXIT + +# Time constants for generating test data +DAY=$((24 * 60 * 60)) +NOW=$(date +%s) + +# Generate a snapshot line for mock data +# Args: snapshot_name days_ago +# Output: zroot/ROOT/default@name<TAB>epoch_timestamp +make_snap() { + local name="$1" + local days_ago="$2" + local timestamp=$((NOW - (days_ago * DAY))) + echo -e "zroot/ROOT/default@${name}\t${timestamp}" +} + +# Generate N snapshots with given prefix and starting age +# Args: prefix count start_days_ago +# NOTE: Outputs oldest first (like ZFS with -s creation), so start_age is the OLDEST +make_snaps() { + local prefix="$1" + local count="$2" + local start_age="$3" + # Generate from oldest to newest (ZFS order with -s creation) + for ((i=count; i>=1; i--)); do + make_snap "${prefix}_${i}" $((start_age + i - 1)) + done +} + +# Increment counter in temp file +# Args: position (1=run, 2=passed, 3=failed) +inc_counter() { + local pos="$1" + local counters + read -r run passed failed < "$COUNTER_FILE" + case "$pos" in + 1) run=$((run + 1)) ;; + 2) passed=$((passed + 1)) ;; + 3) failed=$((failed + 1)) ;; + esac + echo "$run $passed $failed" > "$COUNTER_FILE" +} + +# Get counter values +get_counters() { + cat "$COUNTER_FILE" +} + +# Run a test case +# Args: test_name expected_kept expected_deleted env_vars +# Reads mock snapshot data from stdin +run_test() { + local test_name="$1" + local expected_kept="$2" + local expected_deleted="$3" + shift 3 + local env_vars="$*" + + inc_counter 1 # TESTS_RUN++ + echo -e "${BLUE}TEST:${NC} $test_name" + + # Capture stdin (mock data) and pass to prune script + local mock_data + mock_data=$(cat) + + # Run prune script with mock data on stdin + local output + output=$(echo "$mock_data" | env NOW_OVERRIDE="$NOW" $env_vars "$PRUNE_SCRIPT" --test --quiet 2>&1) + + # Extract results + local result_line + result_line=$(echo "$output" | grep "^RESULT:" || echo "RESULT:kept=0,deleted=0") + local actual_kept + actual_kept=$(echo "$result_line" | sed 's/.*kept=\([0-9]*\).*/\1/') + local actual_deleted + actual_deleted=$(echo "$result_line" | sed 's/.*deleted=\([0-9]*\).*/\1/') + + # Compare + if [[ "$actual_kept" == "$expected_kept" ]] && [[ "$actual_deleted" == "$expected_deleted" ]]; then + echo -e " ${GREEN}PASS${NC} (kept=$actual_kept, deleted=$actual_deleted)" + inc_counter 2 # TESTS_PASSED++ + return 0 + else + echo -e " ${RED}FAIL${NC}" + echo -e " Expected: kept=$expected_kept, deleted=$expected_deleted" + echo -e " Actual: kept=$actual_kept, deleted=$actual_deleted" + inc_counter 3 # TESTS_FAILED++ + return 1 + fi +} + +# Print section header +section() { + echo "" + echo -e "${YELLOW}=== $1 ===${NC}" +} + +# Verify prune script exists +if [[ ! -x "$PRUNE_SCRIPT" ]]; then + chmod +x "$PRUNE_SCRIPT" +fi + +echo -e "${GREEN}zfs-snap-prune Test Suite${NC}" +echo "=========================" +echo "Using NOW=$NOW ($(date -d "@$NOW" '+%Y-%m-%d %H:%M:%S'))" +echo "Default policy: KEEP_COUNT=20, MAX_AGE_DAYS=180" + +############################################################################### +section "Basic Cases" +############################################################################### + +# Test 1: Empty list +echo -n "" | run_test "Empty snapshot list" 0 0 + +# Test 2: Single snapshot +make_snap "test1" 5 | run_test "Single snapshot (recent)" 1 0 + +# Test 3: Under keep count - all recent +make_snaps "recent" 10 1 | run_test "10 snapshots, all recent" 10 0 + +# Test 4: Exactly at keep count +make_snaps "exact" 20 1 | run_test "Exactly 20 snapshots" 20 0 + +############################################################################### +section "Over Keep Count - Age Matters" +############################################################################### + +# Test 5: 25 snapshots, all recent (within 180 days) +# Should keep all - none old enough to delete +make_snaps "recent" 25 1 | run_test "25 snapshots, all recent (<180 days)" 25 0 + +# Test 6: 25 snapshots, 5 are old (>180 days) +# First 20 (most recent) kept by count, 5 oldest are >180 days old, so deleted +{ + make_snaps "old" 5 200 # oldest first (200-204 days ago) + make_snaps "recent" 20 1 # newest last (1-20 days ago) +} | run_test "25 snapshots, 5 old (>180 days) - delete 5" 20 5 + +# Test 7: 30 snapshots, 10 beyond limit but only 5 old enough +{ + make_snaps "old" 5 200 # 200-204 days old - delete these + make_snaps "medium" 5 100 # 100-104 days old - not old enough + make_snaps "recent" 20 1 # 1-20 days old +} | run_test "30 snapshots, 5 medium age, 5 old - delete 5" 25 5 + +############################################################################### +section "Genesis Protection" +############################################################################### + +# Test 8: Genesis at position 21, old - should NOT be deleted +{ + make_snap "genesis" 365 # oldest: 1 year old + make_snaps "recent" 20 1 # newest: 1-20 days ago +} | run_test "Genesis at position 21 (old) - protected" 21 0 + +# Test 9: Genesis at position 25, with other old snapshots +{ + make_snap "genesis" 365 # oldest: protected + make_snaps "old" 4 200 # 200-203 days old - should be deleted + make_snaps "recent" 20 1 # 1-20 days old +} | run_test "Genesis at position 25 with 4 old - delete 4, keep genesis" 21 4 + +# Test 10: Genesis within keep count (20 total snapshots) +{ + make_snap "genesis" 365 # oldest + make_snaps "more" 9 15 # 15-23 days ago + make_snaps "recent" 10 1 # 1-10 days ago +} | run_test "Genesis at position 11 (within keep count)" 20 0 + +############################################################################### +section "Custom Configuration" +############################################################################### + +# Test 11: Custom KEEP_COUNT=5 +make_snaps "test" 10 200 | \ + run_test "KEEP_COUNT=5, 10 old snapshots - delete 5" 5 5 KEEP_COUNT=5 + +# Test 12: Custom MAX_AGE_DAYS=30 +{ + make_snaps "medium" 5 50 # 50-54 days old - now considered old with MAX_AGE=30 + make_snaps "recent" 20 1 # 1-20 days ago +} | run_test "MAX_AGE_DAYS=30, 5 snapshots >30 days - delete 5" 20 5 MAX_AGE_DAYS=30 + +# Test 13: Very short retention +make_snaps "test" 15 10 | \ + run_test "KEEP_COUNT=3, MAX_AGE=7, 15 snaps (10+ days old) - delete 12" 3 12 KEEP_COUNT=3 MAX_AGE_DAYS=7 + +# Test 14: Relaxed retention - nothing deleted +make_snaps "test" 50 1 | \ + run_test "KEEP_COUNT=100 - keep all 50" 50 0 KEEP_COUNT=100 + +############################################################################### +section "Edge Cases" +############################################################################### + +# Test 15: Snapshot exactly at MAX_AGE boundary (180 days) - should be kept +{ + make_snap "boundary" 180 # Exactly 180 days - >= cutoff, kept + make_snaps "recent" 20 1 # 1-20 days ago +} | run_test "1 snapshot exactly at 180 day boundary - keep" 21 0 + +# Test 16: Snapshot just over MAX_AGE boundary (181 days) - should be deleted +{ + make_snap "over" 181 # 181 days - just over, should be deleted + make_snaps "recent" 20 1 # 1-20 days ago +} | run_test "1 snapshot at 181 days - delete" 20 1 + +# Test 17: Mixed boundary - some at 180, some at 181 +{ + make_snap "over2" 182 # deleted + make_snap "over1" 181 # deleted + make_snap "boundary" 180 # kept (exactly at cutoff) + make_snaps "recent" 20 1 # 1-20 days ago +} | run_test "2 over boundary, 1 at boundary - delete 2" 21 2 + +# Test 18: Mixed naming patterns (ordered oldest to newest) +{ + make_snap "genesis" 365 + make_snap "before-upgrade" 20 + make_snap "manual_backup" 15 + make_snap "pre-pacman_2025-01-10" 10 + make_snap "pre-pacman_2025-01-15" 5 +} | run_test "Mixed snapshot names (5 total)" 5 0 + +# Test 19: Large number of snapshots +{ + make_snaps "old" 100 200 # 200-299 days old + make_snaps "recent" 20 1 # 1-20 days ago +} | run_test "120 snapshots, 100 old - delete 100" 20 100 + +############################################################################### +section "Realistic Scenarios" +############################################################################### + +# Test 20: One year of weekly pacman updates + genesis +# 52 snapshots (one per week) + genesis +# Ordered oldest first: genesis (365 days), then week_51 (357 days), ..., week_0 (0 days) +{ + make_snap "genesis" 365 + for ((week=51; week>=0; week--)); do + make_snap "pre-pacman_week_${week}" $((week * 7)) + done +} | run_test "1 year of weekly updates (52) + genesis" 27 26 +# Analysis (after tac, newest first): +# Position 1-20: week_0 through week_19 (0-133 days) - kept by count +# Position 21-26: week_20 through week_25 (140-175 days) - kept by age (<180) +# Position 27-52: week_26 through week_51 (182-357 days) - deleted (>180) +# Position 53: genesis (365 days) - protected +# Kept: 20 + 6 + 1 = 27, Deleted: 26 + +# Test 21: Fresh install with only genesis +make_snap "genesis" 1 | run_test "Fresh install - only genesis" 1 0 + +# Test 22: Burst of manual snapshots before big change +{ + make_snap "genesis" 30 + make_snap "before-nvidia" 20 + make_snap "before-DE-change" 19 + make_snaps "pre-pacman" 18 1 +} | run_test "20 snaps + genesis (30 days old)" 21 0 + +############################################################################### +section "Results" +############################################################################### + +# Read final counters +read -r TESTS_RUN TESTS_PASSED TESTS_FAILED < "$COUNTER_FILE" + +echo "" +echo "=========================" +echo -e "Tests run: $TESTS_RUN" +echo -e "Tests passed: ${GREEN}$TESTS_PASSED${NC}" +echo -e "Tests failed: ${RED}$TESTS_FAILED${NC}" +echo "" + +if [[ $TESTS_FAILED -eq 0 ]]; then + echo -e "${GREEN}All tests passed!${NC}" + exit 0 +else + echo -e "${RED}Some tests failed.${NC}" + exit 1 +fi |
