#!/bin/bash

set -x
# Note that we' aren't doing a set -e
set -u
set -o pipefail

function usage
{
    retval="$1"
    case "$retval" in
        0)
            ;;
        *)
            exec 1>&2
            ;;
    esac

    echo "Usage: $0 [--normal|--overwrite] --source-dir /source/directory --dest-dir /destination/directory --compressor zstd --help"
    echo
    echo "Normal mode will do a backup with rotation."
    echo
    echo "Overwrite mode will overwrite the last daily backup, without doing any rotation at all.  It's for redoing a backup."
    echo "when a previous one fails."
    echo
    echo "We default to compressing with zstd."
    echo
    echo "This script can deal pretty well with not being run every day, but it probably shouldn't be run more than once a day."

    exit "$retval"
}

mode=unknown
source_dir=""
destination_dir=""
compressor=zstd

while [ "$#" -ge 1 ]
do
    case "$1" in
        --normal)
            mode=normal
            ;;
        --overwrite)
            mode=overwrite
            ;;
        --source-dir)
            source_dir="$2"
            shift
            ;;
        --dest-dir)
            destination_dir="$2"
            shift
            ;;
        --compressor)
            compressor="$2"
            shift
            ;;
        -h|--help)
            usage 0
            ;;
        *)
            usage 1
            ;;
    esac
    shift
done

all_good=True

case "$mode" in
    normal)
        echo "$0: OK, I will do a normal backup with rotation" 1>&2
        ;;
    overwrite)
        echo "$0: OK, I will overwrite the most recent backup, without any sort of rotation" 1>&2
        ;;
    unknown)
        all_good=False
        echo "$0: You must specify either --normal or --overwrite" 1>&2
        ;;
    *)
        echo "$0: internal error: \$mode has a strange value: $mode" 1>&2
        exit 1
        ;;
esac

case "$source_dir" in
    "")
        echo "$0: --srcdir is a required option" 1>&2
        all_good=False
esac

case "$destination_dir" in
    "")
        echo "$0: --dstdir is a required option" 1>&2
        all_good=False
esac

case "$all_good" in
    True)
        ;;
    False)
        echo "$0: one or more preflight checks failed" 1>&2
        usage 1
        ;;
    *)
        echo "$0: Internal error: \$all_good has a strange value: $all_good" 1>&2
        exit 1
        ;;
esac

# disabled Wed Nov  3 09:54:46 PST 2004, because $destination_dir exists,
# but is not mounted

# Mon Sep 12 10:23:13 PDT 2005: reenabled, now that we have 3511+QFS
#exit 1

umask 077

PATH=/usr/local/bin:"$PATH"

mkdir -p "$destination_dir"/backups
if ! cd "$destination_dir"/backups
then
   echo "$destination_dir"/backups does not exit 1>&2
   exit 1
fi

mkdir -p {daily,weekly,monthly}.{1,2}

seven_days=$((60*60*24*7))
thirty_days=$((60*60*24*30))
curtime=$(python3 -c 'import time; t=int(time.time()); print(t)')

if [ "$mode" = normal ]
then
    rm -rf daily.2
    mv daily.1 daily.2

    mkdir -p daily.1
fi

if tty -s
then
    ONTTY=True
else
    ONTTY=False
fi

if ! cd "$destination_dir/backups"
then
    echo "$0: failed to cd to $destination_dir/backups" 1>&2
    exit 1
fi

# tar likes to exit false if even one file has a problem, but we don't want that to interfere with rotation,
# hence the || true's.
case "$ONTTY" in
    True)
        bytes="$(du -skx "$source_dir" | awk ' { print $1 * 1024 }')"
        (cd "$source_dir" && tar --create --one-file-system --sparse .) \
            | reblock -e "$bytes" -b $((1024*1024)) -t 300 \
            | mtee --quiet -- \
                "$compressor > daily.1/archive" \
                "tar tvf - | $compressor > daily.1/tvf"
        result=$?
        ;;
    False)
        (cd "$source_dir" && tar --create --one-file-system --sparse .) \
            | mtee --quiet -- \
                "$compressor > daily.1/archive" \
                "tar tvf - | $compressor > daily.1/tvf"
        result=$?
        ;;
    *)
        echo "$0: internal error: \$ONTTY has a strange value: $ONTTY" 1>&2
        exit 1
        ;;
esac

function mtime_of_file
{
    filename="$1"
    stat -c '%Y' "$filename"
}

function should_do_weekly
{
    if ! [ -f "weekly.1/archive" ]
    then
        return 0
    fi
    
    mtime_of_weekly=$(mtime_of_file weekly.1/archive)
    if [ $((curtime - mtime_of_weekly)) -gt "$seven_days" ]
    then
        return 0
    else
        return 1
    fi
}

function should_do_monthly
{
    if ! [ -f "monthly.1/archive" ]
    then
        return 0
    fi
    
    mtime_of_monthly=$(mtime_of_file monthly.1/archive)
    if [ $((curtime - mtime_of_monthly)) -gt "$thirty_days" ]
    then
        return 0
    else
        return 1
    fi
}

# ln (hardlinking) appears to work even on CIFS now, though our CIFS server is running Samba, so maybe that's why.
case "$mode" in
    normal)
        if should_do_weekly
        then
            rm -rf weekly.2
            mv weekly.1 weekly.2
            mkdir weekly.1
            for file in daily.1/*
            do
                ln "$file" weekly.1/.
            done
        fi

        if should_do_monthly
        then
            rm -rf monthly.2
            mv monthly.1 monthly.2
            mkdir monthly.1
            for file in daily.1/*
            do
                ln "$file" monthly.1/.
            done
        fi
        ;;
    overwrite)
        ;;
    *)
        echo "$0: internal error: \$mode has a strange value: $mode" 1>&2
        exit 1
        ;;
esac

exit "$result"