From c38d25a3a5467b0f62de398c3931b393c5e862b4 Mon Sep 17 00:00:00 2001 From: Jarno Rankinen Date: Sat, 1 Aug 2020 09:56:16 +0300 Subject: [PATCH 01/10] Created snapsh Created snapsh with license information --- snapsh | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100755 snapsh diff --git a/snapsh b/snapsh new file mode 100755 index 0000000..f760d7a --- /dev/null +++ b/snapsh @@ -0,0 +1,19 @@ +#!/bin/bash + +## snapsh +## Copyright (C) 2020 Jarno Rankinen +## +## This program is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program. If not, see . + + -- 2.40.1 From 187673cbd40dd4d3f8c8c7f7d68320f64d4b3c16 Mon Sep 17 00:00:00 2001 From: Jarno Rankinen Date: Sat, 1 Aug 2020 10:04:12 +0300 Subject: [PATCH 02/10] Help text Display help text when no arguments are given --- snapsh | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/snapsh b/snapsh index f760d7a..14a6852 100755 --- a/snapsh +++ b/snapsh @@ -16,4 +16,17 @@ ## You should have received a copy of the GNU General Public License ## along with this program. If not, see . +help() { + printf "Usage: + snapsh [OPTIONS] + Options: +Exit codes: + 2 - Invalid arguments" +} + +# If no options are given, display help +if [[ "$#" -eq 0 ]]; then + help + exit 2 +fi -- 2.40.1 From dec51cee7626b7149b3b9e86ff0d0cf3ca6b601c Mon Sep 17 00:00:00 2001 From: Jarno Rankinen Date: Sat, 1 Aug 2020 10:24:49 +0300 Subject: [PATCH 03/10] Added -h, --help option --- snapsh | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/snapsh b/snapsh index 14a6852..9274657 100755 --- a/snapsh +++ b/snapsh @@ -19,14 +19,42 @@ help() { printf "Usage: snapsh [OPTIONS] - Options: + +Options: + -h, --help Display this help message Exit codes: - 2 - Invalid arguments" + 2 - Invalid options\n" } + + + # If no options are given, display help if [[ "$#" -eq 0 ]]; then help exit 2 fi + + +# Options parsing: +OPTIONS=$(getopt -a -n snapsh -o h --long help -- "$@") + +# Invalid options (getopt returns nonzero) +if [[ "$?" -ne 0 ]]; then + printf "Error Parsing options\n" + help + exit 2 +fi + +#eval set -- "$OPTIONS" +while true; + do + case "$1" in + -h | --help) + help + exit 0 + ;; + esac + done + -- 2.40.1 From 26a96606ae9f353c83314a081405da2133987924 Mon Sep 17 00:00:00 2001 From: Jarno Rankinen Date: Sat, 1 Aug 2020 12:52:59 +0300 Subject: [PATCH 04/10] Snapshot implemting started --- snapsh | 72 +++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 11 deletions(-) diff --git a/snapsh b/snapsh index 9274657..33a8bbb 100755 --- a/snapsh +++ b/snapsh @@ -16,6 +16,17 @@ ## You should have received a copy of the GNU General Public License ## along with this program. If not, see . +# Environment set up: + +TOPLEVEL="/root/btrfs-toplevel" +SNAPSHOTS_LOCATION="/root/btrfs-toplevel/test" + +BTRFS_EXECUTABLE=$(which btrfs) +TIMESTAMP=$(date +%Y.%m.%d-%H:%M:%S) +SUBVOLUME="root" + + + help() { printf "Usage: snapsh [OPTIONS] @@ -24,11 +35,43 @@ Options: -h, --help Display this help message Exit codes: - 2 - Invalid options\n" + 2 - Invalid options + 3 - Error creating subvolume\n" } +snapshot() { + + EXIT_CODE=0 + root_check + + # Check that the subvolume storing snapshots exists + if [[ ! -d ${SNAPSHOTS_LOCATION} ]]; then + printf "Subvolume ${SNAPSHOTS_LOCATION} does not exist. Create it now?\n" + read -p "y/n: " + + if [[ "${REPLY}" == "y" ]]; then + ${BTRFS_EXECUTABLE} subvolume create ${SNAPSHOTS_LOCATION} + else + EXIT_CODE=3 + fi + fi + + exit ${EXIT_CODE} +} + + + +# Check for root permissions +root_check() { + if [[ "$UID" -ne 0 ]]; then + printf "This option needs root permission.\n" + exit 3 + fi +} + + # If no options are given, display help if [[ "$#" -eq 0 ]]; then @@ -38,7 +81,7 @@ fi # Options parsing: -OPTIONS=$(getopt -a -n snapsh -o h --long help -- "$@") +OPTIONS=$(getopt -a -n snapsh -o hs: --long help,snapshot: -- "$@") # Invalid options (getopt returns nonzero) if [[ "$?" -ne 0 ]]; then @@ -48,13 +91,20 @@ if [[ "$?" -ne 0 ]]; then fi #eval set -- "$OPTIONS" -while true; - do - case "$1" in - -h | --help) - help - exit 0 - ;; - esac - done +while true; do + case "$1" in + + -h | --help) + help + shift + exit 0 + ;; + + -s | --snapshot) + snapshot + shift 2 + ;; + + esac +done -- 2.40.1 From 1974a733a1107d94e5a772f09825bb0cb1181da7 Mon Sep 17 00:00:00 2001 From: Jarno Rankinen Date: Sat, 1 Aug 2020 20:03:19 +0300 Subject: [PATCH 05/10] Snapshotting implemented Snapshotting (-s, --snapshot) implemented. --- snapsh | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/snapsh b/snapsh index 33a8bbb..87eb574 100755 --- a/snapsh +++ b/snapsh @@ -19,11 +19,12 @@ # Environment set up: TOPLEVEL="/root/btrfs-toplevel" -SNAPSHOTS_LOCATION="/root/btrfs-toplevel/test" +SNAPSHOTS_LOCATION="/root/btrfs-toplevel/snapshots" BTRFS_EXECUTABLE=$(which btrfs) TIMESTAMP=$(date +%Y.%m.%d-%H:%M:%S) SUBVOLUME="root" +DESCRIPTION="" @@ -36,7 +37,7 @@ Options: Exit codes: 2 - Invalid options - 3 - Error creating subvolume\n" + 3 - Target subvolume does not exist\n" } @@ -49,13 +50,37 @@ snapshot() { # Check that the subvolume storing snapshots exists if [[ ! -d ${SNAPSHOTS_LOCATION} ]]; then printf "Subvolume ${SNAPSHOTS_LOCATION} does not exist. Create it now?\n" - read -p "y/n: " + read -n 1 -p "y/n: " if [[ "${REPLY}" == "y" ]]; then ${BTRFS_EXECUTABLE} subvolume create ${SNAPSHOTS_LOCATION} + unset ${REPLY} else EXIT_CODE=3 fi + else + printf "Creating snapshot of subvolume ${SUBVOLUME} as ${SUBVOLUME}_snapshot_${TIMESTAMP}\n" + + # Create info file for listing snapshots + # Created first on the source subvolume, then deleted + printf "DATE=$(date) + SOURCE_SUBVOLUME=${SUBVOLUME} + DESCRIPTION=${DESCRIPTION} + TYPE=\"manual\"\n" > ${TOPLEVEL}/${SUBVOLUME}/.snapsh + + # Create readonly subvolume + ${BTRFS_EXECUTABLE} subvolume snapshot -r ${TOPLEVEL}/${SUBVOLUME} ${SNAPSHOTS_LOCATION}/${SUBVOLUME}_snapshot_${TIMESTAMP} + + # Delete info file from source + rm -f ${TOPLEVEL}/${SUBVOLUME}/.snapsh + + printf "Snapshot created! + ${SUBVOLUME}_snapshot_${TIMESTAMP} + DATE=$(date) + SOURCE_SUBVOLUME=${SUBVOLUME} + DESCRIPTION=${DESCRIPTION} + TYPE=\"manual\"\n" + fi exit ${EXIT_CODE} @@ -90,7 +115,7 @@ if [[ "$?" -ne 0 ]]; then exit 2 fi -#eval set -- "$OPTIONS" +eval set -- "$OPTIONS" while true; do case "$1" in @@ -101,6 +126,7 @@ while true; do ;; -s | --snapshot) + SUBVOLUME="$2" snapshot shift 2 ;; -- 2.40.1 From c7db86d95707c6773a01727766bb65038cb56791 Mon Sep 17 00:00:00 2001 From: Jarno Rankinen Date: Sat, 1 Aug 2020 20:39:56 +0300 Subject: [PATCH 06/10] Snapshot descriptions implenented Snapshots descriptions for snapshot listing implemented --- snapsh | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/snapsh b/snapsh index 87eb574..e3616d3 100755 --- a/snapsh +++ b/snapsh @@ -34,6 +34,10 @@ help() { Options: -h, --help Display this help message + -d STR, --description STR Add a description for the snapshot displayed in the + snapshots listing. Must be used before -s, e.g. + snapsh -d "A snapshot" -s root + -s SUBVOL, --snapshot SUBVOL Take a snapshot of subvolume named SUBVOL. Exit codes: 2 - Invalid options @@ -106,7 +110,7 @@ fi # Options parsing: -OPTIONS=$(getopt -a -n snapsh -o hs: --long help,snapshot: -- "$@") +OPTIONS=$(getopt -a -n snapsh -o hs:d: --long help,snapshot:,description: -- "$@") # Invalid options (getopt returns nonzero) if [[ "$?" -ne 0 ]]; then @@ -125,6 +129,11 @@ while true; do exit 0 ;; + -d | --description) + DESCRIPTION="$2" + shift 2 + ;; + -s | --snapshot) SUBVOLUME="$2" snapshot -- 2.40.1 From c2f4819a3859cc84fd0b3224e5a354b7832f4dea Mon Sep 17 00:00:00 2001 From: Jarno Rankinen Date: Sun, 2 Aug 2020 20:29:04 +0300 Subject: [PATCH 07/10] Snapshot listing implemented Snapshots listing (-l, --list) implemented. --- snapsh | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/snapsh b/snapsh index e3616d3..0d5739e 100755 --- a/snapsh +++ b/snapsh @@ -36,7 +36,7 @@ Options: -h, --help Display this help message -d STR, --description STR Add a description for the snapshot displayed in the snapshots listing. Must be used before -s, e.g. - snapsh -d "A snapshot" -s root + snapsh -d \"A snapshot\" -s root -s SUBVOL, --snapshot SUBVOL Take a snapshot of subvolume named SUBVOL. Exit codes: @@ -67,9 +67,9 @@ snapshot() { # Create info file for listing snapshots # Created first on the source subvolume, then deleted - printf "DATE=$(date) - SOURCE_SUBVOLUME=${SUBVOLUME} - DESCRIPTION=${DESCRIPTION} + printf "DATE=\"$(date)\" + SOURCE_SUBVOLUME=\"${SUBVOLUME}\" + DESCRIPTION=\"${DESCRIPTION}\" TYPE=\"manual\"\n" > ${TOPLEVEL}/${SUBVOLUME}/.snapsh # Create readonly subvolume @@ -92,6 +92,20 @@ snapshot() { +list() { + root_check + NUM=0 + printf "%6s %s %s %26s %s %s %6s %s %s\n" "Number" "|" "Time:" "|" "Source" "|" "Type" "|" "Description" + for snapshot in ${SNAPSHOTS_LOCATION}/*/; do + . ${snapshot}/.snapsh + printf "%8s %32s %8s %8s %s\n" "${NUM} |" "${DATE} |" "${SOURCE_SUBVOLUME} |" "${TYPE} |" "${DESCRIPTION}" + let NUM=NUM+1 + done + exit 0 +} + + + # Check for root permissions root_check() { if [[ "$UID" -ne 0 ]]; then @@ -110,7 +124,7 @@ fi # Options parsing: -OPTIONS=$(getopt -a -n snapsh -o hs:d: --long help,snapshot:,description: -- "$@") +OPTIONS=$(getopt -a -n snapsh -o hs:d:l --long help,snapshot:,description:,list -- "$@") # Invalid options (getopt returns nonzero) if [[ "$?" -ne 0 ]]; then @@ -140,6 +154,11 @@ while true; do shift 2 ;; + -l | --list) + list + shift + ;; + esac done -- 2.40.1 From be893f3ee5242cfe7361af77a5023771a880f3ca Mon Sep 17 00:00:00 2001 From: Jarno Rankinen Date: Sun, 2 Aug 2020 20:42:59 +0300 Subject: [PATCH 08/10] Updated helptext with list option --- snapsh | 1 + 1 file changed, 1 insertion(+) diff --git a/snapsh b/snapsh index 0d5739e..e634f43 100755 --- a/snapsh +++ b/snapsh @@ -38,6 +38,7 @@ Options: snapshots listing. Must be used before -s, e.g. snapsh -d \"A snapshot\" -s root -s SUBVOL, --snapshot SUBVOL Take a snapshot of subvolume named SUBVOL. + -l, --list List snapshots Exit codes: 2 - Invalid options -- 2.40.1 From 974b2225dced6e5b7b494740f5ea743c4787fcbf Mon Sep 17 00:00:00 2001 From: Jarno Rankinen Date: Sun, 2 Aug 2020 21:38:27 +0300 Subject: [PATCH 09/10] Snapshot removal implemented Snapshot removing implemented (-r, --remove) Updated helptext Variable expansion fixes --- snapsh | 58 ++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/snapsh b/snapsh index e634f43..e685614 100755 --- a/snapsh +++ b/snapsh @@ -25,6 +25,7 @@ BTRFS_EXECUTABLE=$(which btrfs) TIMESTAMP=$(date +%Y.%m.%d-%H:%M:%S) SUBVOLUME="root" DESCRIPTION="" +REMOVE_TARGET="" @@ -39,10 +40,8 @@ Options: snapsh -d \"A snapshot\" -s root -s SUBVOL, --snapshot SUBVOL Take a snapshot of subvolume named SUBVOL. -l, --list List snapshots - -Exit codes: - 2 - Invalid options - 3 - Target subvolume does not exist\n" + -r NUMBER, --remove NUMBER Remove snapshot NUMBER. See snapshot numbers with + snapsh -l\n" } @@ -61,7 +60,7 @@ snapshot() { ${BTRFS_EXECUTABLE} subvolume create ${SNAPSHOTS_LOCATION} unset ${REPLY} else - EXIT_CODE=3 + EXIT_CODE=1 fi else printf "Creating snapshot of subvolume ${SUBVOLUME} as ${SUBVOLUME}_snapshot_${TIMESTAMP}\n" @@ -95,7 +94,7 @@ snapshot() { list() { root_check - NUM=0 + NUM=1 printf "%6s %s %s %26s %s %s %6s %s %s\n" "Number" "|" "Time:" "|" "Source" "|" "Type" "|" "Description" for snapshot in ${SNAPSHOTS_LOCATION}/*/; do . ${snapshot}/.snapsh @@ -107,11 +106,43 @@ list() { +remove() { + root_check + SNAPSHOTS=(${SNAPSHOTS_LOCATION}/*/) + let INDEX=${REMOVE_TARGET}-1 + TARGET=${SNAPSHOTS[${INDEX}]} + . ${TARGET}/.snapsh + + if [[ "${INDEX}" -gt "${#SNAPSHOTS[@]}" ]]; then + printf "Snapshot number ${REMOVE_TARGET} does not exist.\n" + list + exit 1 + elif [[ "${REMOVE_TARGET}" -lt 1 ]]; then + printf "Number must be a positive integer.\n" + list + exit 1 + fi + + printf "Delete snapshot ${REMOVE_TARGET}: ${DATE}, subvolume ${SOURCE_SUBVOLUME}, ${DESCRIPTION} (y/n)? " + read -n 1 + if [[ "${REPLY}" == "y" ]]; then + printf "\n" + ${BTRFS_EXECUTABLE} property set ${TARGET} ro false + ${BTRFS_EXECUTABLE} subvolume delete ${TARGET} + exit 0 + else + printf "\nAborted by user.\n" + exit 1 + fi +} + + + # Check for root permissions root_check() { if [[ "$UID" -ne 0 ]]; then printf "This option needs root permission.\n" - exit 3 + exit 1 fi } @@ -120,18 +151,18 @@ root_check() { # If no options are given, display help if [[ "$#" -eq 0 ]]; then help - exit 2 + exit 0 fi # Options parsing: -OPTIONS=$(getopt -a -n snapsh -o hs:d:l --long help,snapshot:,description:,list -- "$@") +OPTIONS=$(getopt -a -n snapsh -o hs:d:lr: --long help,snapshot:,description:,list,remove: -- "$@") # Invalid options (getopt returns nonzero) if [[ "$?" -ne 0 ]]; then printf "Error Parsing options\n" help - exit 2 + exit 1 fi eval set -- "$OPTIONS" @@ -160,6 +191,13 @@ while true; do shift ;; + -r | --remove) + REMOVE_TARGET="$2" + remove + shift 2 + ;; + + esac done -- 2.40.1 From 8e4de6a074d08195d8ebdc1ea3315fdcc72a1ee2 Mon Sep 17 00:00:00 2001 From: Jarno Rankinen <50285623+0ranki@users.noreply.github.com> Date: Sun, 2 Aug 2020 21:47:27 +0300 Subject: [PATCH 10/10] Update README.md Added information for snapshot deletion. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0c00b14..6c89ef2 100644 --- a/README.md +++ b/README.md @@ -12,3 +12,4 @@ Btrfs snapshot managing bash script - Display usage instructions with `snapsh -h` or `snapsh --help` - Taking snapshots requires root priviledges. Take a snapshot with `snapsh -s SUBVOLUME` or `snapsh --snapshot SUBVOLUME`, where `SUBVOLUME` is the name of the source subvolume. You can add a description for the snapshot with the `-d | --description` option (must be used before the `-s` option)

Example with Fedora default btrfs layout with `root` and `home` subvolumes:
`snapsh -d "This is a snapshot" -s root`
This will create a snapshot called `root_snapshot_YYYY.MM.DD-hh:mm:ss` to the `snapshots` subvolume (or the one you defined with `SNAPSHOTS_LOCATION`), with a description "This is a snapshot" - Snapshots can be listed with `snapsh -l` or `snapsh --list` +- Delete snapshots with the `-r` or `--remove` option. List snapshots first with `snapsh -l`, then delete snapshot with e.g. `snapsh -r 2`, where 2 is the number of the deletable snapshot in the `-l` listing. The list numbers always start from 1 and increment from there, so always check the number before deletion. Batch deletion might be implemented later. -- 2.40.1