#!/usr/bin/env bash

set -Eeuo pipefail

SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"

COMPRESS_THRS=$(($(nproc) / 4))
[[ $COMPRESS_THRS -eq 0 ]] && COMPRESS_THRS=1
[[ $COMPRESS_THRS -gt 8 ]] && COMPRESS_THRS=8

fail() {
    echo "$1" >&2
    exit 1
}

if [[ ${1:-} ]]; then
    . $1
    [[ $WORK_DIR ]] && mkdir -p $WORK_DIR && cd $WORK_DIR
else
    SHORT_HOST=$(hostname -s)
    TARGET=backup-$SHORT_HOST@antea
    SOURCES_FILE=$SCRIPT_DIR/sources_$SHORT_HOST
    . $SOURCES_FILE
fi
[[ ${TARGET:-} ]] || fail "TARGET expected to be set in config"
[[ ${SOURCES:-} ]] || fail "SOURCES expected to be set in $SOURCES_FILE or config"
TIMESTAMP=$(date +"%Y-%m-%d")

mkdir lock || fail "ERROR: backup already in progress (lock dir exists)"
function trap_global { rmdir lock; }
trap trap_global EXIT

get_snars() { find $1/ -type f -name '*.snar' -printf '%f\n' | sort; }
get_type() {
    mkdir -p "$1"

    if ! ls $1/*.snar >&/dev/null; then
        echo full
    else
        LAST_FULL=$(date --date=$(get_snars $1 | grep full | tail -n1 | sed 's/[.].*$//') +%s)
        LAST_BIG=$(date --date=$(get_snars $1 | grep '\(full\|weekly\)' | tail -n1 | sed 's/[.].*$//') +%s)
        NOW=$(date +%s)
        # do a full backup every 8 weeks
        if [[ $(($NOW - $LAST_FULL)) -ge $((3600 * 24 * 7 * 8)) ]]; then
            echo full
        elif [[ $(($NOW - $LAST_BIG)) -ge $((3600 * 24 * 7)) ]]; then
            echo weekly
        else
            echo daily
        fi
    fi
}

for SRC in $SOURCES; do
    SRC_ID=$(echo $SRC | sed 's|^/||')
    mkdir -p $SRC_ID

    TYPE=$(get_type $SRC_ID)
    [[ "$TYPE" = "full" ]] || [[ "$TYPE" = "weekly" ]] || [[ "$TYPE" = "daily" ]] || \
        fail "invalid backup type for $SRC: $TYPE"

    BACKUP_FILE=${SRC_ID}/${TIMESTAMP}.${TYPE}.tar.xz
    INCFILE_TGT=stamps/${SRC_ID}/${TIMESTAMP}.${TYPE}.snar.xz
    INCFILE=${SRC_ID}/${TIMESTAMP}.${TYPE}.snar
    INCFILE_TMP=${INCFILE}.tmp

    [[ -f $INCFILE_TMP ]] && fail "ERROR: backup $SRC already in progress (temporary incremental backup file exists)"
    if [[ -f $INCFILE ]]; then
        echo "WARNING: backup $SRC already done (incremental backup file exists)" >&2
        continue
    fi

    # drop extraneous stamp files
    if [[ "$TYPE" = "full" ]]; then
        echo "deleting" ${SRC_ID}/*.snar
        rm -f ${SRC_ID}/*.snar
    elif [[ "$TYPE" = "weekly" ]]; then
        echo "deleting" ${SRC_ID}/*.daily.snar
        rm -f ${SRC_ID}/*.daily.snar
    fi

    INC_FILTER="."
    if [[ "$TYPE" = "weekly" ]]; then
        INC_FILTER='\(full\|weekly\)'
    fi

    if [[ "$TYPE" != "full" ]]; then
        LAST=${SRC_ID}/$(get_snars $SRC_ID | grep $INC_FILTER | tail -n1)
        if [[ -f "$LAST" ]]; then
            cp --reflink=auto $LAST $INCFILE_TMP
        fi
    fi

    ssh $TARGET "mkdir -p stamps/${SRC_ID} ${SRC_ID}"

    function trap_local {
        ssh $TARGET "rm -f $BACKUP_FILE.tmp $INCFILE.tmp"
        rm -f $INCFILE_TMP
        trap_global
    }
    trap trap_local EXIT;

    echo "Backup $SRC ($TYPE)…"
    {
        {
            tar --create --xattrs --acls --listed-incremental=$INCFILE_TMP $SRC | \
                    xz -T$COMPRESS_THRS -3 | \
                    ssh $TARGET "umask 0027; cat > $BACKUP_FILE.tmp"
        } 3>&1 1>&2 2>&3 | \
                { grep -v "Removing leading \`/'" || true; } | \
                { grep -v "socket ignored" || true; }
    } 3>&1 1>&2 2>&3
    xz -T$COMPRESS_THRS --keep --stdout $INCFILE_TMP | ssh $TARGET "umask 0027; cat > $INCFILE_TGT.tmp"
    if echo $SRC | grep -q '^/home'; then
        USR=$(echo $SRC | sed 's|^/home/\([^/]*\).*$|\1|')
        ssh $TARGET "setfacl -m 'u:$USR:r' $BACKUP_FILE.tmp"
        DIR=$(dirname $BACKUP_FILE)
        while true; do
            ssh $TARGET "setfacl -m 'u:$USR:rx' $DIR"
            [[ "$DIR" = "." ]] && break
            DIR=$(dirname $DIR)
        done
    fi
    ssh $TARGET "mv $BACKUP_FILE{.tmp,}; mv $INCFILE_TGT{.tmp,}"
    trap trap_global EXIT
    mv $INCFILE_TMP $INCFILE
done
