From c38d25a3a5467b0f62de398c3931b393c5e862b4 Mon Sep 17 00:00:00 2001 From: Jarno Rankinen Date: Sat, 1 Aug 2020 09:56:16 +0300 Subject: [PATCH 01/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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 5910ad5f80214433fae94f433bce2cf81807dbaf Mon Sep 17 00:00:00 2001 From: Jarno Rankinen Date: Wed, 5 Aug 2020 00:23:28 +0300 Subject: [PATCH 10/15] Rollbacks implemented Rollback (--rollback) implemented. Requires tidying up printouts. Fixed issue with using a larger number than the number of snapshots for "--remove" --- snapsh | 92 +++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 79 insertions(+), 13 deletions(-) diff --git a/snapsh b/snapsh index e685614..97ddcbb 100755 --- a/snapsh +++ b/snapsh @@ -25,7 +25,6 @@ BTRFS_EXECUTABLE=$(which btrfs) TIMESTAMP=$(date +%Y.%m.%d-%H:%M:%S) SUBVOLUME="root" DESCRIPTION="" -REMOVE_TARGET="" @@ -41,7 +40,10 @@ Options: -s SUBVOL, --snapshot SUBVOL Take a snapshot of subvolume named SUBVOL. -l, --list List snapshots -r NUMBER, --remove NUMBER Remove snapshot NUMBER. See snapshot numbers with - snapsh -l\n" + snapsh -l + --rollback NUMBER Roll back to snapshot NUMBER. See snapshot numbers + with snapsh -l. Target subvolume is detected from + snapshot automatically.\n" } @@ -109,20 +111,19 @@ list() { remove() { root_check SNAPSHOTS=(${SNAPSHOTS_LOCATION}/*/) + + if [[ "${REMOVE_TARGET}" -gt "${#SNAPSHOTS[@]}" ]]; then + printf "Snapshot number ${REMOVE_TARGET} does not exist.\n" + exit 1 + elif [[ "${REMOVE_TARGET}" -lt 1 ]]; then + printf "Number must be greater than 0.\n" + exit 1 + fi + 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 @@ -138,6 +139,64 @@ remove() { +rollback() { + root_check + SNAPSHOTS=(${SNAPSHOTS_LOCATION}/*/) + + if [[ "${ROLLBACK_TARGET}" -gt "${#SNAPSHOTS[@]}" ]]; then + printf "Snapshot number ${ROLLBACK_TARGET} does not exist.\n" + exit 1 + elif [[ "${ROLLBACK_TARGET}" -lt 1 ]]; then + printf "Number must be greater than 0.\n" + exit 1 + fi + + let INDEX=${ROLLBACK_TARGET}-1 + TARGET=${SNAPSHOTS[${INDEX}]} + . ${TARGET}/.snapsh + + printf "You are about to roll back to snapshot ${ROLLBACK_TARGET}: ${DATE}, subvolume ${SOURCE_SUBVOLUME}, type ${TYPE}, ${DESCRIPTION}.\nAre you sure (yes/no)? " + read + + if [[ "${REPLY}" == "yes" ]]; then + unset ${REPLY} + printf "\nCreating a backup snapshot of ${SOURCE_SUBVOLUME}...\n" + # Create info file + printf "DATE=\"$(date)\" + SOURCE_SUBVOLUME=\"${SOURCE_SUBVOLUME}\" + DESCRIPTION=\"Rollback backup\" + TYPE=\"backup\"\n" > ${TOPLEVEL}/${SOURCE_SUBVOLUME}/.snapsh + + # Create backup snapshot + ${BTRFS_EXECUTABLE} subvolume snapshot -r ${TOPLEVEL}/${SUBVOLUME} ${SNAPSHOTS_LOCATION}/${SUBVOLUME}_snapshot_${TIMESTAMP} + rm -f ${TOPLEVEL}/${SOURCE_SUBVOLUME}/.snapsh + + # Rename current subvolume + printf "Renaming ${SOURCE_SUBVOLUME} to ${SOURCE_SUBVOLUME}.backup...\n" + mv ${TOPLEVEL}/${SOURCE_SUBVOLUME} ${TOPLEVEL}/${SOURCE_SUBVOLUME}.backup + + printf "Copying ${TARGET} to ${TOPLEVEL}/${SOURCE_SUBVOLUME}...\n" + ${BTRFS_EXECUTABLE} subvolume snapshot ${TARGET} ${TOPLEVEL}/${SOURCE_SUBVOLUME} + + printf "System needs to be restarted. Do you wish to do that now? (recommended!)? (y/n) " + read -n 1 + + if [[ "${REPLY}" == "y" ]]; then + systemctl restart & exit 0 + else + printf "\nPlease restart system as soon as possible. Any changes to\nsubvolume ${SOURCE_SUBVOLUME} will not persist after rebooting.\n" + exit 1 + fi + + else + printf "\nAborted by user\n" + exit 1 + fi + +} + + + # Check for root permissions root_check() { if [[ "$UID" -ne 0 ]]; then @@ -156,7 +215,7 @@ fi # Options parsing: -OPTIONS=$(getopt -a -n snapsh -o hs:d:lr: --long help,snapshot:,description:,list,remove: -- "$@") +OPTIONS=$(getopt -a -n snapsh -o hs:d:lr: --long help,snapshot:,description:,list,remove:,rollback: -- "$@") # Invalid options (getopt returns nonzero) if [[ "$?" -ne 0 ]]; then @@ -197,6 +256,13 @@ while true; do shift 2 ;; + --rollback) + ROLLBACK_TARGET="$2" + rollback + shift 2 + ;; + + esac done -- 2.40.1 From 10b221be188206843f58c47f527cabc5b19e1991 Mon Sep 17 00:00:00 2001 From: Jarno Rankinen Date: Wed, 5 Aug 2020 08:40:31 +0300 Subject: [PATCH 11/15] Fixed rebooting after rollback Fixed typo in systemctl reboot call after rollback --- snapsh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snapsh b/snapsh index 97ddcbb..b2705f1 100755 --- a/snapsh +++ b/snapsh @@ -182,9 +182,9 @@ rollback() { read -n 1 if [[ "${REPLY}" == "y" ]]; then - systemctl restart & exit 0 + systemctl reboot & exit 0 else - printf "\nPlease restart system as soon as possible. Any changes to\nsubvolume ${SOURCE_SUBVOLUME} will not persist after rebooting.\n" + printf "\nPlease restart system as soon as possible. Any changes to subvolume ${SOURCE_SUBVOLUME} will not persist after rebooting.\n" exit 1 fi -- 2.40.1 From 4c834e8c1b4ef97ab7dd65378f9252c7490cbe0e Mon Sep 17 00:00:00 2001 From: Jarno Rankinen Date: Wed, 5 Aug 2020 09:15:58 +0300 Subject: [PATCH 12/15] Updated helptext --- snapsh | 67 +++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/snapsh b/snapsh index b2705f1..c389931 100755 --- a/snapsh +++ b/snapsh @@ -29,15 +29,16 @@ DESCRIPTION="" help() { - printf "Usage: - snapsh [OPTIONS] + printf "Usage:\n +snapsh [OPTIONS] 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. + -d STR, --description STR Add a description for the snapshot displayed in the + snapshots listing. + -t TYPE, --type TYPE Set the type of snapshot, where TYPE=manual|auto|boot|backup + Can be used with -l, --list to filter results -l, --list List snapshots -r NUMBER, --remove NUMBER Remove snapshot NUMBER. See snapshot numbers with snapsh -l @@ -65,6 +66,11 @@ snapshot() { EXIT_CODE=1 fi else + # TYPE defaults to manual + if [[ -z "${SET_TYPE}" ]]; then + SET_TYPE="manual" + fi + printf "Creating snapshot of subvolume ${SUBVOLUME} as ${SUBVOLUME}_snapshot_${TIMESTAMP}\n" # Create info file for listing snapshots @@ -72,7 +78,7 @@ snapshot() { printf "DATE=\"$(date)\" SOURCE_SUBVOLUME=\"${SUBVOLUME}\" DESCRIPTION=\"${DESCRIPTION}\" - TYPE=\"manual\"\n" > ${TOPLEVEL}/${SUBVOLUME}/.snapsh + TYPE=\"${SET_TYPE}\"\n" > ${TOPLEVEL}/${SUBVOLUME}/.snapsh # Create readonly subvolume ${BTRFS_EXECUTABLE} subvolume snapshot -r ${TOPLEVEL}/${SUBVOLUME} ${SNAPSHOTS_LOCATION}/${SUBVOLUME}_snapshot_${TIMESTAMP} @@ -85,7 +91,7 @@ snapshot() { DATE=$(date) SOURCE_SUBVOLUME=${SUBVOLUME} DESCRIPTION=${DESCRIPTION} - TYPE=\"manual\"\n" + TYPE=\"${SET_TYPE}\"\n" fi @@ -99,8 +105,14 @@ list() { 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 - printf "%8s %32s %8s %8s %s\n" "${NUM} |" "${DATE} |" "${SOURCE_SUBVOLUME} |" "${TYPE} |" "${DESCRIPTION}" + if [[ -z "${SET_TYPE}" ]]; then + . ${snapshot}/.snapsh + printf "%8s %32s %8s %8s %s\n" "${NUM} |" "${DATE} |" "${SOURCE_SUBVOLUME} |" "${TYPE} |" "${DESCRIPTION}" + elif [[ -n "${SET_TYPE}" ]]; then + . ${snapshot}/.snapsh + [[ "${SET_TYPE}" == "${TYPE}" ]] && printf "%8s %32s %8s %8s %s\n" "${NUM} |" "${DATE} |" "${SOURCE_SUBVOLUME} |" "${TYPE} |" "${DESCRIPTION}" + fi + let NUM=NUM+1 done exit 0 @@ -124,7 +136,7 @@ remove() { TARGET=${SNAPSHOTS[${INDEX}]} . ${TARGET}/.snapsh - printf "Delete snapshot ${REMOVE_TARGET}: ${DATE}, subvolume ${SOURCE_SUBVOLUME}, ${DESCRIPTION} (y/n)? " + printf "Delete snapshot ${REMOVE_TARGET}: ${DATE}, source subvolume ${SOURCE_SUBVOLUME}, ${TYPE}, ${DESCRIPTION} (y/n)? " read -n 1 if [[ "${REPLY}" == "y" ]]; then printf "\n" @@ -215,11 +227,11 @@ fi # Options parsing: -OPTIONS=$(getopt -a -n snapsh -o hs:d:lr: --long help,snapshot:,description:,list,remove:,rollback: -- "$@") +OPTIONS=$(getopt -a -n snapsh -o hs:d:lr:t: --long help,snapshot:,description:,list,remove:,rollback:,type: -- "$@") # Invalid options (getopt returns nonzero) if [[ "$?" -ne 0 ]]; then - printf "Error Parsing options\n" + printf "Invalid options.\n" help exit 1 fi @@ -241,12 +253,12 @@ while true; do -s | --snapshot) SUBVOLUME="$2" - snapshot + SNAPSHOT=true shift 2 ;; -l | --list) - list + LIST=true shift ;; @@ -261,9 +273,34 @@ while true; do rollback shift 2 ;; + + -t | --type) + case "$2" in + manual) + SET_TYPE="manual";; + auto) + SET_TYPE="auto";; + boot) + SET_TYPE="boot";; + backup) + SET_TYPE="backup";; + *) + printf "\nIncorrect type value.\n" + exit 1;; + esac + shift 2 + ;; - + --) + shift + break + ;; esac done +if [[ -n "${SNAPSHOT}" ]]; then + snapshot +elif [[ -n "${LIST}" ]]; then + list +fi \ No newline at end of file -- 2.40.1 From c9f5c9dfc5d11f904a0acfb1f45ec0f247dbed9e Mon Sep 17 00:00:00 2001 From: Jarno Rankinen Date: Wed, 5 Aug 2020 09:18:30 +0300 Subject: [PATCH 13/15] Revert "Updated helptext" This reverts commit 4c834e8c1b4ef97ab7dd65378f9252c7490cbe0e. --- snapsh | 67 +++++++++++++--------------------------------------------- 1 file changed, 15 insertions(+), 52 deletions(-) diff --git a/snapsh b/snapsh index c389931..b2705f1 100755 --- a/snapsh +++ b/snapsh @@ -29,16 +29,15 @@ DESCRIPTION="" help() { - printf "Usage:\n -snapsh [OPTIONS] + printf "Usage: + snapsh [OPTIONS] Options: -h, --help Display this help message - -s SUBVOL, --snapshot SUBVOL Take a snapshot of subvolume named SUBVOL. -d STR, --description STR Add a description for the snapshot displayed in the - snapshots listing. - -t TYPE, --type TYPE Set the type of snapshot, where TYPE=manual|auto|boot|backup - Can be used with -l, --list to filter results + 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 -r NUMBER, --remove NUMBER Remove snapshot NUMBER. See snapshot numbers with snapsh -l @@ -66,11 +65,6 @@ snapshot() { EXIT_CODE=1 fi else - # TYPE defaults to manual - if [[ -z "${SET_TYPE}" ]]; then - SET_TYPE="manual" - fi - printf "Creating snapshot of subvolume ${SUBVOLUME} as ${SUBVOLUME}_snapshot_${TIMESTAMP}\n" # Create info file for listing snapshots @@ -78,7 +72,7 @@ snapshot() { printf "DATE=\"$(date)\" SOURCE_SUBVOLUME=\"${SUBVOLUME}\" DESCRIPTION=\"${DESCRIPTION}\" - TYPE=\"${SET_TYPE}\"\n" > ${TOPLEVEL}/${SUBVOLUME}/.snapsh + TYPE=\"manual\"\n" > ${TOPLEVEL}/${SUBVOLUME}/.snapsh # Create readonly subvolume ${BTRFS_EXECUTABLE} subvolume snapshot -r ${TOPLEVEL}/${SUBVOLUME} ${SNAPSHOTS_LOCATION}/${SUBVOLUME}_snapshot_${TIMESTAMP} @@ -91,7 +85,7 @@ snapshot() { DATE=$(date) SOURCE_SUBVOLUME=${SUBVOLUME} DESCRIPTION=${DESCRIPTION} - TYPE=\"${SET_TYPE}\"\n" + TYPE=\"manual\"\n" fi @@ -105,14 +99,8 @@ list() { NUM=1 printf "%6s %s %s %26s %s %s %6s %s %s\n" "Number" "|" "Time:" "|" "Source" "|" "Type" "|" "Description" for snapshot in ${SNAPSHOTS_LOCATION}/*/; do - if [[ -z "${SET_TYPE}" ]]; then - . ${snapshot}/.snapsh - printf "%8s %32s %8s %8s %s\n" "${NUM} |" "${DATE} |" "${SOURCE_SUBVOLUME} |" "${TYPE} |" "${DESCRIPTION}" - elif [[ -n "${SET_TYPE}" ]]; then - . ${snapshot}/.snapsh - [[ "${SET_TYPE}" == "${TYPE}" ]] && printf "%8s %32s %8s %8s %s\n" "${NUM} |" "${DATE} |" "${SOURCE_SUBVOLUME} |" "${TYPE} |" "${DESCRIPTION}" - fi - + . ${snapshot}/.snapsh + printf "%8s %32s %8s %8s %s\n" "${NUM} |" "${DATE} |" "${SOURCE_SUBVOLUME} |" "${TYPE} |" "${DESCRIPTION}" let NUM=NUM+1 done exit 0 @@ -136,7 +124,7 @@ remove() { TARGET=${SNAPSHOTS[${INDEX}]} . ${TARGET}/.snapsh - printf "Delete snapshot ${REMOVE_TARGET}: ${DATE}, source subvolume ${SOURCE_SUBVOLUME}, ${TYPE}, ${DESCRIPTION} (y/n)? " + printf "Delete snapshot ${REMOVE_TARGET}: ${DATE}, subvolume ${SOURCE_SUBVOLUME}, ${DESCRIPTION} (y/n)? " read -n 1 if [[ "${REPLY}" == "y" ]]; then printf "\n" @@ -227,11 +215,11 @@ fi # Options parsing: -OPTIONS=$(getopt -a -n snapsh -o hs:d:lr:t: --long help,snapshot:,description:,list,remove:,rollback:,type: -- "$@") +OPTIONS=$(getopt -a -n snapsh -o hs:d:lr: --long help,snapshot:,description:,list,remove:,rollback: -- "$@") # Invalid options (getopt returns nonzero) if [[ "$?" -ne 0 ]]; then - printf "Invalid options.\n" + printf "Error Parsing options\n" help exit 1 fi @@ -253,12 +241,12 @@ while true; do -s | --snapshot) SUBVOLUME="$2" - SNAPSHOT=true + snapshot shift 2 ;; -l | --list) - LIST=true + list shift ;; @@ -273,34 +261,9 @@ while true; do rollback shift 2 ;; - - -t | --type) - case "$2" in - manual) - SET_TYPE="manual";; - auto) - SET_TYPE="auto";; - boot) - SET_TYPE="boot";; - backup) - SET_TYPE="backup";; - *) - printf "\nIncorrect type value.\n" - exit 1;; - esac - shift 2 - ;; - --) - shift - break - ;; + esac done -if [[ -n "${SNAPSHOT}" ]]; then - snapshot -elif [[ -n "${LIST}" ]]; then - list -fi \ No newline at end of file -- 2.40.1 From 45299d1515f1248e142f1bba15e0bc49c1594b82 Mon Sep 17 00:00:00 2001 From: Jarno Rankinen Date: Wed, 5 Aug 2020 09:20:32 +0300 Subject: [PATCH 14/15] Revert "Revert "Updated helptext"" Messed up with reverting Added -l, --list option --- snapsh | 67 +++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/snapsh b/snapsh index b2705f1..c389931 100755 --- a/snapsh +++ b/snapsh @@ -29,15 +29,16 @@ DESCRIPTION="" help() { - printf "Usage: - snapsh [OPTIONS] + printf "Usage:\n +snapsh [OPTIONS] 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. + -d STR, --description STR Add a description for the snapshot displayed in the + snapshots listing. + -t TYPE, --type TYPE Set the type of snapshot, where TYPE=manual|auto|boot|backup + Can be used with -l, --list to filter results -l, --list List snapshots -r NUMBER, --remove NUMBER Remove snapshot NUMBER. See snapshot numbers with snapsh -l @@ -65,6 +66,11 @@ snapshot() { EXIT_CODE=1 fi else + # TYPE defaults to manual + if [[ -z "${SET_TYPE}" ]]; then + SET_TYPE="manual" + fi + printf "Creating snapshot of subvolume ${SUBVOLUME} as ${SUBVOLUME}_snapshot_${TIMESTAMP}\n" # Create info file for listing snapshots @@ -72,7 +78,7 @@ snapshot() { printf "DATE=\"$(date)\" SOURCE_SUBVOLUME=\"${SUBVOLUME}\" DESCRIPTION=\"${DESCRIPTION}\" - TYPE=\"manual\"\n" > ${TOPLEVEL}/${SUBVOLUME}/.snapsh + TYPE=\"${SET_TYPE}\"\n" > ${TOPLEVEL}/${SUBVOLUME}/.snapsh # Create readonly subvolume ${BTRFS_EXECUTABLE} subvolume snapshot -r ${TOPLEVEL}/${SUBVOLUME} ${SNAPSHOTS_LOCATION}/${SUBVOLUME}_snapshot_${TIMESTAMP} @@ -85,7 +91,7 @@ snapshot() { DATE=$(date) SOURCE_SUBVOLUME=${SUBVOLUME} DESCRIPTION=${DESCRIPTION} - TYPE=\"manual\"\n" + TYPE=\"${SET_TYPE}\"\n" fi @@ -99,8 +105,14 @@ list() { 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 - printf "%8s %32s %8s %8s %s\n" "${NUM} |" "${DATE} |" "${SOURCE_SUBVOLUME} |" "${TYPE} |" "${DESCRIPTION}" + if [[ -z "${SET_TYPE}" ]]; then + . ${snapshot}/.snapsh + printf "%8s %32s %8s %8s %s\n" "${NUM} |" "${DATE} |" "${SOURCE_SUBVOLUME} |" "${TYPE} |" "${DESCRIPTION}" + elif [[ -n "${SET_TYPE}" ]]; then + . ${snapshot}/.snapsh + [[ "${SET_TYPE}" == "${TYPE}" ]] && printf "%8s %32s %8s %8s %s\n" "${NUM} |" "${DATE} |" "${SOURCE_SUBVOLUME} |" "${TYPE} |" "${DESCRIPTION}" + fi + let NUM=NUM+1 done exit 0 @@ -124,7 +136,7 @@ remove() { TARGET=${SNAPSHOTS[${INDEX}]} . ${TARGET}/.snapsh - printf "Delete snapshot ${REMOVE_TARGET}: ${DATE}, subvolume ${SOURCE_SUBVOLUME}, ${DESCRIPTION} (y/n)? " + printf "Delete snapshot ${REMOVE_TARGET}: ${DATE}, source subvolume ${SOURCE_SUBVOLUME}, ${TYPE}, ${DESCRIPTION} (y/n)? " read -n 1 if [[ "${REPLY}" == "y" ]]; then printf "\n" @@ -215,11 +227,11 @@ fi # Options parsing: -OPTIONS=$(getopt -a -n snapsh -o hs:d:lr: --long help,snapshot:,description:,list,remove:,rollback: -- "$@") +OPTIONS=$(getopt -a -n snapsh -o hs:d:lr:t: --long help,snapshot:,description:,list,remove:,rollback:,type: -- "$@") # Invalid options (getopt returns nonzero) if [[ "$?" -ne 0 ]]; then - printf "Error Parsing options\n" + printf "Invalid options.\n" help exit 1 fi @@ -241,12 +253,12 @@ while true; do -s | --snapshot) SUBVOLUME="$2" - snapshot + SNAPSHOT=true shift 2 ;; -l | --list) - list + LIST=true shift ;; @@ -261,9 +273,34 @@ while true; do rollback shift 2 ;; + + -t | --type) + case "$2" in + manual) + SET_TYPE="manual";; + auto) + SET_TYPE="auto";; + boot) + SET_TYPE="boot";; + backup) + SET_TYPE="backup";; + *) + printf "\nIncorrect type value.\n" + exit 1;; + esac + shift 2 + ;; - + --) + shift + break + ;; esac done +if [[ -n "${SNAPSHOT}" ]]; then + snapshot +elif [[ -n "${LIST}" ]]; then + list +fi \ No newline at end of file -- 2.40.1 From b1c68219da510a7285525f06bc948669a10c29bc Mon Sep 17 00:00:00 2001 From: Jarno Rankinen <50285623+0ranki@users.noreply.github.com> Date: Wed, 5 Aug 2020 09:53:16 +0300 Subject: [PATCH 15/15] Update README.md Added -t, --type option --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9e30707..35403ab 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,11 @@ Btrfs snapshot managing bash script - Script needs the toplevel subvolume (id=5) mounted somewhere. Default location is `/root/btrfs-toplevel`, but you can mount it anywhere you like and define it with `TOPLEVEL` variable. (A separate config file will be implemented later). - Will create a subvolume named snapshots by default to the toplevel. This can also be changed with `SNAPSHOTS_LOCATION`. - 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" +- 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

Example with Fedora default btrfs layout with `root` and `home` subvolumes:
`snapsh -s root -d "This is a snapshot" `
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. - Rollback to a snapshot with `snapsh --rollback NUMBER`, where NUMBER is the number listed in `snapsh -l`. The subvolume will be detected from the snapshot. E.g. `snapsh --rollback 14`. Script will ask to reboot system once done. +- Snapshot type displayed in listing can be set with the `-t, --type` option. When used in conjunction with `-l | --list`, it will filter the results based on the type. E.g. `snapsh -l -t manual` will list all the snapshots with the type `manual`. Type can be one of `manual, auto, boot` or `backup`. Note that only `manual` and `backup` are implemented properly at this stage, but you can still use the other types as well. ### Planned features: - separate config file -- 2.40.1