#! /bin/bash
#
# s3backup.sh
# General-purpose, Ansible-managed backup script to push directories, DBs, and
# more up to an S3 bucket
#
# NOTICE: THIS FILE CONTAINS SECRETS
# This file may contain the following secrets depending on configuration:
# * An AWS access key
# * An AWS session token
# These are NOT things you want arbitrary readers to access! Ansible will
# attempt to ensure this file has 0700 permissions, but that won't stop you
# from changing that yourself
# DO NOT ALLOW THIS FILE TO BE READ BY NON-ROOT USERS

# NOTICE: DO NOT MODIFY THIS FILE
# Any changes made will be clobbered by Ansible
# Please make any configuration changes in the main repo

set -e

# AWS S3 configuration
# NOTE: THIS IS SECRET INFORMATION
export AWS_ACCESS_KEY_ID="{{ backup_s3_aws_access_key_id }}"
export AWS_SECRET_ACCESS_KEY="{{ backup_s3_aws_secret_access_key }}"

# Directories to backup
# Ansible will determine the entries here

# We use a bash array because it affords us some level of sanitization, enough
# to let us back up items whose paths contain spaces
declare -a DIRS
{% for item in backup_s3backup_list + backup_s3backup_list_extra %}
DIRS+=("{{ item }}")
{% endfor %}
# End directory manual configuration

# If we have ostree, add diff'd configs to the list, too
if command -v ostree > /dev/null 2>&1; then
	for file in $(
		ostree admin config-diff 2>/dev/null | \
		grep -oP '^[A|M]\s*\K.*'
	); do
		DIRS+=("/etc/$file")
	done
fi

# Helper functions
backup() {
	# Takes a file or directory to backup and backs it up
	[ -z "$*" ] && return 1

	if command -v restic > /dev/null 2>&1; then
		for dir in "$@"; do
			echo "- $dir"
		done
		# Back up everything in the $DIRS array (which was passed as args)
		/opt/restic-wrapper \
			--verbose \
			backup \
			"$@"
		# In addition, we should also prune our backups
		# https://restic.readthedocs.io/en/stable/060_forget.html
		# --keep-daily n	Keeps daily backups for the last n days
		# --keep-weekly n	Keeps weekly backups for the last n weeks
		# --keep-montly n	Keeps monthly backups for the last n months
		# --keep-tag foo	Keeps all snapshots tagged with "foo"
		# --host "$HOSTNAME"	Only act on *our* snapshots. We don't care about other machines'
		# --prune		Remove orphaned blobs when we remove snapshots
		/opt/restic-wrapper \
			forget \
			--verbose \
			--keep-daily 7 \
			--keep-weekly 4 \
			--keep-monthly 6 \
			--keep-tag noremove \
			--host "$HOSTNAME" \
			--prune
	else
		dir="$@"
		echo "- $dir"
		nice -n 10 tar {{ backup_s3backup_tar_args }}{{ backup_s3backup_tar_args_extra }} \
	{% for item in backup_s3backup_exclude_list + backup_s3backup_exclude_list_extra %}
			--exclude "{{ item }}" \
	{% endfor %}
			"$dir" \
			| aws s3 cp --expected-size 274877906944 - \
{% if backup_s3_aws_endpoint_url is defined %}
			--endpoint-url="{{ backup_s3_aws_endpoint_url }}" \
{% endif %}
			"s3://{{ backup_s3_bucket }}/$HOSTNAME/$dir/$(date "+{{ backup_dateformat }}").tar.gz"
	fi
}

# Dump Postgres DBs, if possible
if command -v psql > /dev/null 2>&1; then
	# Put down a place for us to store backups, if we don't have it already
	backupdir="/opt/postgres-backups"
	mkdir -p "$backupdir"
	# Populate a list of databases
	declare -a DATABASES
	while read line; do
		DATABASES+=("$line")
	done < <(sudo -u postgres psql -t -A -c "SELECT datname FROM pg_database where datname not in ('template0', 'template1', 'postgres');" 2>/dev/null)

	# pgdump all DBs, compress them, and pipe straight up to S3
	echo "Commencing backup on the following databases:"
	for dir in "${DATABASES[@]}"; do
		echo "- $dir"
	done
	echo "Will upload resultant backups to {{ backup_s3_bucket }}"
	for db in "${DATABASES[@]}"; do
		echo "Backing up $db"
		path="$backupdir/$db.pgsql.gz"
		sudo -u postgres pg_dump "$db" \
			| gzip -v9 \
			> "$path"
		DIRS+=("$path")
	done
fi

# Tar up all items in the backup list, recursively, and pipe them straight
# up to S3
if [ -n "${DIRS[*]}" ]; then
	echo "Commencing backup on the following items:"
	for dir in "${DIRS[@]}"; do
		echo "- $dir"
	done
	if command -v restic > /dev/null 2>&1; then
		echo "An ignore list was specified, but restic was detected as the backup method."
		echo "The following list of items WILL be backed up:"
	else
		echo "Will ignore the following items:"
	fi
	{% for item in backup_s3backup_exclude_list + backup_s3backup_exclude_list_extra %}
	echo "- {{ item }}"
	{% endfor %}
	echo "Will upload resultant backups to {{ backup_s3_bucket }}"
	if command -v restic > /dev/null 2>&1; then
		echo "Using restic for backups"
		backup ${DIRS[*]}
	else
		echo "Using rudimentary tar and S3 for backups"
		for dir in "${DIRS[@]}"; do
			if [ "$dir" == "/data" ]; then
				for datadir in "$dir"/*; do
					[ -e "$datadir" ] && backup "$datadir"
				done
			else
				backup "$dir"
			fi
		done
	fi
fi