summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <cjennings@citizen.lan>2021-04-04 23:02:44 -0500
committerCraig Jennings <cjennings@citizen.lan>2021-04-04 23:02:44 -0500
commitccda4719ac3c1e4350ee96121e17a463bf5ac517 (patch)
tree3b7485268de880caf482bb71afe0610d036e2c94
parent7749a67d3846bbb88d9c07c02fcbd92866da4ba0 (diff)
separate hostnames, avoid alias conflicts, and many related changes.
- printf instead of echo - make script executable after install - make TYPE case insensitive earlier for HELP - validate destination directory exists - validate destination is a mounted drive, if not attempt a mount.
-rwxr-xr-xrsyncshot104
-rw-r--r--rsyncshot.org104
2 files changed, 134 insertions, 74 deletions
diff --git a/rsyncshot b/rsyncshot
index 5b01129..8887533 100755
--- a/rsyncshot
+++ b/rsyncshot
@@ -1,6 +1,5 @@
#!/bin/bash
# rsyncshot
-
# Craig Jennings craigmartinjennings@gmail.com
# Inspired by Mike Rubel: http://www.mikerubel.org/computers/rsync_snapshots/
@@ -13,9 +12,11 @@
# set -x
# Default Locations For Setup
+# Modify BACKUPLOCATION to point to the mount point of your backup
+MOUNTPOINT=/media/backup;
SCRIPTLOC=/usr/local/bin/rsyncshot;
-DESTINATION=/media/backup;
+DESTINATION=$MOUNTPOINT/$HOSTNAME
INSTALLHOME=/etc/rsyncshot
LOGHOME=/var/log/rsyncshot.log;
@@ -23,6 +24,14 @@ LOGHOME=/var/log/rsyncshot.log;
INCLUDES="$INSTALLHOME/include.txt";
EXCLUDES="$INSTALLHOME/exclude.txt";
+# Sidestep Alias Conflicts
+# Copy, move, and rm commands are often aliased to require user input.
+# Using a variable allows us to sidestep this and complete the actions without any interaction.
+
+CP="/usr/bin/cp"
+MV="/usr/bin/mv"
+RM="/usr/bin/rm"
+
# Default Cron Job Entries
# CRON_H = hourly on minute 0 from 1am to 11pm
# CRON_D = daily at midnight, Monday - Saturday
@@ -32,23 +41,21 @@ CRON_H="0 1-23 * * * $SCRIPTLOC hourly 22";
CRON_D="0 0 * * 1-6 $SCRIPTLOC daily 6";
CRON_W="0 0 * * 7 $SCRIPTLOC weekly 51";
-# Help
+# Help Function
help()
{
- echo ""
- echo "rsyncshot - compact snapshots on Linux using rsync and hard links."
- echo ""
- echo "rsyncshot must be run as root"
- echo ""
- echo "Usage: "
- echo "rsyncshot <name> <number of backups to retain>"
- echo " setup (installs rsyncshot and cron jobs)"
- echo " help (prints this info)"
- echo "Notes:"
- echo "- install and log locations defined in script."
-
-# Error
+ printf "\nrsyncshot - compact snapshots on Linux using rsync and hard links.\n\n"
+ printf "Usage:\nrsyncshot <name> <number of backups to retain>\n"
+ printf " setup (installs rsyncshot and cron jobs)\n"
+ printf " help (prints this info)\n"
+ printf "Notes:\n"
+ printf '%s\n' "- rsyncshot must be run as root"
+ printf '%s\n' "- install and log locations defined in script."
+ printf '%s\n'
+}
+
+# Error Function
error()
{
@@ -57,27 +64,27 @@ error()
exit 1;
}
-# Setup
+# Setup Function
setup()
-{
- # copy this file to directory on path
- cp -f $0 /usr/local/bin
- echo "$0 copied to /usr/local/bin"
+{ # copy this file to directory on path and make executable
+ $CP -f $0 $SCRIPTLOC
+ sudo chmod +x $SCRIPTLOC
+ echo "$0 copied to $SCRIPTLOC and is executable"
# make install home if it doesn't exist;
if [ ! -d $INSTALLHOME ]; then
- mkdir -p $INSTALLHOME;
- "Created install home at $INSTALLHOME.";
+ mkdir -p $INSTALLHOME;
+ echo "Created install home at $INSTALLHOME.";
fi
# create includes file and add default entries
- if [ -f $INCLUDES ]; then rm $INCLUDES; fi
+ if [ -f $INCLUDES ]; then $RM $INCLUDES; fi
printf "/home /etc /usr/local/bin" >> $INCLUDES;
echo "modify include file at $INCLUDES";
# create excludes file and add default entries
- if [ -f $EXCLUDES ]; then rm $EXCLUDES; fi
+ if [ -f $EXCLUDES ]; then $RM $EXCLUDES; fi
printf "*.pyc\n*.pyo\n*.class\n*.elc\n*.o\n*.tmp\n.cache*" >> $EXCLUDES;
echo "modify exclude file at $EXCLUDES";
@@ -87,34 +94,38 @@ setup()
echo "$CRON_D >> $LOGHOME 2>&1" >> crontemp;
echo "$CRON_W >> $LOGHOME 2>&1">> crontemp;
crontab crontemp;
- rm crontemp;
+ $RM crontemp;
echo "hourly, daily, and weekly cron jobs installed.";
}
-# Check Help Option
+# Display Help If Requested
+# Make the argument uppercase for case insensitivity.
+TYPE=$(tr '[a-z]' '[A-Z]' <<< $1);
if [ "$TYPE" = "HELP" ]; then help; exit; fi
-# Ensure Root
+# Ensure We're Running As Root
if [ "$EUID" -ne 0 ]; then error "This script must be run as root."; fi
-# Display How Script Was Started
+# Display Start Information
echo "rsyncshot invoked on `date -u` with: $0 $1 $2";
-# Parameter Validation
+# Validate Backup Type
+# First argument must be alpha characters
if ! [[ $1 =~ [a-zA-Z] ]]; then error "snapshot type not recognized."; fi
-TYPE=$(tr '[a-z]' '[A-Z]' <<< $1);
if [ "$TYPE" = "SETUP" ]; then setup; exit; fi
# Validate Max Snapshots
+# Second argument must be numeric
if ! [[ $2 =~ [0-9] ]]; then error "max snapshots not a number."; fi
MAX=$(($2-1));
# Validate Include File (Source Directories) Exist
+# Validates the include file exists, and checks the file contents are valid directories
if [ ! -f "$INCLUDES" ]; then error "include file $INCLUDES not found."; fi
SOURCES=$(<$INCLUDES);
@@ -127,7 +138,26 @@ done
if [ ! -f "$EXCLUDES" ]; then error "Exclude file $EXCLUDES not found."; fi
-# Sync Each Directory In Turn
+# Validate Mountpoint
+# Fail if mountpoint doesn't exist.
+# Attempt mounting if destination filesystem not mounted; error if attempt fails.
+
+[ -d $MOUNTPOINT ] || error "$MOUNTPOINT doesn't exist!"
+
+if grep -qs "$MOUNTPOINT" /proc/mounts ; then
+ true
+else if [ $? -eq 0 ]; then
+ true
+ else
+ error "$MOUNTPOINT unmounted, and mount attempt failed."
+ fi
+fi
+
+# Validate Destination Directory Exists
+
+[ -d $DESTINATION ] || mkdir $DESTINATION || error "$DESTINATION doesn't exist, and attempt to create failed."
+
+# Sync Each Backup Directory In Turn
for SOURCE in $SOURCES
do
@@ -137,18 +167,18 @@ do
--update $SOURCE $DESTINATION/latest ;
done
-# Delete Max+1 Snapshot If Exists
+# If Exists, Delete Max+1 Snapshot
if [ -d $DESTINATION/$TYPE.$MAX ]; then
- rm -rf $DESTINATION/$TYPE.$MAX;
+ $RM -rf $DESTINATION/$TYPE.$MAX;
fi
-# Rotate Snapshots Descending
+# Rotate Remaining Snapshots Descending
for (( start=$(($MAX)); start>=0; start--)); do
end=$(($start+1));
if [ -d $DESTINATION/$TYPE.$start ]; then
- mv $DESTINATION/$TYPE.$start $DESTINATION/$TYPE.$end;
+ $MV $DESTINATION/$TYPE.$start $DESTINATION/$TYPE.$end;
fi
done
@@ -158,7 +188,7 @@ touch $DESTINATION/latest
# Hard Link Only Copy to Destination
-cp -al $DESTINATION/latest $DESTINATION/$TYPE.0;
+$CP -al $DESTINATION/latest $DESTINATION/$TYPE.0;
# Make Directory Type Read-Only
diff --git a/rsyncshot.org b/rsyncshot.org
index 830654b..08bbc2b 100644
--- a/rsyncshot.org
+++ b/rsyncshot.org
@@ -1,5 +1,6 @@
* rsyncshot
-#+begin_SRC sh :tangle rsyncshot :comments org :shebang "#!/bin/bash"
+#+begin_SRC sh :tangle rsyncshot :comments no :shebang "#!/bin/bash"
+# rsyncshot
# Craig Jennings craigmartinjennings@gmail.com
# Inspired by Mike Rubel: http://www.mikerubel.org/computers/rsync_snapshots/
#+end_SRC
@@ -12,9 +13,11 @@
# set -x
#+end_SRC
* Default Locations For Setup
+Modify BACKUPLOCATION to point to the mount point of your backup
#+begin_SRC sh :tangle rsyncshot :comments org
+MOUNTPOINT=/media/backup;
SCRIPTLOC=/usr/local/bin/rsyncshot;
-DESTINATION=/media/backup;
+DESTINATION=$MOUNTPOINT/$HOSTNAME
INSTALLHOME=/etc/rsyncshot
LOGHOME=/var/log/rsyncshot.log;
@@ -22,6 +25,14 @@ LOGHOME=/var/log/rsyncshot.log;
INCLUDES="$INSTALLHOME/include.txt";
EXCLUDES="$INSTALLHOME/exclude.txt";
#+end_SRC
+* Sidestep Alias Conflicts
+Copy, move, and rm commands are often aliased to require user input.
+Using a variable allows us to sidestep this and complete the actions without any interaction.
+#+begin_SRC sh :tangle rsyncshot :comments org
+CP="/usr/bin/cp"
+MV="/usr/bin/mv"
+RM="/usr/bin/rm"
+#+end_SRC
* Default Cron Job Entries
CRON_H = hourly on minute 0 from 1am to 11pm
CRON_D = daily at midnight, Monday - Saturday
@@ -32,24 +43,21 @@ CRON_D="0 0 * * 1-6 $SCRIPTLOC daily 6";
CRON_W="0 0 * * 7 $SCRIPTLOC weekly 51";
#+end_SRC
* Functions
-** Help
+** Help Function
#+begin_SRC sh :tangle rsyncshot :comments org
help()
{
- echo ""
- echo "rsyncshot - compact snapshots on Linux using rsync and hard links."
- echo ""
- echo "rsyncshot must be run as root"
- echo ""
- echo "Usage: "
- echo "rsyncshot <name> <number of backups to retain>"
- echo " setup (installs rsyncshot and cron jobs)"
- echo " help (prints this info)"
- echo "Notes:"
- echo "- install and log locations defined in script."
-
+ printf "\nrsyncshot - compact snapshots on Linux using rsync and hard links.\n\n"
+ printf "Usage:\nrsyncshot <name> <number of backups to retain>\n"
+ printf " setup (installs rsyncshot and cron jobs)\n"
+ printf " help (prints this info)\n"
+ printf "Notes:\n"
+ printf '%s\n' "- rsyncshot must be run as root"
+ printf '%s\n' "- install and log locations defined in script."
+ printf '%s\n'
+}
#+end_SRC
-** Error
+** Error Function
#+begin_SRC sh :tangle rsyncshot :comments org
error()
{
@@ -58,27 +66,27 @@ error()
exit 1;
}
#+end_SRC
-** Setup
+** Setup Function
#+begin_SRC sh :tangle rsyncshot :comments org
setup()
-{
- # copy this file to directory on path
- cp -f $0 /usr/local/bin
- echo "$0 copied to /usr/local/bin"
+{ # copy this file to directory on path and make executable
+ $CP -f $0 $SCRIPTLOC
+ sudo chmod +x $SCRIPTLOC
+ echo "$0 copied to $SCRIPTLOC and is executable"
# make install home if it doesn't exist;
if [ ! -d $INSTALLHOME ]; then
- mkdir -p $INSTALLHOME;
- "Created install home at $INSTALLHOME.";
+ mkdir -p $INSTALLHOME;
+ echo "Created install home at $INSTALLHOME.";
fi
# create includes file and add default entries
- if [ -f $INCLUDES ]; then rm $INCLUDES; fi
+ if [ -f $INCLUDES ]; then $RM $INCLUDES; fi
printf "/home /etc /usr/local/bin" >> $INCLUDES;
echo "modify include file at $INCLUDES";
# create excludes file and add default entries
- if [ -f $EXCLUDES ]; then rm $EXCLUDES; fi
+ if [ -f $EXCLUDES ]; then $RM $EXCLUDES; fi
printf "*.pyc\n*.pyo\n*.class\n*.elc\n*.o\n*.tmp\n.cache*" >> $EXCLUDES;
echo "modify exclude file at $EXCLUDES";
@@ -88,35 +96,39 @@ setup()
echo "$CRON_D >> $LOGHOME 2>&1" >> crontemp;
echo "$CRON_W >> $LOGHOME 2>&1">> crontemp;
crontab crontemp;
- rm crontemp;
+ $RM crontemp;
echo "hourly, daily, and weekly cron jobs installed.";
}
#+end_SRC
-* Check Help Option
+* Display Help If Requested
+Make the argument uppercase for case insensitivity.
#+begin_SRC sh :tangle rsyncshot :comments org
+TYPE=$(tr '[a-z]' '[A-Z]' <<< $1);
if [ "$TYPE" = "HELP" ]; then help; exit; fi
#+end_SRC
-* Ensure Root
+* Ensure We're Running As Root
#+begin_SRC sh :tangle rsyncshot :comments org
if [ "$EUID" -ne 0 ]; then error "This script must be run as root."; fi
#+end_SRC
-* Display How Script Was Started
+* Display Start Information
#+begin_SRC sh :tangle rsyncshot :comments org
echo "rsyncshot invoked on `date -u` with: $0 $1 $2";
#+end_SRC
-* Parameter Validation
+* Validate Backup Type
+First argument must be alpha characters
#+begin_SRC sh :tangle rsyncshot :comments org
if ! [[ $1 =~ [a-zA-Z] ]]; then error "snapshot type not recognized."; fi
-TYPE=$(tr '[a-z]' '[A-Z]' <<< $1);
if [ "$TYPE" = "SETUP" ]; then setup; exit; fi
#+end_SRC
* Validate Max Snapshots
+Second argument must be numeric
#+begin_SRC sh :tangle rsyncshot :comments org
if ! [[ $2 =~ [0-9] ]]; then error "max snapshots not a number."; fi
MAX=$(($2-1));
#+end_SRC
* Validate Include File (Source Directories) Exist
+Validates the include file exists, and checks the file contents are valid directories
#+begin_SRC sh :tangle rsyncshot :comments org
if [ ! -f "$INCLUDES" ]; then error "include file $INCLUDES not found."; fi
SOURCES=$(<$INCLUDES);
@@ -129,8 +141,26 @@ done
#+begin_SRC sh :tangle rsyncshot :comments org
if [ ! -f "$EXCLUDES" ]; then error "Exclude file $EXCLUDES not found."; fi
#+end_SRC
+* Validate Mountpoint
+Fail if mountpoint doesn't exist.
+Attempt mounting if destination filesystem not mounted; error if attempt fails.
+#+begin_SRC sh :tangle rsyncshot :comments org
+ [ -d $MOUNTPOINT ] || error "$MOUNTPOINT doesn't exist!"
-* Sync Each Directory In Turn
+ if grep -qs "$MOUNTPOINT" /proc/mounts ; then
+ true
+ else if [ $? -eq 0 ]; then
+ true
+ else
+ error "$MOUNTPOINT unmounted, and mount attempt failed."
+ fi
+ fi
+#+end_SRC
+* Validate Destination Directory Exists
+#+begin_SRC sh :tangle rsyncshot :comments org
+[ -d $DESTINATION ] || mkdir $DESTINATION || error "$DESTINATION doesn't exist, and attempt to create failed."
+#+end_SRC
+* Sync Each Backup Directory In Turn
#+begin_SRC sh :tangle rsyncshot :comments org
for SOURCE in $SOURCES
do
@@ -140,18 +170,18 @@ do
--update $SOURCE $DESTINATION/latest ;
done
#+end_SRC
-* Delete Max+1 Snapshot If Exists
+* If Exists, Delete Max+1 Snapshot
#+begin_SRC sh :tangle rsyncshot :comments org
if [ -d $DESTINATION/$TYPE.$MAX ]; then
- rm -rf $DESTINATION/$TYPE.$MAX;
+ $RM -rf $DESTINATION/$TYPE.$MAX;
fi
#+end_SRC
-* Rotate Snapshots Descending
+* Rotate Remaining Snapshots Descending
#+begin_SRC sh :tangle rsyncshot :comments org
for (( start=$(($MAX)); start>=0; start--)); do
end=$(($start+1));
if [ -d $DESTINATION/$TYPE.$start ]; then
- mv $DESTINATION/$TYPE.$start $DESTINATION/$TYPE.$end;
+ $MV $DESTINATION/$TYPE.$start $DESTINATION/$TYPE.$end;
fi
done
#+end_SRC
@@ -161,7 +191,7 @@ touch $DESTINATION/latest
#+end_SRC
* Hard Link Only Copy to Destination
#+begin_SRC sh :tangle rsyncshot :comments org
-cp -al $DESTINATION/latest $DESTINATION/$TYPE.0;
+$CP -al $DESTINATION/latest $DESTINATION/$TYPE.0;
#+end_SRC
* Make Directory Type Read-Only
#+begin_SRC sh :tangle rsyncshot :comments org