Skip to content

Instantly share code, notes, and snippets.

@shoaibi
Created June 25, 2015 16:51
Show Gist options
  • Save shoaibi/7824cc7ce558ba4dfc74 to your computer and use it in GitHub Desktop.
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
#!/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.
#!/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