#!/bin/bash
#
# propagate.sh v1.1 2006/04
#
# Shell script for sending multiple files to multiple
# remote hosts via secure copy (scp).
#
# Copyright (C) 2006 by Pedro Venda < pjvenda (at) pjvenda org >
#
# Distributed under the terms of the GNU Public License Agreement (GPLv2)
# http://www.fsf.org/licensing/licenses/gpl.html or
# http://www.fsf.org/licensing/licenses/gpl.txt
#

#
# Usage
# =====
#
# Setup configuration files:
#   * local_files.conf (file1
#                     file2)
#   * hosts.conf (host1
#               host2)
#   * rem_dest.conf (username:destination_path)
#   * comments are prefixed by '#' and ignored by the script 
#
# Run script
#   ./propagate.sh
#   (best used with ssh keys, of course...)
#
# Changelog
# =========
#
# v1.1 2006/04/02
# - dependency checks: external binaries
# - check for needed directories
# - check for configuration files
# - new configuration file: rem_dest.conf
# - structured coding style
# - logging 
#
# v1.0 2006/03/31
# - initial version
#

# relevant binaries
CAT=/bin/cat
SSH=/usr/bin/ssh
SCP=/usr/bin/scp
CUT=/bin/cut
EGREP=/bin/egrep
RM=/bin/rm
DATE=/bin/date

# directories
BASE_DIR=.
CONF_DIR=${BASE_DIR}/conf
FILES_DIR=${BASE_DIR}/files
LOG_DIR=${BASE_DIR}/log

# configuration files
FILES_CONF=${CONF_DIR}/local_files.conf
HOSTS_CONF=${CONF_DIR}/hosts.conf
DEST_CONF=${CONF_DIR}/rem_dest.conf

# logfile (filename or '-' for stdout)
#LOG_FILE=${LOG_DIR}/propagate.log
LOG_FILE=-

# ignore comments on configuration files with this REGEXP
LINE_IGNORE="(^#.*|^ *$|^$)"

SL=">>>>>>>> --"
SR="-- <<<<<<<<"

#
# calculates current time
# do_time generates time string like hh:mm:ss on $TIME
# do_date generates date string like yyyy-mm-dd on $TODAY
# do_now generates time string like yyyy-mm-dd hh:mm:ss on $NOW
#
# usage: do_time
# usage: do_date
# usage: do_now
function do_time() {
        TIME=$(${DATE} +"%T")
}
function do_date() {
        TODAY=$(${DATE} +"%F")
}
function do_now() {
        do_time
        do_date
        NOW="${TODAY} ${TIME}"
}

#
# check for files and config directories existence and permissions
#
# usage: check_dirs
#
function check_dirs() {
	echo -n "checking for necessary directories... "
	if [ ! -d ${BASE_DIR} ] || [ ! -x ${BASE_DIR} ]; then
		echo "unable to find or read ${BASE_DIR}"
		return 0
	fi
	if [ ! -d ${CONF_DIR} ] || [ ! -x ${CONF_DIR} ]; then
		echo "unable to find or read ${CONF_DIR}"
		return 0
	fi
	if [ ! -d ${FILES_DIR} ] || [ ! -x ${FILES_DIR} ]; then
		echo "unable to find or read ${FILES_DIR}"
		return 0
	fi
	echo "ok"
	return 1
}

#
# check for configuration files existence and readability
#
# usage: check_config_files
#
function check_config_files() {
	echo -n "checking configuration files... "
	if [ ! -r ${FILES_CONF} ]; then
		echo "unable to find or read ${FILES_CONF}"
		return 0
	fi
	if [ ! -r ${HOSTS_CONF} ]; then
		echo "unable to find or read ${HOSTS_CONF}"
		return 0
	fi
	if [ ! -r ${DEST_CONF} ]; then
		echo "unable to find or read ${DEST_CONF}"
		return 0
	fi
	echo "ok"
	return 1
}

#
# check for log file writability
#
# usage: check_log_file
#
function check_log_files() {
	echo -n "checking log files... "
	if [ "${LOG_FILE}" == "-" ]; then
		echo "stdout:ok"
		return 1
	fi
	if [ ! -d ${LOG_DIR} ] || [ ! -x ${LOG_DIR} ]; then
		echo "unable to find or read ${LOG_DIR}"
		return 0
	fi
	if [ ! -w ${LOG_FILE} ]; then
		echo "unable to find or write ${LOG_FILE}"
		return 0
	fi
	echo "${LOG_FILE}:ok"
	return 1
}

#
# check external binary dependecies
#
# usage: check_dep
#
function check_dep() {
	echo -n "checking dependencies... "
	if [ ! -x ${CAT} ]; then
		echo "unable to execute or find ${CAT}"
		return 0
	fi
	if [ ! -x ${DATE} ]; then
		echo "unable to execute or find ${DATE}"
		return 0
	fi
	if [ ! -x ${SSH} ]; then
		echo "unable to execute or find ${SSH}"
		return 0
	fi
	if [ ! -x ${SCP} ]; then
		echo "unable to execute or find ${SCP}"
		return 0
	fi
	if [ ! -x ${CUT} ]; then
		echo "unable to execute or find ${CUT}"
		return 0
	fi
	if [ ! -x ${EGREP} ]; then
		echo "unable to execute or find ${EGREP}"
		return 0
	fi
	if [ ! -x ${RM} ]; then
		echo "unable to execute or find ${RM}"
		return 0
	fi
	echo "ok"
	return 1
}

#
# parse $FILES_CONF configuration files and generate a
# string with all the files to be propagated
# That string will be placed on the global variable ${FILES}
#
# usage: parse_files_config $CONFIG_FILE
function parse_files_config() {
	local CONFIG_FILE=${1}
	do_time
	echo "${TIME} - parsing files config file (${CONFIG_FILE}):"
	# find and create list of files from ${CONFIG_FILE}
	while read LINE; do
		if [ "${FILES}" == "" ]; then
			FILES="${FILES}${FILES_DIR}/${LINE}"
		else
			FILES="${FILES} ${FILES_DIR}/${LINE}"
		fi
		echo "  * ${LINE}"
	done < <(${CAT} ${CONFIG_FILE} | ${EGREP} -v "${LINE_IGNORE}")
}

#
# parse $DEST_CONF configuration files and generate a
# two global variables with DESTINATION and USERNAME
#
# usage: parse_dest_config $CONFIG_FILE
function parse_dest_config() {
	local CONFIG_FILE=${1}
	do_time
	echo "${TIME} - parsing destination config file (${CONFIG_FILE}):"
	while read LINE; do
		USERNAME=$(echo ${LINE} | cut -d ':' -f 1)
		DESTINATION=$(echo ${LINE} | cut -d ':' -f 2)
	done < <(${CAT} ${CONFIG_FILE} | ${EGREP} -v "${LINE_IGNORE}")
	echo "  * destination: ${DESTINATION}"
	echo "  * username: ${USERNAME}"
}

#
# parse $HOSTS_CONF configuration files and generate a
# string with all the hosts to be propagated
# That string will be placed on the global variable ${HOSTS}
#
# usage: parse_hosts_config $CONFIG_FILE
function parse_hosts_config() {
	local CONFIG_FILE=${1}
	do_time
	echo "${TIME} - parsing hosts config file (${CONFIG_FILE}):"
	# find and create list of hosts from ${CONFIG_FILE}
	while read LINE; do
		if [ "${HOSTS}" == "" ]; then
			HOSTS="${LINE}"
		else
			HOSTS="${HOSTS} ${LINE}"
		fi
		echo "  * ${LINE}"
	done < <(${CAT} ${CONFIG_FILE} | ${EGREP} -v "${LINE_IGNORE}")
}

#
# propagate $files into $usename@$hosts:$destination
#
# usage: propagate "$files" "$hosts" "$username" "$destination"
function propagate() {
	local FILES=${1}
	local HOSTS=${2}
	local USERNAME=${3}
	local DESTINATION=${4}

	for HOST in ${HOSTS}; do
		HOST_DEST="${USERNAME}@${HOST}:${DESTINATION}"
		do_time
		echo "${TIME} - processing host: ${HOST}"
		${SCP} ${FILES} ${HOST_DEST}
	done
}

if [ ! "${LOG_FILE}" == "-" ]; then
        # copy stdout into file descriptor 6
        exec 6>&1
        # redirect stdout into logfile
        exec 1>>${LOG_FILE}
fi

# start run log line
do_now
echo "${SL} propagate.sh start run at ${NOW} ${SR}"

# do all sorts of checks before actually doing real work
check_dirs
if [ $? == 0 ]; then
	exit 1
fi
# check files
check_config_files
if [ $? == 0 ]; then
	exit 1
fi
# check dependencies
check_dep
if [ $? == 0 ]; then
	exit 1
fi
# check log files
check_log_files
if [ $? == 0 ]; then
	exit 1
fi

# parse files config file
parse_files_config ${FILES_CONF}
# parse hosts config file
parse_hosts_config ${HOSTS_CONF}
# parse destination config file
parse_dest_config ${DEST_CONF}

# propagate $FILES into $USERNAME@$HOSTS:$DESTINATION
propagate "${FILES}" "${HOSTS}" "${USERNAME}" "${DESTINATION}"

# end run log line
do_now
echo "${SL} propagate.sh end run   at ${NOW} ${SR}"
