diff --git a/README.md b/README.md index e8387f7..fbb6230 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,2 @@ # snapsh -Btrfs snapshot managing bash script - -### Requirements: -- `bash` -- GNU `setopt` - part of GNU `coreutils` -- `btrfs-progs`- Userspace programs for btrfs - -### Instructions: -- 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" -- 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. - -### Planned features: -- separate config file -- `--install` option to integrate script into system -- Batch deletion of snapshots -- `systemd` timer for automating snapshots -- `--auto` option (quiet) for automated snapshots -- Option to snapshot multiple/all subvolumes - -Root access is required for `btrfs-progs`. - -### License: -GPL v3.0 +Btrfs snapshot managing script 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