Skip to content

Instantly share code, notes, and snippets.

@SmartFinn
Last active December 4, 2023 19:19
Show Gist options
  • Save SmartFinn/013dc2670f6605826acfae8e25c11178 to your computer and use it in GitHub Desktop.
Save SmartFinn/013dc2670f6605826acfae8e25c11178 to your computer and use it in GitHub Desktop.
A script for creating/rotating snapshots of LVM volumes via (ana)cron
#!/usr/bin/env bash
#
# Creating and rotating snapshots of LVM volumes
#
# https://gist.github.com/SmartFinn/013dc2670f6605826acfae8e25c11178
#
# Copyright (c) 2023 Serhii Yeremenko (https://github.com/SmartFinn)
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
set -eu -o pipefail
readonly PROGNAME="$(basename "${BASH_SOURCE[0]}" .sh)"
readonly VERSION="0.0.3"
msg() {
printf "%s: %s\n" "$PROGNAME" "$*"
}
err() {
printf "%s: Error: %s\n" "$PROGNAME" "$*" >&2
}
usage() {
cat <<- EOF
usage:
$PROGNAME [options] LogicalVolumePath...
OPTIONS:
-l LogicalExtentsNumber[%{VG|PVS|FREE|ORIGIN}]
Default value: 100%ORIGIN
Gives the number of logical extents to allocate for
the new logical volume.
See lvcreate(8) for details.
-L LogicalVolumeSize[b|B|s|S|k|K|m|M|g|G|t|T|p|P|e|E]
Gives the size to allocate for the new logical volume.
Default unit is megabytes.
See lvcreate(8) for details.
-N NumberOfSnapshots
Default value: 2
Specify how many of the last snapshots to keep.
-D Delete all snapshots of the specified logical volume.
-V print $PROGNAME version and exit
-h show this help
EOF
exit "${1:-0}"
}
create_snapshot() {
local snap_name="${1?snap_name is required}"
local lv_path="${2?lv_path is required}"
[ "$DO_NOT_CREATE" -eq 0 ] || return 0
sync
lvcreate "${SNAP_SIZE[@]}" --snapshot --name "$snap_name" "$lv_path"
}
clear_snapshots() {
local lv_name="${1?lv_name is required}"
local snap_prefix="${2?no prefix is specified}"
local -i snap_count
local -i excess_snaps
local -a snapshots=()
read -ra snapshots < <(lvs --noheadings -o lv_path -S "origin=$lv_name" \
-S 'lv_attr=~^s' -S "lv_name=~^$snap_prefix" | xargs)
snap_count=${#snapshots[@]}
excess_snaps=$(( snap_count - KEEP ))
for (( i=0; i < excess_snaps; i++ )); do
[ -n "${snapshots[$i]}" ] || continue
lvremove --force "${snapshots[$i]}"
done
}
declare -a SNAP_SIZE=(-l 100%ORIGIN)
declare -i KEEP=2
declare -i DO_NOT_CREATE="${DO_NOT_CREATE:-0}"
while getopts ":DL:l:N:Vh" opt; do
case "$opt" in
D ) DO_NOT_CREATE=1
KEEP=0
;;
L | \
l ) SNAP_SIZE=("-$opt" "$OPTARG") ;;
N ) KEEP="$OPTARG" ;;
V ) printf "%s %s\n" "$PROGNAME" "$VERSION"
exit 0
;;
h ) usage 0 ;;
: ) err "option requires an argument -- '-$OPTARG'"
usage 2
;;
\?) err "illegal option -- '-$OPTARG'"
usage 2
;;
esac
done
shift $((OPTIND-1))
# Return an error if positional parameters are not found
if [ -z "${1:-}" ]; then
err "no logical volume is specified"
usage 2
fi
for lvol in "$@"; do
unset LVM2_LV_NAME LVM2_LV_PATH
eval "$(lvs --noheadings --nameprefixes -o lv_name,lv_path "$lvol")"
[ -b "$LVM2_LV_PATH" ] || exit 1
timestamp="$(date +%Y%m%d_%H%M%S)"
snap_prefix="${LVM2_LV_NAME}_snap_"
snap_name="${snap_prefix}${timestamp}"
create_snapshot "$snap_name" "$LVM2_LV_PATH"
clear_snapshots "$LVM2_LV_NAME" "$snap_prefix"
done
@SmartFinn
Copy link
Author

@KalyaSc Thanks for pointing that. I have swapped the options.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment