Separate config file, rollback cleanup systemd-unit #15

Merged
jarno merged 4 commits from config-file into master 2020-08-07 11:32:13 +03:00
4 changed files with 107 additions and 19 deletions

View File

@ -14,6 +14,7 @@ Btrfs snapshot managing bash script
- Snapshots can be listed with `snapsh -l` or `snapsh --list` - 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. - 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 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. - 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: ### Planned features:

88
snapsh
View File

@ -16,16 +16,25 @@
## You should have received a copy of the GNU General Public License ## You should have received a copy of the GNU General Public License
## along with this program. If not, see <http://www.gnu.org/licenses/>. ## along with this program. If not, see <http://www.gnu.org/licenses/>.
printf "\n" # Print an empty line for readability
# Environment set up: # Environment set up:
TOPLEVEL="/root/btrfs-toplevel" # If config file exists, source it, otherwise use default values
SNAPSHOTS_LOCATION="/root/btrfs-toplevel/snapshots" if [[ -e /etc/snapsh.conf ]]; then
. /etc/snapsh.conf
else
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)" # 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) BTRFS_EXECUTABLE=$(which btrfs)
TIMESTAMP=$(date +%Y.%m.%d-%H:%M:%S)
SUBVOLUME="root"
DESCRIPTION=""
SUBVOLUME="root" # Default subvolume
DESCRIPTION="" # Description is blank unless set with the -d|--description option
help() { help() {
@ -52,7 +61,7 @@ Options:
snapshot() { snapshot() {
EXIT_CODE=0 EXIT_CODE=0
root_check root_check #Check that script is run with root privileges.
# Check that the subvolume storing snapshots exists # Check that the subvolume storing snapshots exists
if [[ ! -d ${SNAPSHOTS_LOCATION} ]]; then if [[ ! -d ${SNAPSHOTS_LOCATION} ]]; then
@ -60,6 +69,7 @@ snapshot() {
read -n 1 -p "y/n: " read -n 1 -p "y/n: "
if [[ "${REPLY}" == "y" ]]; then if [[ "${REPLY}" == "y" ]]; then
# Create subvolume defined with SNAPSHOTS_LOCATION
${BTRFS_EXECUTABLE} subvolume create ${SNAPSHOTS_LOCATION} ${BTRFS_EXECUTABLE} subvolume create ${SNAPSHOTS_LOCATION}
unset ${REPLY} unset ${REPLY}
else else
@ -74,7 +84,7 @@ snapshot() {
printf "Creating snapshot of subvolume ${SUBVOLUME} as ${SUBVOLUME}_snapshot_${TIMESTAMP}\n" printf "Creating snapshot of subvolume ${SUBVOLUME} as ${SUBVOLUME}_snapshot_${TIMESTAMP}\n"
# Create info file for listing snapshots # 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)\" printf "DATE=\"$(date)\"
SOURCE_SUBVOLUME=\"${SUBVOLUME}\" SOURCE_SUBVOLUME=\"${SUBVOLUME}\"
DESCRIPTION=\"${DESCRIPTION}\" DESCRIPTION=\"${DESCRIPTION}\"
@ -122,8 +132,11 @@ list() {
remove() { remove() {
root_check root_check
# List snapshots in to array SNAPSHOTS
SNAPSHOTS=(${SNAPSHOTS_LOCATION}/*/) SNAPSHOTS=(${SNAPSHOTS_LOCATION}/*/)
# Check that given NUMBER is a valid snapshot
if [[ "${REMOVE_TARGET}" -gt "${#SNAPSHOTS[@]}" ]]; then if [[ "${REMOVE_TARGET}" -gt "${#SNAPSHOTS[@]}" ]]; then
printf "Snapshot number ${REMOVE_TARGET} does not exist.\n" printf "Snapshot number ${REMOVE_TARGET} does not exist.\n"
exit 1 exit 1
@ -140,8 +153,8 @@ remove() {
read -n 1 read -n 1
if [[ "${REPLY}" == "y" ]]; then if [[ "${REPLY}" == "y" ]]; then
printf "\n" printf "\n"
${BTRFS_EXECUTABLE} property set ${TARGET} ro false ${BTRFS_EXECUTABLE} property set ${TARGET} ro false # Set snapshot as read-write first
${BTRFS_EXECUTABLE} subvolume delete ${TARGET} ${BTRFS_EXECUTABLE} subvolume delete ${TARGET} # Delete snapshot
exit 0 exit 0
else else
printf "\nAborted by user.\n" printf "\nAborted by user.\n"
@ -152,9 +165,10 @@ remove() {
rollback() { rollback() {
root_check root_check # Check root privileges
SNAPSHOTS=(${SNAPSHOTS_LOCATION}/*/) SNAPSHOTS=(${SNAPSHOTS_LOCATION}/*/) # List snapshots to array
# Check that NUBER to roll back to is a valid snapshot
if [[ "${ROLLBACK_TARGET}" -gt "${#SNAPSHOTS[@]}" ]]; then if [[ "${ROLLBACK_TARGET}" -gt "${#SNAPSHOTS[@]}" ]]; then
printf "Snapshot number ${ROLLBACK_TARGET} does not exist.\n" printf "Snapshot number ${ROLLBACK_TARGET} does not exist.\n"
exit 1 exit 1
@ -167,12 +181,12 @@ rollback() {
TARGET=${SNAPSHOTS[${INDEX}]} TARGET=${SNAPSHOTS[${INDEX}]}
. ${TARGET}/.snapsh . ${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 read
if [[ "${REPLY}" == "yes" ]]; then if [[ "${REPLY}" == "yes" ]]; then
unset ${REPLY} unset ${REPLY}
printf "\nCreating a backup snapshot of ${SOURCE_SUBVOLUME}...\n" printf "\nCreating a backup snapshot of ${SOURCE_SUBVOLUME}...\n\n"
# Create info file # Create info file
printf "DATE=\"$(date)\" printf "DATE=\"$(date)\"
SOURCE_SUBVOLUME=\"${SOURCE_SUBVOLUME}\" SOURCE_SUBVOLUME=\"${SOURCE_SUBVOLUME}\"
@ -180,9 +194,12 @@ rollback() {
TYPE=\"backup\"\n" > ${TOPLEVEL}/${SOURCE_SUBVOLUME}/.snapsh TYPE=\"backup\"\n" > ${TOPLEVEL}/${SOURCE_SUBVOLUME}/.snapsh
# Create backup snapshot # 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 rm -f ${TOPLEVEL}/${SOURCE_SUBVOLUME}/.snapsh
printf "\n"
# Rename current subvolume # Rename current subvolume
printf "Renaming ${SOURCE_SUBVOLUME} to ${SOURCE_SUBVOLUME}.backup...\n" printf "Renaming ${SOURCE_SUBVOLUME} to ${SOURCE_SUBVOLUME}.backup...\n"
mv ${TOPLEVEL}/${SOURCE_SUBVOLUME} ${TOPLEVEL}/${SOURCE_SUBVOLUME}.backup mv ${TOPLEVEL}/${SOURCE_SUBVOLUME} ${TOPLEVEL}/${SOURCE_SUBVOLUME}.backup
@ -190,13 +207,13 @@ rollback() {
printf "Copying ${TARGET} to ${TOPLEVEL}/${SOURCE_SUBVOLUME}...\n" printf "Copying ${TARGET} to ${TOPLEVEL}/${SOURCE_SUBVOLUME}...\n"
${BTRFS_EXECUTABLE} subvolume snapshot ${TARGET} ${TOPLEVEL}/${SOURCE_SUBVOLUME} ${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 read -n 1
if [[ "${REPLY}" == "y" ]]; then if [[ "${REPLY}" == "y" ]]; then
systemctl reboot & exit 0 systemctl reboot & exit 0
else 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 exit 1
fi fi
@ -209,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 # Check for root permissions
root_check() { root_check() {
if [[ "$UID" -ne 0 ]]; then if [[ "$UID" -ne 0 ]]; then
@ -227,7 +272,7 @@ fi
# Options parsing: # 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) # Invalid options (getopt returns nonzero)
if [[ "$?" -ne 0 ]]; then if [[ "$?" -ne 0 ]]; then
@ -237,6 +282,8 @@ if [[ "$?" -ne 0 ]]; then
fi fi
eval set -- "$OPTIONS" eval set -- "$OPTIONS"
# Loop through options until -- is reached
while true; do while true; do
case "$1" in case "$1" in
@ -274,6 +321,11 @@ while true; do
shift 2 shift 2
;; ;;
--post-rollback)
post-rollback
shift
;;
-t | --type) -t | --type)
case "$2" in case "$2" in
manual) manual)
@ -285,7 +337,7 @@ while true; do
backup) backup)
SET_TYPE="backup";; SET_TYPE="backup";;
*) *)
printf "\nIncorrect type value.\n" printf "\nIncorrect TYPE value.\n"
exit 1;; exit 1;;
esac esac
shift 2 shift 2

View File

@ -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

25
snapsh.conf Normal file
View File

@ -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)