From 1770fe4e56aaae431d072d618d50af8f25a8f9d8 Mon Sep 17 00:00:00 2001 From: Jarno Rankinen Date: Fri, 7 Aug 2020 06:58:16 +0300 Subject: [PATCH 1/4] Separate config file Started implementing separate config file. Needs to be copied/linked manually to /etc/snapsh.conf --- snapsh | 16 +++++++++++----- snapsh.conf | 25 +++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 snapsh.conf diff --git a/snapsh b/snapsh index c389931..45b254b 100755 --- a/snapsh +++ b/snapsh @@ -18,13 +18,19 @@ # Environment set up: -TOPLEVEL="/root/btrfs-toplevel" -SNAPSHOTS_LOCATION="/root/btrfs-toplevel/snapshots" +if [[ -e /etc/snapsh.conf ]]; then + . /etc/snapsh.conf +else + TOPLEVEL="/root/btrfs-toplevel" + SNAPSHOTS_LOCATION="/root/btrfs-toplevel/snapshots" + TIMESTAMP="$(date +%Y.%m.%d-%H.%M.%S)" + SUBVOLUME="root" + DESCRIPTION="" +fi + +## In case of problems, define the path to the 'btrfs' executable here BTRFS_EXECUTABLE=$(which btrfs) -TIMESTAMP=$(date +%Y.%m.%d-%H:%M:%S) -SUBVOLUME="root" -DESCRIPTION="" diff --git a/snapsh.conf b/snapsh.conf new file mode 100644 index 0000000..ac3f6e0 --- /dev/null +++ b/snapsh.conf @@ -0,0 +1,25 @@ +## Snapsh requires the toplevel (subvolid=5) mounted somewhere +## You can define your mount point here. +## Default: "/root/btrfs-toplevel" +TOPLEVEL="/root/btrfs-toplevel" + +## You should create a subvolume for snapshots. Snapsh will do this +## automatically, define the subvolume here. It is recommended +## to have the subvolume directly under the btrfs root (id=5) +## Use the full path +## Default: "/root/" +SNAPSHOTS_LOCATION="/root/btrfs-toplevel/snapshots" + +## Timestamp format. See 'date --help' for formatting options. +## This will only be used for the name of the snapshot (=subvolume) +## created. E.g. with default setting snapshots will be created as +## "/root/btrfs-toplevel/snapshots/root_snapshot_2020_07_31-01.59.23" +## Default: $(date +%Y.%m.%d-%H.%M.%S) +TIMESTAMP="$(date +%Y.%m.%d-%H.%M.%S)" + + + +## Experimental: +## Define the list of subvolumes to be handled by snapsh here. +## Defaut: (root home) +SUBVOLUMES=(root home) \ No newline at end of file From c2750ab03d3dfe7e7cb374a1e0a65ed5c2c815be Mon Sep 17 00:00:00 2001 From: Jarno Rankinen Date: Fri, 7 Aug 2020 08:43:11 +0300 Subject: [PATCH 2/4] Added comments to script --- snapsh | 49 +++++++++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/snapsh b/snapsh index 45b254b..492b06c 100755 --- a/snapsh +++ b/snapsh @@ -16,22 +16,25 @@ ## You should have received a copy of the GNU General Public License ## along with this program. If not, see . +printf "\n" # Print an empty line for readability + # Environment set up: +# If config file exists, source it, otherwise use default values if [[ -e /etc/snapsh.conf ]]; then . /etc/snapsh.conf else - TOPLEVEL="/root/btrfs-toplevel" - SNAPSHOTS_LOCATION="/root/btrfs-toplevel/snapshots" + TOPLEVEL="/root/btrfs-toplevel" # Mountpoint of subvolid=6 + SNAPSHOTS_LOCATION="/root/btrfs-toplevel/snapshots" # Mountpoint of subvolume for snapshots - TIMESTAMP="$(date +%Y.%m.%d-%H.%M.%S)" - SUBVOLUME="root" - DESCRIPTION="" -fi + TIMESTAMP="$(date +%Y.%m.%d-%H.%M.%S)" # Timestamp used in naming snapshots +fi # (Not the one used in --list) ## In case of problems, define the path to the 'btrfs' executable here BTRFS_EXECUTABLE=$(which btrfs) +SUBVOLUME="root" # Default subvolume +DESCRIPTION="" # Description is blank unless set with the -d|--description option help() { @@ -58,7 +61,7 @@ Options: snapshot() { EXIT_CODE=0 - root_check + root_check #Check that script is run with root privileges. # Check that the subvolume storing snapshots exists if [[ ! -d ${SNAPSHOTS_LOCATION} ]]; then @@ -66,6 +69,7 @@ snapshot() { read -n 1 -p "y/n: " if [[ "${REPLY}" == "y" ]]; then + # Create subvolume defined with SNAPSHOTS_LOCATION ${BTRFS_EXECUTABLE} subvolume create ${SNAPSHOTS_LOCATION} unset ${REPLY} else @@ -80,7 +84,7 @@ snapshot() { 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 + # Created first on the source subvolume, then deleted from the source printf "DATE=\"$(date)\" SOURCE_SUBVOLUME=\"${SUBVOLUME}\" DESCRIPTION=\"${DESCRIPTION}\" @@ -128,8 +132,11 @@ list() { remove() { root_check + + # List snapshots in to array SNAPSHOTS SNAPSHOTS=(${SNAPSHOTS_LOCATION}/*/) + # Check that given NUMBER is a valid snapshot if [[ "${REMOVE_TARGET}" -gt "${#SNAPSHOTS[@]}" ]]; then printf "Snapshot number ${REMOVE_TARGET} does not exist.\n" exit 1 @@ -146,8 +153,8 @@ remove() { read -n 1 if [[ "${REPLY}" == "y" ]]; then printf "\n" - ${BTRFS_EXECUTABLE} property set ${TARGET} ro false - ${BTRFS_EXECUTABLE} subvolume delete ${TARGET} + ${BTRFS_EXECUTABLE} property set ${TARGET} ro false # Set snapshot as read-write first + ${BTRFS_EXECUTABLE} subvolume delete ${TARGET} # Delete snapshot exit 0 else printf "\nAborted by user.\n" @@ -158,9 +165,10 @@ remove() { rollback() { - root_check - SNAPSHOTS=(${SNAPSHOTS_LOCATION}/*/) + root_check # Check root privileges + SNAPSHOTS=(${SNAPSHOTS_LOCATION}/*/) # List snapshots to array + # Check that NUBER to roll back to is a valid snapshot if [[ "${ROLLBACK_TARGET}" -gt "${#SNAPSHOTS[@]}" ]]; then printf "Snapshot number ${ROLLBACK_TARGET} does not exist.\n" exit 1 @@ -173,12 +181,12 @@ rollback() { 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)? " + printf "\nYou are about to roll back to snapshot ${ROLLBACK_TARGET}: ${DATE}, subvolume ${SOURCE_SUBVOLUME}, type ${TYPE}, ${DESCRIPTION}.\n\nAre you sure (yes/no)? " read if [[ "${REPLY}" == "yes" ]]; then unset ${REPLY} - printf "\nCreating a backup snapshot of ${SOURCE_SUBVOLUME}...\n" + printf "\nCreating a backup snapshot of ${SOURCE_SUBVOLUME}...\n\n" # Create info file printf "DATE=\"$(date)\" SOURCE_SUBVOLUME=\"${SOURCE_SUBVOLUME}\" @@ -186,9 +194,12 @@ rollback() { TYPE=\"backup\"\n" > ${TOPLEVEL}/${SOURCE_SUBVOLUME}/.snapsh # Create backup snapshot - ${BTRFS_EXECUTABLE} subvolume snapshot -r ${TOPLEVEL}/${SUBVOLUME} ${SNAPSHOTS_LOCATION}/${SUBVOLUME}_snapshot_${TIMESTAMP} + printf "\nCreating snapshot of ${SUBVOLUME} as ${SNAPSHOTS_LOCATION}/${SUBVOLUME}_backup_${TIMESTAMP}...\n" + ${BTRFS_EXECUTABLE} subvolume snapshot -r ${TOPLEVEL}/${SUBVOLUME} ${SNAPSHOTS_LOCATION}/${SUBVOLUME}_backup_${TIMESTAMP} rm -f ${TOPLEVEL}/${SOURCE_SUBVOLUME}/.snapsh + printf "\n" + # Rename current subvolume printf "Renaming ${SOURCE_SUBVOLUME} to ${SOURCE_SUBVOLUME}.backup...\n" mv ${TOPLEVEL}/${SOURCE_SUBVOLUME} ${TOPLEVEL}/${SOURCE_SUBVOLUME}.backup @@ -196,13 +207,13 @@ rollback() { 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) " + printf "\nSystem needs to be restarted. Do you wish to do that now? (recommended!)? (y/n) " read -n 1 if [[ "${REPLY}" == "y" ]]; then systemctl reboot & exit 0 else - printf "\nPlease restart system as soon as possible. Any changes to subvolume ${SOURCE_SUBVOLUME} will not persist after rebooting.\n" + printf "\n\nPlease restart system as soon as possible. Any changes to subvolume ${SOURCE_SUBVOLUME} will not persist after rebooting.\n" exit 1 fi @@ -243,6 +254,8 @@ if [[ "$?" -ne 0 ]]; then fi eval set -- "$OPTIONS" + +# Loop through options until -- is reached while true; do case "$1" in @@ -291,7 +304,7 @@ while true; do backup) SET_TYPE="backup";; *) - printf "\nIncorrect type value.\n" + printf "\nIncorrect TYPE value.\n" exit 1;; esac shift 2 From b6e5132cb5a097b73cc6e5635ce9de61d77a46bb Mon Sep 17 00:00:00 2001 From: Jarno Rankinen Date: Fri, 7 Aug 2020 11:22:28 +0300 Subject: [PATCH 3/4] Rollback cleanups implemented Implemented systemd unit that removes the *.backup subvolumes leftover from rollbacks --- snapsh | 37 ++++++++++++++++++++++++++++++++++-- snapsh-post-rollback.service | 10 ++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 snapsh-post-rollback.service diff --git a/snapsh b/snapsh index 492b06c..9b4989f 100755 --- a/snapsh +++ b/snapsh @@ -226,6 +226,34 @@ rollback() { +post-rollback() { + ## This function is meant to be executed by the included systemd unit on every boot + ## Outputs to systemd-journal, use journalctl -t snapsh to check output + + EXIT_CODE=0 + + shopt -s nullglob + + echo "Checking for leftover subvolumes..." | systemd-cat -t snapsh + + BACKUPS=("${TOPLEVEL}/*.backup/") + + if [[ -n "$BACKUPS" ]]; then + for backup in ${TOPLEVEL}/*.backup/; do + echo "${backup} found" | systemd-cat -t snapsh + echo "Deleting ${backup}..." | systemd-cat -t snapsh + ${BTRFS_EXECUTABLE} subvolume delete ${backup} > /dev/null + let EXIT_CODE=${EXIT_CODE}+$? + done + exit ${EXIT_CODE} + else + echo "No leftovers found." | systemd-cat -t snapsh + exit 0 + fi +} + + + # Check for root permissions root_check() { if [[ "$UID" -ne 0 ]]; then @@ -244,7 +272,7 @@ 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:t: --long help,snapshot:,description:,list,remove:,rollback:,type:,post-rollback -- "$@") # Invalid options (getopt returns nonzero) if [[ "$?" -ne 0 ]]; then @@ -292,6 +320,11 @@ while true; do rollback shift 2 ;; + + --post-rollback) + post-rollback + shift + ;; -t | --type) case "$2" in @@ -322,4 +355,4 @@ if [[ -n "${SNAPSHOT}" ]]; then snapshot elif [[ -n "${LIST}" ]]; then list -fi \ No newline at end of file +fi diff --git a/snapsh-post-rollback.service b/snapsh-post-rollback.service new file mode 100644 index 0000000..e6e1d98 --- /dev/null +++ b/snapsh-post-rollback.service @@ -0,0 +1,10 @@ +[Unit] +Description=Remove leftover *.backup subvolumes + +[Service] +Type=oneshot +ExecStart=/usr/sbin/snapsh --post-rollback +RemainAfterExit=yes + +[Install] +WantedBy=default.target From 3d700de1db1b5f320a0dcbdd1afd6c4c778bbcfe Mon Sep 17 00:00:00 2001 From: Jarno Rankinen <50285623+0ranki@users.noreply.github.com> Date: Fri, 7 Aug 2020 11:31:12 +0300 Subject: [PATCH 4/4] Update README.md Added instructions for enabling post-rollback cleanup systemd-unit. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 35403ab..1171e02 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Btrfs snapshot managing bash script - 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. + - Rollback renames your currently active subvolume to SUBVOLUME.backup, if you want these backups to be deleted automatically, copy the included `snapsh-post-rollback.service` to `/etc/systemd/system` and `systemctl enable snapsh-post-rollback.service`. It will check if `*.backup` subvolumes exist and delete them on every boot. This is recommended to be enabled, since rollbacks also take a "regular" readonly snapshot as `SUBVOLUME_backup_YYYY.MM.DD-hh:mm:ss`. The unit will be enabled automatically with the `--install` option which will be done later. - 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: