bu.sh 11.3 KB
Newer Older
clewsy's avatar
clewsy committed
1 2
#!/bin/bash

3 4
##This script will take as an input either a specific file or a list of multiple files.
##These files will be backed up to the specified server.
clewsy's avatar
clewsy committed
5

clewsy's avatar
clewsy committed
6
##########Configurable settings
7
BU_USER="b4t"
clewsy's avatar
clewsy committed
8 9
BU_SERVER_LOCAL="seymour"
BU_SERVER_REMOTE="clews.pro"
10 11
BU_REMOTE_DIR="/home/$BU_USER/file_cache/$HOSTNAME"

clewsy's avatar
clewsy committed
12
##########Colours
13 14 15
RED="\033[02;31m"
GREEN="\033[02;32m"
BLUE="\033[01;34m"
clewsy's avatar
clewsy committed
16 17
RESET="\033[0m"

18 19 20 21
##########Define log file and ensure directory exists.
BU_LOG_FILE="${HOME}/.log/bu.log"
if [ ! -d "$(dirname "${BU_LOG_FILE}")" ]; then mkdir --parents "$(dirname "${BU_LOG_FILE}")"; fi

clewsy's avatar
clewsy committed
22
##########Exit codes
clewsy's avatar
clewsy committed
23 24 25 26 27 28 29 30 31 32
SUCCESS=0		## Noice.
BAD_OPTION=1		## Incorrect usage.
TOO_MANY_ARGS=2		## More than one argument was provided.
MISSING_ARG=3		## Option -f or -d provided but argument was not provided.
BAD_ARG=4		## Specified or default file list not readable.
NO_RSYNC=5		## rsync not installed.
NO_REM_DIR=6		## ssh command to create remote directory failed.
BAD_LIST_FILE=7		## List file not identified as ascii text file.
NO_VALID_FILES=8	## Parsing list file  found no valid files to back up.
RSYNC_FAILED=9		## rsync command was reached but failed.
33 34 35 36 37 38 39 40 41 42 43

##########Function to print current date and time.  Used for logging.
TIMESTAMP () { echo -ne "$(date +%Y-%m-%d\ %T)"; }

##########Function for exit conditions.  Log error or success and exit.
QUIT ()
{
	if [ "${1}" -gt 0 ]; then	echo -e "$(TIMESTAMP) - Script failed with error code ${1}." >> "${BU_LOG_FILE}"
	else				echo -e "$(TIMESTAMP) - Script completed successfully." >> "${BU_LOG_FILE}"; fi
	exit "${1}"
}
44

clewsy's avatar
clewsy committed
45
##########Usage
46
USAGE="
47
Usage: $(basename "$0") [option] [file/list]
48
Where [file/list] is either:
clewsy's avatar
clewsy committed
49
	file	-	a specific file/directory to be backed up (requires option \"-f\" or \"-d\").
50 51
	list	-	a text list of files/directories to be backed up.
Valid options:
clewsy's avatar
clewsy committed
52 53
	-f	-	Argument is a specific file to be backed up.
	-d	-	Argument is a specific directory to be backed up.
54
	-l	-	Argument is a text file containing a list of file/directories to be backed up.
clewsy's avatar
clewsy committed
55
	-q	-	Quiet - suppress most output.
56
	-v	- 	Verbose - print additional info to stdout.
57 58 59 60
	-h	-	Print this usage and exit.
	none	-	No option entered - Default assumes \"-l\".
"

61
DEST="/dev/null"	## Default destination for command output.  I.e. don't display on screen.  -v (verbose) option changes this.
62

clewsy's avatar
clewsy committed
63
##########Interpret options
64
while getopts 'fdlqvh' OPTION; do		## Call getopts to identify selected options and set corresponding flags.
65
	case "$OPTION" in
66 67 68 69 70 71 72 73 74
		f)	ARGUMENT_TYPE="FILE" ;;	## -f identifies the provided argument as a directory/file to be backed up.
		d)	ARGUMENT_TYPE="FILE" ;;	## -d identifies the provided argument as a directory/file to be backed up.
		l)	ARGUMENT_TYPE="LIST" ;;	## -l identifies the argument as a list of files to be backed up.
		q)	QUIET_MODE="TRUE" ;;	## -q flag to suppress some output that would otherwise go to /dev/stdout.
		v)	DEST="/dev/stdout" ;;	## -v activates verbose mode by sending output to /dev/stdout (instead of /dev/null).
		h)	echo -e "$USAGE"	## -h option just prints the usage then quits.
			QUIT ${SUCCESS}	 ;;	## Exit successfully.
		?)	echo -e "${RED}Error:${RESET} Invalid option/s."
			echo -e "$USAGE"	## Invalid option, show usage.
75
			QUIT "${BAD_OPTION}" ;;	## Exit.
76 77
	esac
done
78
shift $((OPTIND -1))	## This ensures only non-option arguments are considered arguments when referencing $#, #* and $n.
79

80
##########Check correct usage
81 82
if [ $# -gt 1 ]; then						## Check if more than one argument was entered.
	echo -e "${RED}Error:${RESET} Too many arguments."	## If so, show usage and exit.
83
	echo -e "${USAGE}"
84
	QUIT "${TOO_MANY_ARGS}"
clewsy's avatar
clewsy committed
85 86
fi

87
if [ "${ARGUMENT_TYPE}" == "FILE" ] && [ $# -lt 1 ]; then	## Check if expected argument is a file but no argument entered.
88 89
	echo -e "${RED}Error:${RESET} Missing argument."	## If true, show usage and exit.
	echo -e "${USAGE}"					## (Note, no argument acceptable for -l option as default list file will be assumed).
90
	QUIT "${MISSING_ARG}"
91
fi
92

clewsy's avatar
clewsy committed
93
##########Validate argument
94 95 96 97 98
ARGUMENT=${1-"$(dirname "$0")/bu.list"}		## First argument is the file name of the list of files to be backed up or a specific file to be backed up..
						## If argument not provided, set default (bu.list in same dir as script).
						## Syntax: parameter=${parameter-default}
if [ ! -e "${ARGUMENT}" ]; then			## Check the argument exists
	echo -e	"${RED}Error:${RESET} File \"${ARGUMENT}\" does not exist."
99
	echo -e "${USAGE}"
100
	QUIT "${BAD_ARG}"
101 102
fi

clewsy's avatar
clewsy committed
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
#########Define array of options to be used by ssh.
SSH_OPTIONS=(	
	-4					## Use IPV4 (alternatively, -6 for IPV6).
	'-o StrictHostKeyChecking=no'		## Disable user verification for connecting to unknown (not yet authenticated) host.
	'-o UserKnownHostsFile=/dev/null'	## Disable automatically saving "newly discovered" hosts to the default knownhosts file.
	'-o BatchMode=yes'			## Disable password prompts and host key confirmation requests.
	'-o ConnectTimeout=4'			## Stop attempting the connection after specified number of seconds.
)
export RSYNC_RSH="ssh ${SSH_OPTIONS[*]}"	## Set the RSYNC_RSH environment variable so that rsync uses the same ssh options as the ssh commands.
echo -e "\nConfigured ssh options   : ${SSH_OPTIONS[*]}" > ${DEST}

#########Define array of options to be used by rsync.
RSYNC_OPTIONS=(
	--archive	## Archive mode, equivalent to -rlptgoD (no -H, -A, -X)
	--relative
	--verbose
	--human-readable
	--progress
)
## Note using --archive is equivalent to:
##	-r --recursive
##	-l --links (copy symlinks as symlinks)
##	-p --perms (preserve permissions)
##	-t --times (preserve modification times)
##	-g --group (preserve group)
##	-o --owner (preserve owner when run as superuser)
##	-D --devices preserve device files when run as superuser)
echo -e "Configured rsync options : ${RSYNC_OPTIONS[*]}" > ${DEST}

132 133 134 135 136
##########Verify if rsync is installed.
echo -e "\nChecking for rsync:" > ${DEST}
if ! command -v rsync >> /dev/null; then	## If rsync not installed (send to /dev/null to suppress stdout)
	echo -e "${RED}Error:${RESET} rsync not installed."
	QUIT ${NO_RSYNC}
clewsy's avatar
clewsy committed
137 138 139 140 141 142
else
	echo -e "${GREEN}Confirmation:${RESET} rsync installed." > ${DEST}
fi

##########Determine server hostname (i.e. use local network or remote network).
echo -e "\nChecking for local backup server availability." > ${DEST}
143
if ssh "${SSH_OPTIONS[@]}" "${BU_SERVER_LOCAL}" "exit" > ${DEST} 2>&1; then	## If an ssh connection to the local server is successful...
144
	BU_SERVER="${BU_SERVER_LOCAL}"							## Use the local server.
clewsy's avatar
clewsy committed
145 146
	echo "Using local server (${BU_SERVER})." > ${DEST}
else
147
	BU_SERVER="${BU_SERVER_REMOTE}"							## Otherwise, use the remote server.
clewsy's avatar
clewsy committed
148 149 150 151 152
	echo "Using remote server (${BU_SERVER})." > ${DEST}
fi

##########Validate the backup folder or create if absent.
echo -e "\nChecking for remote backup directory \"${BU_REMOTE_DIR}\" on remote backup server \"${BU_SERVER}\" (will be created if absent)." > ${DEST}
153
if ! ssh "${SSH_OPTIONS[@]}" ${BU_USER}@${BU_SERVER} "mkdir -p ${BU_REMOTE_DIR}" > ${DEST} 2>&1; then	## Connects to the remote server and creates the backup dir.
154 155
	echo -e "${RED}Error:${RESET} Failed to create remote directory."			## If this fails, print error and exit.
	QUIT ${NO_REM_DIR}
clewsy's avatar
clewsy committed
156 157 158
fi
echo -e "${GREEN}Remote backup directory \"${BU_REMOTE_DIR}\" validated.${RESET}" > ${DEST}

159
##########Create the temp list file.
160 161
TEMP_BU_FILE_LIST="/tmp/temp_bu_file_list"				## Define the temporary file which will contain a list of file/s to be backed up..
if [ -e "${TEMP_BU_FILE_LIST}" ]; then rm "${TEMP_BU_FILE_LIST}"; fi	## If it exists, delete the temp file (in case script failed previously before deleting).
162

163
##########Fill the temp list file (i.e. validate, strip comments).
164 165 166 167 168 169 170 171 172
if [ "${ARGUMENT_TYPE}" == "FILE" ]; then				## If provided argument is a specific file to be backed up (option -f)
	ARGUMENT="$(readlink -f "${ARGUMENT}")"				## Convert to full path (readlink -f will convert from relative path.)
	echo -e "\nBackup the following file: ${ARGUMENT}" > ${DEST}	## Print the file to be backed up.
	echo "${ARGUMENT}" > "${TEMP_BU_FILE_LIST}"			## Create the list of files to be backed up - in this case a list of one.
									## Use find to capture the absolute directory location of the file.
else									## Else if argument is not a specific file, assume it is a list of files.
	if	command -v file >> /dev/null && 								## If "file" is installed and...
		! file "${ARGUMENT}" | grep "ASCII text" >> /dev/null; then					## list file is not ascii text (as expected).
			echo -e "${RED}Error:${RESET} Bad backup list file (expecting ascii text file)."	## Then print usage and exit.
173
			echo -e "${USAGE}"
174
			QUIT ${BAD_LIST_FILE}
175
	else
176 177 178
		echo -e "\nBackup list is \"$ARGUMENT\". Checking files..." > ${DEST}	## Else the argument is assumed a list of files (option -l or no option).
		while read -r LINE ; do							## Iterate for every line in the backup file list.
			STRIPPED_LINE=$(echo "${LINE}" | tr -s " " | tr -d "\t" | cut -d "#" -f 1)	## Strip the comments.
179
													## 1) Squash any repeated spaces into a single space.
180
													## (Can't delete in case filename has spaces)
181 182 183
													## 2) Delete any tabs.
													## 3) Delete content of the line from the first '#'.
			if [ "${STRIPPED_LINE}" ]; then 						## If there is anything left of the stripped line.
184
				if [ "$(echo "${STRIPPED_LINE}" | cut -b ${#STRIPPED_LINE})" == " " ]; then			## If there is a trailing space left at the end...
185
					STRIPPED_LINE="$(echo "${STRIPPED_LINE}" | cut --complement -b ${#STRIPPED_LINE})"; fi	## Then delete the trailing space.
186 187 188 189
				FULL_PATH=${STRIPPED_LINE/#\~/$HOME}					## Expanded variable will be treated as a literal string.
				FULL_PATH=${FULL_PATH/\$HOME/$HOME}					## These two commands evaluate first "~" and then "$HOME"
													## then substitute either for the actual variable $HOME
													## Syntax: ${variable/string_match/replacement}
190 191 192 193 194 195 196

				for f in ${FULL_PATH}; do						## Loop to capture usecase that includes a wildcard '*' in FULL_PATH
					if [ -e "${f}" ]; then						## If the stripped and expanded line exists as a file
						echo -e "Adding: ${GREEN}${f}${RESET}" > ${DEST}	## Say so and then
						echo "${f}" >> "${TEMP_BU_FILE_LIST}"			## copy the stripped/expanded line to the temp file.
					else	echo -e "Failed: ${RED}${f}${RESET} does not exist and will be skipped"; fi	## Else skip the line.
				done
197 198
			fi
		done < "${ARGUMENT}"
199 200 201
		if [ ! -e "${TEMP_BU_FILE_LIST}" ]; then						## If the temp list file was not created
			echo -e "${RED}Error: ${RESET}The list file did not list any valid files."	## Then it didn't contain any valid files.
			echo -e "${USAGE}"								## So print usage and exit.
202
			QUIT "${NO_VALID_FILES}"; fi
203
	fi
204 205
fi

206
##########Run the sync.
207 208 209 210 211 212 213 214 215
echo > ${DEST}	## Log file will show rsync was attempted.
{
	echo -e "$(TIMESTAMP) - Attempting rsync backup to ${BU_SERVER}..."
	echo -e "-------------File list:-------------"
	cat ${TEMP_BU_FILE_LIST}
	echo -e "------------------------------------"
 } >> "${BU_LOG_FILE}"

if [ "${QUIET_MODE}" != "TRUE" ]; then echo -e "${BLUE}Using rsync to copy listed files to \"${RESET}${BU_USER}@${BU_SERVER}:${BU_REMOTE_DIR}/${BLUE}\"${RESET}"; fi
clewsy's avatar
clewsy committed
216
if ! rsync "${RSYNC_OPTIONS[@]}" --files-from="${TEMP_BU_FILE_LIST}" / "${BU_USER}@${BU_SERVER}:${BU_REMOTE_DIR}/" > ${DEST} 2>&1; then
217
	echo -e "${RED}Error:${RESET} Sync failed."	## If rsync failed
218
	QUIT "${RSYNC_FAILED}"
219 220 221
else	
	echo -e "${GREEN}Success.${RESET}"			
fi
clewsy's avatar
clewsy committed
222

223 224
##########Delete the temp file.
rm "${TEMP_BU_FILE_LIST}"
225

226
##########All done.
227
echo -e "\n${GREEN}Success:${RESET} Script complete.\n" > ${DEST}
228
QUIT "${SUCCESS}"