#!/bin/sh

HOME=/root
PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/sbin:/usr/local/bin

usage()
{
	echo "$(basename $0): [-hnlR] COMMAND"
	echo ""
	echo "    -h                Print this help"
	echo "    -n                Dry-run"
	echo "    -l                Send output to syslog daemon.notice"
	echo "    -R <S3 region>    Use S3 instead of WebDAV, pointing at this OVH region"
	echo "    -S <subject>      Specify the subject of the backup to lookup instead auto-detecting"
	echo "    -k <key>          Path to X.509 key"
	echo "    -c <crt>          Path to X.509 cert"
	echo "    -C <CA crt>       Path to X.509 CA cert"
	echo ""
	echo "    $(basename $0) full"
	echo "    $(basename $0) run"
	echo "    $(basename $0) status"
	echo "    $(basename $0) list"
	echo "    $(basename $0) verify-fast"
	echo "    $(basename $0) verify"
	echo "    $(basename $0) cleanup"
	echo "    $(basename $0) remove-old-sets"
	echo "    $(basename $0) restore [-R <S3 region>] <relpath> <dest> <YYYY-MM-DDTHH:MM:SS>"
	echo ""
}

die()
{
	echo "$(basename $0): error: $1" >&2
	exit 1
}

gen_canary() {
	date +%Y-%m-%d > /$verify_canary
	dd if=/dev/urandom status=none bs=1k count=1 | openssl base64 >> /$verify_canary
}

setup_target()
{
	export RCLONE_CONFIG=/dev/null
	if [ -z "$restore_region" ]; then
		export RCLONE_CONFIG_DUPLICITY_TYPE="webdav"
		export RCLONE_CONFIG_DUPLICITY_URL="https://backups.ca-east-1.minmaxiste.com"
		export RCLONE_CA_CERT="$ca_cert"
		export RCLONE_CLIENT_KEY="$key"
		export RCLONE_CLIENT_CERT="$cert"
	else
		if [ "$restore_region" = "ca-east-tor" ]; then
			rclone_target="duplicity:/minmaxiste-backups/$subject"
		else
			rclone_target="duplicity:/minmaxiste-backups-eu/$subject"
		fi
		target="rclone://$rclone_target"
		export RCLONE_CONFIG_DUPLICITY_TYPE="s3"
		if [ -z "$RCLONE_CONFIG_DUPLICITY_ACCESS_KEY_ID" ]; then
			read -p "S3 access key ID: " RCLONE_CONFIG_DUPLICITY_ACCESS_KEY_ID
		fi
		if [ -z "$RCLONE_CONFIG_DUPLICITY_SECRET_ACCESS_KEY" ]; then
			read -p "S3 secret access key: " RCLONE_CONFIG_DUPLICITY_SECRET_ACCESS_KEY
		fi
		export RCLONE_CONFIG_DUPLICITY_ACCESS_KEY_ID
		export RCLONE_CONFIG_DUPLICITY_SECRET_ACCESS_KEY
		export RCLONE_CONFIG_DUPLICITY_REGION="$restore_region"
		export RCLONE_CONFIG_DUPLICITY_PROVIDER="Other"
		export RCLONE_CONFIG_DUPLICITY_ENDPOINT="https://s3.${restore_region}.io.cloud.ovh.net/"
		export RCLONE_CONFIG_DUPLICITY_ACL="private"
	fi
}

do_restore()
{
	relpath="$1"
	dest="$2"
	time="$3"

	if [ -z "$relpath" -o -z "$dest" ]; then
		usage
		exit 1
	fi

	if [ ! -z "$time" ]; then
		time="--time $time"
	fi

	duplicity \
		--archive-dir $cachedir \
		--tempdir=$tmpdir \
		--verbosity $verbosity \
		restore \
		--path-to-restore "$relpath" \
		$time \
		$target "$dest"
}

do_full()
{
	[ "$subject" != "$cert_subject" ] && die "cannot override FQDN on write operations"
	gen_canary
	duplicity \
		--archive-dir $cachedir \
		--tempdir=$tmpdir \
		--verbosity $verbosity \
		--jsonstat \
		$dryrun \
		--include-filelist $file_list \
		--volsize $volsize \
		full / $target
}

do_run()
{
	[ "$subject" != "$cert_subject" ] && die "cannot override FQDN on write operations"
	gen_canary
	duplicity \
		--archive-dir $cachedir \
		--tempdir=$tmpdir \
		--verbosity $verbosity \
		--jsonstat \
		--full-if-older-than "${backup_chain_days}D" \
		$dryrun \
		--include-filelist $file_list \
		--volsize $volsize \
		incr / $target
}

do_status()
{
	duplicity \
		--archive-dir $cachedir \
		--tempdir=$tmpdir \
		--jsonstat \
		--verbosity 1 \
		collection-status $target
}

do_list()
{
	duplicity \
		--archive-dir $cachedir \
		--tempdir=$tmpdir \
		--jsonstat \
		--verbosity 1 \
		list-current-files $target
}

do_cleanup()
{
	[ "$subject" != "$cert_subject" ] && die "cannot override FQDN on write operations"
	[ ! -z "$dryrun" ] && return 0
	duplicity \
		--archive-dir $cachedir \
		--tempdir=$tmpdir \
		--verbosity $verbosity \
		--force \
		cleanup $target
}

do_remove_old_sets()
{
	[ "$subject" != "$cert_subject" ] && die "cannot override FQDN on write operations"
	[ ! -z "$dryrun" ] && return 0
	duplicity \
		--archive-dir $cachedir \
		--tempdir=$tmpdir \
		--verbosity $verbosity \
		remove-all-but-n-full $full_retention --force $target
}

do_verify_fast()
{
	[ "$subject" != "$cert_subject" ] && die "cannot override FQDN for verify"
	tmpdest=`mktemp -t backup-verify-fast.XXXXXX`

	do_restore $verify_canary $tmpdest

	diff -q /$verify_canary $tmpdest
	st=$?
	if [ $st -eq 0 ]; then
		echo "Verify OK"
	else
		echo "Verify failed"
	fi
	rm -f $tmpdest
	return $st
}

do_verify()
{
	[ "$subject" != "$cert_subject" ] && die "cannot override FQDN for verify"
	touch $verify_cookie
	duplicity \
		--archive-dir $cachedir \
		--tempdir=$tmpdir \
		--verbosity $verbosity \
		--include-filelist $file_list \
		verify $target /
}

do_verify_after_n_days()
{
	[ "$subject" != "$cert_subject" ] && die "cannot override FQDN for verify"
	last_full=$(rclone ls $rclone_target | egrep duplicity-full.[0-9]+T[0-9]+Z.manifest.gpg | \
		sort | tail -1 | \
		sed "s/^ *[0-9]* *duplicity-full.\([0-9][0-9]*\)T[0-9]*Z.manifest.gpg/\\1/")
	last_full_ts=$(date -d "$last_full" +%s)
	last_full_delta=$(echo "`date +%s` - $last_full_ts" | bc)

	# Run a verify on the Nth day after the last full
	[ -f $verify_cookie -a -z "`find $verify_cookie -mtime +$verify_after_n_days 2>/dev/null`" ] && return 0
	days_secs=$(echo "$verify_after_n_days * 86400" | bc)
	[ "$last_full_delta" -lt "$days_secs" ] && return 0

	touch $verify_cookie
	duplicity \
		--archive-dir $cachedir \
		--tempdir=$tmpdir \
		--verbosity $verbosity \
		--include-filelist $file_list \
		verify $target /
}

process() {
	setup_target
	if [ "$mode" = "full" ]; then
		do_full
	elif [ "$mode" = "run" ]; then
		do_run
	elif [ "$mode" = "collection-status" -o "$mode" = "status" ]; then
		do_status
	elif [ "$mode" = "list" ]; then
		do_list
	elif [ "$mode" = "verify-after-n-days" ]; then
		do_verify_after_n_days
	elif [ "$mode" = "verify" ]; then
		do_verify
	elif [ "$mode" = "verify-fast" ]; then
		do_verify_fast
	elif [ "$mode" = "cleanup" ]; then
		do_cleanup
	elif [ "$mode" = "remove-old-sets" ]; then
		do_remove_old_sets
	elif [ "$mode" = "restore" ]; then
		do_restore $@
	else
		usage
		exit 1
	fi
}

args=`getopt hnlS:R:k:c:C: $*`
if [ $? -ne 0 ]; then
	usage
	exit 2
fi
set -- $args

verbosity=4
restore_region=""
dryrun=""
use_log=false
ca_cert="/etc/certes/root.pem"
key="/etc/certes/key.pem"
cert="/var/lib/certes/cert.pem"
subject=""
cert_subject=""
while [ $# -ne 0 ]; do
	case "$1" in
		-h)
			usage
			exit 0
			;;
		-n)
			dryrun="--dry-run"
			shift
			;;
		-l)
			use_log=true
			shift
			;;
		-R)
			if [ "$2" != "ca-east-tor" -a "$2" != "eu-west-par" ]; then
				die "-R should be either 'ca-east-tor' or 'eu-west-par'"
			fi
			restore_region="$2"
			shift
			shift
			;;
		-S)
			subject="$2"
			shift
			shift
			;;
		-k)
			key="$2"
			shift
			shift
			;;
		-C)
			ca_cert="$2"
			shift
			shift
			;;
		-c)
			cert="$2"
			shift
			shift
			;;
		--)
			shift
			break
			;;
	esac
done

if [ -z "$subject" ]; then
	[ -r "$key" ] || die "failed to read SSL key: $key"
	[ -r "$cert" ] || die "failed to read SSL cert: $cert"
	cert_subject=$(openssl x509 -in "$cert" -subject -noout | egrep -o 'CN=[A-Za-z0-9\.-]+' | cut -d= -f 2)
	[ -z "$cert_subject" ] && die "could not find subject in certificate"
	subject="$cert_subject"
fi

mode="$1"
if [ -z "$mode" ]; then
	usage
	exit 1
fi
shift

basedir="/var/duplicity/$subject"
tmpdir="$basedir/tmp"
cachedir="$basedir/cache"
verify_cookie="$basedir/.last_verify_cookie"
rclone_target="duplicity:/$subject"
target="rclone://$rclone_target"
file_list=/etc/duplicity.conf
lock="$basedir/.lock"
verify_canary=etc/duplicity.canary
verify_after_n_days=15

# With a bucket protection policy in place for 120 days, we keep a chain for
# ~90 days to ensure the initial full backup is protected. We keep 2+ sets
# to ensure we don't attempt deleting before the protection period has
# expired to avoid backend errors.
full_retention=2
backup_chain_days=90
# Higher volume size to reduce the number of operations (cost) on the backend
volsize=250

[ -x /usr/bin/bc ] || die "bc command not found"
[ -x /usr/bin/rclone ] || die "rclone command not found"
[ -x /usr/bin/duplicity ] || die "duplicity command not found"
[ -x /usr/bin/openssl ] || die "openssl command not found"
[ -x /usr/bin/flock ] || die "flock command not found"

[ "`whoami`" = "root" ] || die "must be run as root"

if [ ! -d "$cachedir" ]; then
	mkdir -p $cachedir
	chmod 700 $cachedir
fi

if [ ! -d "$tmpdir" ]; then
	mkdir -p $tmpdir
	chmod 700 $tmpdir
fi

touch "$lock" || die "cannot write lock file"

if [ "$subject" = "$cert_subject" ]; then
	[ -r /etc/duplicity.secrets ] \
		|| die "cannot read secrets file /etc/duplicity.secrets"

	. /etc/duplicity.secrets

	[ -r $file_list ] || die "cannot read conf file $file_list"
fi

if [ -z "$PASSPHRASE" ]; then
	read -p "Duplicity encryption key: " PASSPHRASE
fi
export PASSPHRASE

(
	flock -n 3 || exit 2
	if $use_log; then
		process $@ 2>&1 | logger -t duplicity -p daemon.notice
	else
		process $@
	fi
) 3>"$lock"

st=$?
if [ $st -eq 2 ]; then
	die "flock failed; already running?"
fi

exit $st
