From c31fd020925fd7b2c3c076896e1bfb9724850bc2 Mon Sep 17 00:00:00 2001 From: david Date: Sun, 29 Oct 2023 22:54:21 +0100 Subject: [PATCH] more progress on the new version of btrfs-snapshots.sh, still untested though --- btrfs-snapshots.sh | 135 ++++++++++++++++++++++++++++++++------------- lib.sh | 24 ++++++++ 2 files changed, 121 insertions(+), 38 deletions(-) diff --git a/btrfs-snapshots.sh b/btrfs-snapshots.sh index 17d74cd..296a2eb 100755 --- a/btrfs-snapshots.sh +++ b/btrfs-snapshots.sh @@ -35,15 +35,18 @@ LIBRARIES="lib.sh" DEPENDENCIES="basename dirname readlink ln mkdir rm ls date btrfs" REQUIRE_ROOT=1 -SUBVOLUMES="/" -DESTINATION_ROOT=/srv/snapshots +SNAP_FOLDER=".snapshots" +SNAP_PREFIX="snapshot-" +SNAP_TIMESTAMP=$(date +%Y%m%d%H%M) #add %S if you want sub-minute snapshots +SNAP_NAME="$SNAP_PREFIX$SNAP_TIMESTAMP" -PREFIX="snapshot-" -TIMESTAMP=$(date +%Y%m%d%H%M) #add %S if you want sub-minute snapshots -NAME="$PREFIX$TIMESTAMP" +# Build the Btrfs command +BTRFS_BINARY="$(command -v btrfs)" +BTRFS_OPTIONS="-q" +BTRFS_COMMAND="$BTRFS_BINARY $BTRFS_OPTIONS" # How many snapshots should be kept? -MAX_SNAPSHOTS=32 +SNAPSHOTS=128 ## @@ -52,14 +55,22 @@ MAX_SNAPSHOTS=32 # Print help information function print_help() { - printf "%s\n\n%s\n%b\n\n%s\n %-15s %s\n %-15s %s\n %-15s %s\n %-15s %s\n" \ + printf "%s\n\n%s\n%b\n\n%s\n %-15s %s\n %-15s %s\n %-15s %s\n %-15s %s\n %-15s %s\n" \ "$DESCRIPTION" "Usage:" "$LIB_BOLD$EXECUTABLE $LIB_CLEAR" "Options:" \ + "-s, --snapshots " "Override how many snapshots to keep (default: 128)" \ "-h, --help" "Print help screen and exit" \ "-i, --info" "Print script information and exit" \ "-v, --verbose" "More verbose output" \ "-q, --quiet" "No output except errors (overrides -v)" } +# Verify that input is a Btrfs subvolume +function is_subvolume() { + if ! $($BTRFS_COMMAND subvolume show $1 >/dev/null 2>&1); then + return 1 + fi +} + ## ## Preflight @@ -96,40 +107,88 @@ fi ## ## Liftoff ## -exit 0 -# take the snapshots -for SUBVOLUME in $SUBVOLUMES; do - if [ -z ${SUBVOLUME#/} ]; then - SNAPSHOT_PATH="$DESTINATION_ROOT" - else - PATH="$DESTINATION_ROOT/${SUBVOLUME#/}" - fi - if [ ! -d $SNAPSHOT_PATH ]; then - echo "warning: snapshot path ($SNAPSHOT_PATH) does not yet exist. creating." - mkdir -p $SNAPSHOT_PATH - fi - if [ -d $SNAPSHOT_PATH/$NAME ]; then - echo "warning: snapshots with same name already exists. skipping." - else - #TODO: handle failed snapshots and failed link creations - btrfs -q subvolume snapshot -r $SUBVOLUME $SNAPSHOT_PATH/$NAME - if [ -h $SNAPSHOT_PATH/latest ]; then - rm $SNAPSHOT_PATH/latest - fi - ln -sf $SNAPSHOT_PATH/$NAME $SNAPSHOT_PATH/latest - fi + +while [[ "$1" =~ ^- && ! "$1" == "--" ]]; do + case $1 in + -s|--snapshots) + shift + if lib_is_int $1; then + SNAPSHOTS=$1 + else + lib_print "!Input for --snapshots must be an intager" + exit 1 + fi + ;; + -h|--help) + H=1 + ;; + -i|--info) + I=1 + ;; + -v|--verbose) + V=1 + ;; + -q|--quiet) + Q=1 + ;; + *) + lib_print "!Unknown option [$1]" + lib_print "Try --help or -h for available options" + exit 1 + ;; + esac + shift done -# Delete old snapshots -SNAPSHOTS=$(ls -r $SNAPSHOT_PATH | grep $PREFIX) -i=0 -for SNAPSHOT in $SNAPSHOTS; do - if [ $i -ge $MAX_SNAPSHOTS ]; then - #TODO: handle failed snapshot deletions - btrfs -q subvolume delete $SNAPSHOT_PATH/$SNAPSHOT +if [[ "$1" == '--' ]]; then + shift +fi + +if [[ $I -eq 1 ]]; then + lib_print_info + exit 0 +elif [[ $H -eq 1 ]]; then + print_help + exit 0 +fi + +if [[ -z "$1" ]]; then + print_help + exit 0 +else + if is_subvolume "$1"; then + SNAP_FOLDER="${1%/}/$SNAP_FOLDER" + # Create $SNAP_FOLDER if it does not exist + if [[ ! -d "$SNAP_FOLDER" ]]; then + lib_print "?Creating snapshot folder because it does not yet exist [$SNAP_FOLDER]" + mkdir "$SNAP_FOLDER" fi - i=$((i+1)) -done + if [[ -d "$SNAP_FOLDER/$SNAP_NAME" ]]; then + lib_print "?Skipping snapshot because it already exists. [$SNAP_NAME]" + else + # Take the snapshot + $BTRFS_COMMAND subvolume snapshot -r "${1%/}" "$SNAP_FOLDER/$SNAP_NAME" + if [ -h "$SNAP_FOLDER/latest" ]; then + rm "$SNAP_FOLDER/latest" + fi + ln -sf "$SNAP_FOLDER/$SNAP_NAME" "$SNAP_FOLDER/latest" + # Delete old snapshots + SNAPS=$(ls -r $SNAP_FOLDER | grep $SNAP_PREFIX) + i=0 + for SNAP in $SNAPS; do + if [[ $i -ge $SNAPSHOTS ]]; then + #TODO: handle failed snapshot deletions + $BTRFS_COMMAND subvolume delete "$SNAP_FOLDER/$SNAP" + lib_print "Deleted old snapshot [$SNAP_FOLDER/$SNAP]" + fi + i=$((i+1)) + done + fi + else + lib_print "!Given path does not appear to be a Btrfs subvolume [$1]" + exit 1 + fi +fi ## diff --git a/lib.sh b/lib.sh index 6dcc998..1a8bb0a 100644 --- a/lib.sh +++ b/lib.sh @@ -142,3 +142,27 @@ function lib_healthchecks() { curl -fsS -m 10 --retry 5 -o /dev/null "$1" fi } + +# Verify if input is an intager +function lib_is_int { + local INT_EXPR='^[0-9]+$' + if ! [[ $1 =~ $INT_EXPR ]]; then + return 1 + fi +} + +# Verify if input is alphanumeric +function lib_is_alnum { + local ALNUM_EXPR='^[a-zA-Z0-9]+$' + if ! [[ $1 =~ $ALNUM_EXPR ]]; then + return 1 + fi +} + +# Verify if input looks like an URL +function lib_is_url { + local URL_EXPR='^https?://.+$' + if ! [[ $1 =~ $URL_EXPR ]]; then + return 1 + fi +}