Created
June 25, 2015 16:51
-
-
Save shoaibi/7824cc7ce558ba4dfc74 to your computer and use it in GitHub Desktop.
Take a backup of specified profiles from your machine to a samba share if connected to home network while sending the backup report to a remote url
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
verbose_mode=0 # Set this to 1 or 0 to enable/disable outputting all log messages. Enabling this causes some known issues with rsync output. keep it disabled. | |
ip_check_url='http://myip.dnsomatic.com/' #URL that would show our current ip. This url should only show IP and nothing else, no other content, no html tags, etc... | |
home_network_ip='1.1.1.1' #External IP that we should be on for the backup to trigger | |
curl_url='http://reportingserver.com/backup-report.php' #url that should get alerts for cron jobs | |
signatures='1234567890' #random string to verify requests on curl_url script | |
rsync_exclusive=1 #whether or not we should exit if there is already an rsync process running, values could be 1 or 0 | |
rsync_additional_opts='--verbose' # any additional switches that we may want to add, an example could be --verbose | |
backup_alert_period=10 #alert me if no backup has run after this many days | |
#backup profile configuration: | |
# Format of each profile: /absolute/path/to/source2;relative/path/to/destionation2;1 | |
# The last "1" marks this profile as enabled. change it to 0 to skip that profile. | |
backup_profiles[0]='/etc;rsync_test/Localhost;1' | |
#backup_profiles[1]='/home/shoaibi/tmp/assignment;backups/assignment;0' | |
#backup_profiles[2]='/home/shoaibi/tmp/configs;backups/configs_backup;1' | |
#backup_profiles[3]='/home/shoaibi/tmp/dir_does_not_exist;backups/something;1' | |
#backup_profiles[4]='/misc/configured.path'; | |
smb_host='homenas' #IP address/hostname of the machine where share is defined | |
smb_user='guest' #Username used with the password defined below to connect to share | |
smb_password= #Password that should be used to connect to share. | |
smb_drive='/backups' #absolute path of the remote share | |
default_mount_point='/Volumes/backups' #If you have configured samba share to be automounted in fstab define this with the path used in fstab | |
unmount_on_completion=1 #whether or not we should unmount remote share when we are done. This is only applicable if we mounted share ourselves. | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
#my_dir=$(realpath $(dirname ${0})) | |
my_dir=$(cd "$(dirname "backup.sh")"; pwd) | |
source "${my_dir}/backup.config.sh" | |
# anything defined in backup.config.sh can be overriden here | |
mount_point="${my_dir}/.share" #directory where we should mount remote samba share if it is not mounted | |
rsync_default_opts=" --archive --compress --sparse " | |
rsync_opts=" ${rsync_default_opts} ${rsync_additional_opts} " # additional rsync options | |
rsync_summary_opts=" ${rsync_default_opts} --stats --dry-run" | |
rsync_bin=$(which rsync) | |
pid_file_name="${0}.pid" | |
pid_file_path="${my_dir}/.run" | |
status_file_name=".${0}.status" | |
status_file_path="${my_dir}" | |
log_file_name="$$.${0}.log" | |
log_file_path="${my_dir}/.logs" | |
rsync_log_file_name="$$.rsync.log" | |
rsync_err_file_name="$$.rsync.err.log" | |
rsync_stats_log="$$.rsync.stats.log" | |
hostname=$(hostname) | |
user=$(whoami) | |
date_now=$(date '+%F') | |
function date_to_timestamp() | |
{ | |
echo $(date -j -f "%Y-%m-%d" "${1}" "+%s") | |
} | |
function timestamp_to_date() | |
{ | |
echo $(date -j -f "%s" "${1}" "+%Y-%m-%d") | |
} | |
function date_diff() | |
{ | |
echo $(((`date -jf %Y-%m-%d ${1} +%s` - `date -jf %Y-%m-%d ${2} +%s`)/86400)) | |
} | |
function make_dir() | |
{ | |
mkdir -p "${1}" &> /dev/null | |
} | |
function log() | |
{ | |
index="$(date +'%s').${RANDOM}" | |
log_timestamp=$(date '+%a %b %d, %Y %k:%M:%S') | |
if [[ $verbose_mode -eq 1 ]]; then | |
echo "[ ${log_timestamp}] [ ${hostname} / ${user} ] [${0} / ${$}] [ ${3}:${4} ] [${1}] ${2}" | |
fi | |
echo "${index}[timestamp]="${log_timestamp}"&" 1>> "${log_file_path}/${log_file_name}" 2> /dev/null | |
echo "${index}[hostname]=${hostname}&" 1>> "${log_file_path}/${log_file_name}" 2> /dev/null | |
echo "${index}[user]=${user}&" 1>> "${log_file_path}/${log_file_name}" 2> /dev/null | |
echo "${index}[process]=${0}&" 1>> "${log_file_path}/${log_file_name}" 2> /dev/null | |
echo "${index}[pid]=$$&" 1>> "${log_file_path}/${log_file_name}" 2> /dev/null | |
echo "${index}[function]=${3}&" 1>> "${log_file_path}/${log_file_name}" 2> /dev/null | |
echo "${index}[line]=${4}&" 1>> "${log_file_path}/${log_file_name}" 2> /dev/null | |
echo "${index}[type]=${1}&" 1>> "${log_file_path}/${log_file_name}" 2> /dev/null | |
echo "${index}[message]=${2}&" 1>> "${log_file_path}/${log_file_name}" 2> /dev/null | |
} | |
function log_error() | |
{ | |
log "Error" "${1}" "${2}" "${3}" | |
} | |
function log_warn() | |
{ | |
log "Warn" "${1}" "${2}" "${3}" | |
} | |
function log_info() | |
{ | |
log "Info" "${1}" "${2}" "${3}" | |
} | |
#$1= message, $2=1|0 do post or not | |
function suicide() | |
{ | |
log_error "${1}" "${2}" "${3}" | |
cleanup "${4}" | |
} | |
function update_status_file() | |
{ | |
log_info "Updating status file" "${FUNCNAME}" "${LINENO}" | |
if [ "x${1}" = "x" ]; then | |
content="${date_now}" | |
else | |
content="${1}" | |
fi | |
echo "${content}" > "${status_file_path}/${status_file_name}" | |
} | |
function send_post() | |
{ | |
curl -k -X POST "${curl_url}" -d @"${log_file_path}/${log_file_name}" -H "${0}_signatures:${signatures}" | |
curl_status=$? | |
if [ $curl_status -ne 0 ]; then | |
echo "Sending POST request to ${curl_url} failed" | |
exit 1 | |
else | |
echo "POST request sent to ${curl_url}" | |
fi | |
} | |
function cleanup() | |
{ | |
if [ "${unmount_on_completion}" = "1" ]; then | |
if [ $(check_if_mounted_on_give_dir "${smb_drive}" "${mount_point}") -eq 0 ]; then | |
echo "Unmounting ${smb_drive}" | |
umount "${smb_drive}" | |
fi | |
fi | |
if [ "x${1}" = "x" -o "${1}" = "1" ]; then | |
send_post | |
fi | |
echo "Cleaning up temporary files" | |
rm -rf "${log_file_path}/${log_file_name}" | |
rm -rf "${pid_file_path}/${pid_file_name}" | |
exit | |
} | |
function pid_process_exists() | |
{ | |
ps -p "${1}" &> /dev/null | |
echo $? #1 means no, 0 means yes | |
} | |
function process_exists() | |
{ | |
pidof "${1}" &> /dev/null | |
echo $? #1 means no, 0 means yes | |
} | |
function file_exists() | |
{ | |
ls "${1}" &> /dev/null | |
echo $? #1 means no, 0 means yes | |
} | |
function find_current_external_ip() | |
{ | |
echo $(curl "${ip_check_url}" 2> /dev/null) | |
} | |
function update_pid_file() | |
{ | |
log_info "Updating pid file to current process id: $$" "${FUNCNAME}" "${LINENO}" | |
echo $$ > "${1}" | |
} | |
function check_if_mounted_on_give_dir() | |
{ | |
mount | grep "${1}" | grep -q "${2}" | |
echo $? #1 means not mounted, 0 means mounted | |
} | |
function mount_media() | |
{ | |
mount_smbfs "//${1}:${2}@${3}${4}" "${5}" &> /dev/null | |
check_if_mounted_on_give_dir "${4}" "${5}" | |
} | |
function signal_handler() | |
{ | |
log_error "Caught termination signal, probably sent by user. Exiting" "${FUNCNAME}" "${LINENO}" | |
cleanup | |
} | |
#step 0 | |
function ensure_required_dirs_exist() | |
{ | |
make_dir "${mount_point}" | |
make_dir "${log_file_path}" | |
make_dir "${pid_file_path}" | |
} | |
#step 1 | |
function check_last_run() | |
{ | |
status_file="${status_file_path}/${status_file_name}" | |
if [ -f "${status_file}" ]; then | |
log_info "Status file exists." "${FUNCNAME}" "${LINENO}" | |
last_successful_run=$(head -n1 ${status_file}) #we only need one line. | |
log_info "Last successful run was on ${last_successful_run}" "${FUNCNAME}" "${LINENO}" | |
if [ $(date_diff "${date_now}" "${last_successful_run}") -gt "${backup_alert_period}" ]; then | |
log_error "Last successful run was more than ${backup_alert_period} days ago." "${FUNCNAME}" "${LINENO}" | |
send_post | |
else | |
log_info "Last successful run was within last ${backup_alert_period} days" "${FUNCNAME}" "${LINENO}" | |
fi | |
else | |
log_info "Creating status file with date of 9 days ago from now." "${FUNCNAME}" "${LINENO}" | |
timestamp_now=$(date_to_timestamp "${date_now}") | |
timestamp_nine_days_ago=$(($timestamp_now - $((60*60*24*9)))) | |
date_nine_days_ago=$(timestamp_to_date "$timestamp_nine_days_ago") | |
update_status_file "${date_nine_days_ago}" | |
fi | |
} | |
# step 2 | |
function check_network() | |
{ | |
current_live_ip=$(find_current_external_ip) | |
if [ "${current_live_ip}" = "${home_network_ip}" ]; then | |
log_info "We are running on home network(${home_network_ip})." "${FUNCNAME}" "${LINENO}" | |
else | |
echo "We(Current IP: ${current_live_ip}) are not running on home network(${home_network_ip})."; | |
cleanup 0 | |
fi | |
} | |
# step 3 | |
function check_rsync_exclusiveness() | |
{ | |
log_info "Checking if we should run in rsync exclusive mode." "${FUNCNAME}" "${LINENO}" | |
if [ "${rsync_exclusive}" = "1" ]; then | |
log_info "rsync exclusive mode is enabled." "${FUNCNAME}" "${LINENO}" | |
if [ $(process_exists 'rsync') -eq 0 ]; then | |
suicide "Rsync processes exist." "${FUNCNAME}" "${LINENO}" | |
else | |
log_info "No existing rsync processes found." "${FUNCNAME}" "${LINENO}" | |
fi | |
else | |
log_info "rsync exclusive mode is disabled" "${FUNCNAME}" "${LINENO}" | |
fi | |
} | |
# step 4 | |
function pid_sanity_checks() | |
{ | |
log_info "Checking pid sanity" "${FUNCNAME}" "${LINENO}" | |
pid_file="${pid_file_path}/${pid_file_name}" | |
log_info "Pid file is : ${pid_file}" "${FUNCNAME}" "${LINENO}" | |
if [ $(file_exists "${pid_file}") -eq 0 ]; then | |
log_info "Pid file already exists. Checking if the pid inside file is actually running." "${FUNCNAME}" "${LINENO}" | |
old_pid=$(head -n1 "${pid_file}") #just read one line as thats as much as we need. | |
log_info "Pid being checked for exection is ${old_pid}" "${FUNCNAME}" "${LINENO}" | |
if [ $(pid_process_exists "${old_pid}") -eq 0 ]; then | |
suicide "Previous ${0} process is still running" "${FUNCNAME}" "${LINENO}" | |
else | |
log_warn "Dangling pid file found." "${FUNCNAME}" "${LINENO}" | |
update_pid_file "${pid_file}" | |
fi | |
else | |
log_info "Creating a new Pid file" "${FUNCNAME}" "${LINENO}" | |
update_pid_file "${pid_file}" | |
fi | |
} | |
#step 5: | |
function mount_point_preparation() | |
{ | |
log_info "Checking if remote share is already mounted on ${default_mount_point}" "${FUNCNAME}" "${LINENO}" | |
if [ "x${default_mount_point}" != "x" -a $(check_if_mounted_on_give_dir "${smb_drive}" "${default_mount_point}" ) -eq 0 ]; then | |
log_info "${smb_drive} already mounted at ${default_mount_point}." "${FUNCNAME}" "${LINENO}" | |
mount_point="${default_mount_point}" | |
unmount_on_completion=0 | |
else | |
log_info "${smb_drive} was not mounted." "${FUNCNAME}" "${LINENO}" | |
if [ $(mount_media "${smb_user}" "${smb_password}" "${smb_host}" "${smb_drive}" "${mount_point}" ) -eq 0 ]; then | |
log_info "${smb_drive} was successfully mounted at ${mount_point}" "${FUNCNAME}" "${LINENO}" | |
else | |
suicide "Unable to mount ${smb_drive} on ${mount_point}" "${FUNCNAME}" "${LINENO}" | |
fi | |
fi | |
} | |
#step 6: | |
function process_backup_profiles() | |
{ | |
log_info "Processing backup profiles" "${FUNCNAME}" "${LINENO}" | |
log_info "Number of backup profiles founds: ${#backup_profiles[@]}" "${FUNCNAME}" "${LINENO}" | |
for backup_profile in "${backup_profiles[@]}" | |
do | |
absolute_source_path=$(echo ${backup_profile} | awk -F ';' '{print $1}') | |
relative_target_path=$(echo ${backup_profile} | awk -F ';' '{print $2}') | |
enabled=$(echo ${backup_profile} | awk -F ';' '{print $3}') | |
absolute_target_path="${mount_point}/${relative_target_path}" | |
log_info "Processing: ${absolute_source_path} -> ${absolute_target_path}" "${FUNCNAME}" "${LINENO}" | |
if [ "x${absolute_source_path}" = "x" ]; then | |
log_error "Source path could not be extracted" "${FUNCNAME}" "${LINENO}" | |
elif [ "x${relative_target_path}" = "x" ]; then | |
log_error "Target path could not be extracted" "${FUNCNAME}" "${LINENO}" | |
#elif [ ! -e "${absolute_source_path}" ]; then | |
# log_error "Source path does not exist" "${FUNCNAME}" "${LINENO}" | |
elif [ "${enabled}" -ne 1 ]; then | |
log_info "${absolute_source_path} -> ${absolute_target_path} profile is not enabled. Skipping..." "${FUNCNAME}" "${LINENO}" | |
else | |
log_info "Doing a dry-run to ensure actual rsync would run fine" "${FUNCNAME}" "${LINENO}" | |
log_info "Running" ${rsync_bin} ${rsync_summary_opts} "${absolute_source_path}" "${absolute_target_path}" | |
${rsync_bin} ${rsync_summary_opts} "${absolute_source_path}" "${absolute_target_path}" > >(tee "${log_file_path}/${rsync_stats_log}") 2> >(tee "${log_file_path}/${rsync_err_file_name}" >&2) | |
#${rsync_bin} ${rsync_summary_opts} "${absolute_source_path}" "${absolute_target_path}" 1> "${log_file_path}/${rsync_stats_log}" 2> "${log_file_path}/${rsync_err_file_name}" | |
rsync_status=$? | |
if [ $rsync_status -ne 0 ]; then | |
log_error "Dry run for ${absolute_source_path} -> ${absolute_target_path} failed. Error messages were: `cat ${log_file_path}/${rsync_err_file_name} | tr '\n' ';'`" "${FUNCNAME}" "${LINENO}" | |
log_error "Skipping to next profile." "${FUNCNAME}" "${LINENO}" | |
else | |
log_info "Dry run executed fine. Here are some stats for the backup profile to be processed: `cat ${log_file_path}/${rsync_stats_log} | tr '\n' ';'`" "${FUNCNAME}" "${LINENO}" | |
log_info "Starting actual rsync process" "${FUNCNAME}" "${LINENO}" | |
log_info "Running" ${rsync_bin} ${rsync_opts} "${absolute_source_path}" "${absolute_target_path}" | |
${rsync_bin} ${rsync_opts} "${absolute_source_path}" "${absolute_target_path}" > >(tee "${log_file_path}/${rsync_log_file_name}") 2> >(tee "${log_file_path}/${rsync_err_file_name}" >&2) | |
#${rsync_bin} ${rsync_opts} "${absolute_source_path}" "${absolute_target_path}" 1> "${log_file_path}/${rsync_log_file_name}" 2> "${log_file_path}/${rsync_err_file_name}"; echo $?) | |
rsync_status=$? | |
if [ $rsync_status -ne 0 ]; then | |
log_error "Rsync for ${absolute_source_path} -> ${absolute_target_path} failed. Error messages from rsync: `cat ${log_file_path}/${rsync_err_file_name} | tr '\n' ';'`" "${FUNCNAME}" "${LINENO}" | |
else | |
log_info "Rsync for ${absolute_source_path} -> ${absolute_target_path} completed without errors." "${FUNCNAME}" "${LINENO}" | |
fi | |
log_info "And here is the standard output from rsync: `cat ${log_file_path}/${rsync_log_file_name} | tr '\n' ';'`" "${FUNCNAME}" "${LINENO}" | |
fi | |
rm -rf "${log_file_path}/${rsync_log_file_name}" | |
rm -rf "${log_file_path}/${rsync_stats_log}" | |
rm -rf "${log_file_path}/${rsync_err_file_name}" | |
fi | |
done | |
log_info "Processed all backup profiles" "${FUNCNAME}" "${LINENO}" | |
update_status_file | |
cleanup | |
} | |
trap 'signal_handler' 2 | |
trap 'signal_handler' 15 | |
trap 'signal_handler' SIGINT | |
trap 'signal_handler' SIGTERM | |
ensure_required_dirs_exist | |
check_last_run | |
check_network | |
check_rsync_exclusiveness | |
pid_sanity_checks | |
mount_point_preparation | |
process_backup_profiles | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment