diff options
| author | Benjamin Linskey | 2025-07-30 14:11:27 -0400 |
|---|---|---|
| committer | Benjamin Linskey | 2025-07-30 14:11:27 -0400 |
| commit | c018affc0098d4a74b215f91296e50be089ec29f (patch) | |
| tree | 3b07eb9bec3f01eed100ae9232ae071a136d68b4 | |
| download | zfs-snapshots-c018affc0098d4a74b215f91296e50be089ec29f.tar.gz | |
Add script and sample crontab
| -rw-r--r-- | example.cron | 16 | ||||
| -rwxr-xr-x | snapshots.sh | 167 |
2 files changed, 183 insertions, 0 deletions
diff --git a/example.cron b/example.cron new file mode 100644 index 0000000..853aad3 --- /dev/null +++ b/example.cron @@ -0,0 +1,16 @@ +# Example crontab for /etc/cron.d. See crontab(5). + +SHELL=/bin/sh + +# Make recursive snapshots of zroot and tank/foo, keeping 24 +# hourly, seven daily, eight weekly, 24 monthly, and unlimited yearly +# snapshots. +@hourly root snapshots.sh -c -p -t hourly -k 24 -r zroot tank/foo +@daily root snapshots.sh -c -p -t daily -k 7 -r zroot tank/foo +@weekly root snapshots.sh -c -p -t weekly -k 8 -r zroot tank/foo +@monthly root snapshots.sh -c -p -t monthly -k 24 -r zroot tank/foo +@yearly root snapshots.sh -c -t yearly -r zroot tank/foo + +# Make a non-recursive snapshot of tank/bar daily at 5:00, +# preserving the three most recent snapshots. +0 5 * * * root snapshots.sh -c -p -t my-custom-tag -k 3 tank/bar diff --git a/snapshots.sh b/snapshots.sh new file mode 100755 index 0000000..e4faaf0 --- /dev/null +++ b/snapshots.sh @@ -0,0 +1,167 @@ +#!/bin/sh + +# Manages ZFS snapshots. +# +# At least one of the following options must be provided to specify an +# operation to perform: +# +# -c create snapshot(s) +# -p prune snapshots +# -l list snapshots +# +# If the -c or -p option is specified, at least one dataset must be specified +# as well. +# +# If the -r option is specified, child datasets of the specified dataset(s) +# will be recursively created, deleted, or listed. +# +# If the -c or -p option is specified, the -t option must be provided to +# specify a tag. +# +# If the -p option is specified, the -k option must be provided to specify the +# number of snapshots to keep for each dataset (including any newly created +# snapshots). +# +# Usage: snapshots.sh [-cplrnvh] [-t tag] [-k num] [dataset ...]" +# +# -c create snapshot(s) (requires -t) +# -p prune snapshots (requires -t and -k) +# -l list snapshots +# -r recursively create or delete snapshots +# -n dry run: print commands that would be executed, but do not +# actually modify data +# -v verbose mode +# -h print usage +# -k num keep *num* snapshots for each dataset, including any newly +# created snapshots +# -t tag use *tag* as the snapshot name prefix + +set -e + +usage() { + printf "usage: %s [-cplrnvh] [-t tag] [-k num] [dataset ...]\n" "$0" +} + +tag= +keep= +recursive= +dry_run= +verbose= +create= +prune= +list= + +while getopts t:k:cplrknvh name; do + case $name in + t) tag="$OPTARG";; + k) keep="$OPTARG";; + c) create=1;; + p) prune=1;; + l) list=1;; + r) recursive=1;; + n) dry_run=1;; + v) verbose=1;; + h) usage + exit 0;; + ?) usage + exit 2;; + esac +done +shift $((OPTIND - 1)) + +if [ -z "$create" ] && [ -z "$prune" ] && [ -z "$list" ]; then + printf "At least one of -c, -p, and -l must be specified.\n" + usage + exit 1 +fi + +if [ "$#" -eq 0 ] && { [ -n "$create" ] || [ -n "$prune" ]; }; then + printf "At least one dataset must be specified\n" + usage + exit 1 +fi + +if { [ -n "$create" ] || [ -n "$prune" ]; } && [ -z "$tag" ]; then + printf "Missing -t option\n" + usage + exit 1 +fi + +if [ -n "$prune" ] && [ -z "$keep" ]; then + printf "Missing -k option\n" + usage + exit 1 +fi + +if [ -z "$prune" ] && [ -n "$keep" ]; then + printf "\-k option is only valid with -p\n" + usage + exit 1 +fi + +create_cmd='zfs snapshot' +if [ -n "$recursive" ]; then + create_cmd="$create_cmd -r" +fi +readonly create_cmd + +destroy_cmd='zfs destroy' +if [ -n "$recursive" ]; then + destroy_cmd="$destroy_cmd -R" +fi +readonly destroy_cmd + +# Create snapshots. +if [ -n "$create" ]; then + for dataset in "$@"; do + # FreeBSD's date -I option uses a "+00:00" suffix rather than "Z", and + # the + character is illegal in snapshot names, so we have to specify + # the format manually. + cmd="$create_cmd ${dataset}@${tag}-$(date -z utc +%Y-%m-%dT%H:%M:%SZ)" + + if [ -n "$dry_run" ] || [ -n "$verbose" ]; then + printf "%s\n" "$cmd" + fi + + if [ -z "$dry_run" ]; then + $cmd + fi + done +fi + +# Prune snapshots. +if [ -n "$prune" ]; then + if [ -n "$prune_only" ]; then + keep=$((keep + 1)) + fi + + for dataset in "$@"; do + snapshots=$(zfs list -t snapshot -o name -S name -H "$dataset") + to_delete=$(printf "%s\n" "$snapshots" | grep "@${tag}-" | tail -n +"$((keep + 1))") + for s in $to_delete; do + cmd="$destroy_cmd $s" + if [ -n "$dry_run" ] || [ -n "$verbose" ]; then + printf "%s\n" "$cmd" + fi + + if [ -z "$dry_run" ]; then + $cmd + fi + done + done +fi + +# List snapshots. +if [ -n "$list" ]; then + cmd="zfs list" + if [ -n "$recursive" ]; then + cmd="$cmd -r" + fi + snapshots=$($cmd -t snapshot -o name -s name -H "$@") + + if [ -n "$tag" ]; then + printf "%s\n" "$snapshots" | grep "@${tag}-" + else + printf "%s\n" "$snapshots" + fi +fi |