#! /bin/bash
# vim: cindent:shiftwidth=4:tabstop=4:smarttab:textwidth=100

# PostgreSQL database backup
# dump all databases to .sql files
# compress them and create differentials

# [restore procedure]
# lzop -d roundcube.2012-07-19.sql.lzop
# lz4 -d roundcube.2012-07-19.sql.lz4
# xdelta3 -d roundcube.2012-07-20.sql.vcdiff
# psql roundcube < roundcube.2012-07-20.sql

set -o errexit
set -o nounset
set -o pipefail
test ${SUDO_USER+xxx} && set -o xtrace

#CHANGELOG
#0.5.0	initial, based on mysql-backup

# compression algorithm selection
if [[ -x '/usr/bin/lz4' ]]; then
	declare -r COMPRESSION='lz4'
elif [[ -x '/usr/bin/lzop' ]]; then
	declare -r COMPRESSION='lzop'
else
	echo 'no supported compression programs found'
	exit 1
fi

NICE='nice ionice -c3'
DATE=$(date --rfc-3339=date)
MAX_FULL=2	#number of full backups to keep

TMP_DIR=$(mktemp -d)
DST_DIR='/srv/postgresql-backup'
if [[ ! -d ${DST_DIR} ]]; then
    mkdir -p ${DST_DIR}
fi
cd $DST_DIR

# restrict permissions to protect data
umask 'u=r,g=r,o='

DBs=($(su postgres -c "psql -q -t -c 'SELECT datname FROM pg_catalog.pg_database'"))
for DB_NAME in ${DBs[*]}; do
    # Back up only these explicitly-permitted databases
    # i.e. skip internal, datascout, etc. databases
    case $DB_NAME in
        minervaengagems)
            # Minerva Engage microservice (mnengdb)
            true
        ;;
        *)
            continue
        ;;
    esac

	# list of full backups
	FULLs=($(ls -1 --sort=time $DB_NAME.*.$COMPRESSION || true))

	DST_FILE="$DB_NAME.$DATE.sql"
	su postgres -c "$NICE pg_dump $DB_NAME" | $NICE $COMPRESSION > "$DST_FILE".tmp
	mv --force "$DST_FILE".tmp "$DST_FILE".$COMPRESSION

	day_of_week=$(date +%w) #(0..6); 0 is Sunday
	if [[ $day_of_week -gt 0 && ${#FULLs[*]} -gt 0 ]]; then
		# convert to differential
		# on days other than Sunday
		LAST_FULL=${FULLs[0]}
		XS=$TMP_DIR/${LAST_FULL%.*}
		XD=$TMP_DIR/$DST_FILE
		mkfifo $XS $XD
		$NICE $COMPRESSION --decompress < $LAST_FULL > $XS &
		$NICE $COMPRESSION --decompress < $DST_FILE.$COMPRESSION > $XD &
		$NICE xdelta3 -s $XS $XD > $DST_FILE.vcdiff
		rm $DST_FILE.$COMPRESSION
	fi

	# cleanup old full backups
	i=$MAX_FULL
	while [[ $i -lt ${#FULLs[*]} ]]; do
		rm ${FULLs[$i]}
		i=$[$i+1]
	done
	# rescan the list of full backups
	# and cleanup old partial backups older than the oldest full backup
	FULLs=($(ls -1 --sort=time $DB_NAME.*.$COMPRESSION))
	find . -xdev -maxdepth 1 -type f -name '*.vcdiff' -not -newer ${FULLs[$[${#FULLs[*]}-1]]} -print0 |
		xargs --null --no-run-if-empty rm --
done

# cleanup old backups (deleted databases, etc.)
find $DST_DIR -xdev -maxdepth 1 -type f -mtime +$((MAX_FULL*7*2)) -print0 |
	xargs --null --no-run-if-empty rm --verbose --

rm --recursive $TMP_DIR
