diff options
| author | Craig Jennings <c@cjennings.net> | 2026-01-18 00:41:57 -0600 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-01-18 00:41:57 -0600 |
| commit | ef9d6b33df9948a3a4847696cc0c5aaeca8f8596 (patch) | |
| tree | 37e10cbb834ba7c509086a2ffb1dfbddd630d2c8 /dotfiles/system/.local/bin/zfssnapshot | |
| parent | 8e2b8c3079220dbeae8a64d0370004da08a346c2 (diff) | |
feat(dotfiles): add zfssnapshot and zfsrollback utilities
- zfssnapshot: create dated snapshots across all pools with description
- zfsrollback: fzf-based snapshot selection with multi-dataset rollback
- Both require root and validate input/show appropriate warnings
Diffstat (limited to 'dotfiles/system/.local/bin/zfssnapshot')
| -rwxr-xr-x | dotfiles/system/.local/bin/zfssnapshot | 103 |
1 files changed, 103 insertions, 0 deletions
diff --git a/dotfiles/system/.local/bin/zfssnapshot b/dotfiles/system/.local/bin/zfssnapshot new file mode 100755 index 0000000..b715722 --- /dev/null +++ b/dotfiles/system/.local/bin/zfssnapshot @@ -0,0 +1,103 @@ +#!/bin/env bash +# Craig Jennings <c@cjennings.net> +# Create a ZFS snapshot across all datasets with a dated, descriptive name. + +set -euo pipefail + +# Usage info +show_help() { + cat << EOF +Usage: ${0##*/} [-h] [DESCRIPTION] +Create a ZFS snapshot across all datasets. + + -h display this help and exit + DESCRIPTION short description for the snapshot (optional, will prompt if omitted) + +Snapshot names are formatted as: YYYY-MM-DD-description +Only alphanumeric characters, hyphens, and underscores are allowed in descriptions. +Spaces are converted to hyphens automatically. + +Examples: + ${0##*/} before-upgrade + ${0##*/} "pre system update" + ${0##*/} # prompts for description +EOF +} + +# Check for ZFS +if ! command -v zfs &> /dev/null; then + echo "Error: zfs command not found. Is ZFS installed?" + exit 1 +fi + +# Check for root/sudo +if [ "$EUID" -ne 0 ]; then + echo "Error: This script must be run as root (use sudo)" + exit 1 +fi + +# Parse arguments +while getopts ":h" opt; do + case ${opt} in + h) + show_help + exit 0 + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + show_help + exit 1 + ;; + esac +done +shift $((OPTIND - 1)) + +# Get description from argument or prompt +if [ $# -ge 1 ]; then + description="$*" +else + read -r -p "Enter snapshot description: " description + if [ -z "$description" ]; then + echo "Error: Description cannot be empty" + exit 1 + fi +fi + +# Sanitize description: convert spaces to hyphens, lowercase +description=$(echo "$description" | tr '[:upper:]' '[:lower:]' | tr ' ' '-') + +# Validate description: only allow alphanumeric, hyphens, underscores +if [[ ! "$description" =~ ^[a-z0-9_-]+$ ]]; then + echo "Error: Description contains invalid characters" + echo "Only letters, numbers, hyphens, and underscores are allowed" + echo "Sanitized input was: $description" + exit 1 +fi + +# Create snapshot name with date prefix +date_prefix=$(date +%Y-%m-%d) +snapshot_name="${date_prefix}-${description}" + +# Get all pools +pools=$(zpool list -H -o name) + +if [ -z "$pools" ]; then + echo "Error: No ZFS pools found" + exit 1 +fi + +echo "Creating snapshots with name: @${snapshot_name}" +echo "" + +# Create recursive snapshots on each pool +for pool in $pools; do + echo "Snapshotting pool: $pool" + if zfs snapshot -r "${pool}@${snapshot_name}"; then + echo " ✓ Created ${pool}@${snapshot_name} (recursive)" + else + echo " ✗ Failed to snapshot $pool" + fi +done + +echo "" +echo "Snapshot complete. Verify with: zfs list -t snapshot | grep $snapshot_name" |
