#!/usr/bin/bash

GROB_VERSION=0.2.3

# log_msg <level> <msg> [..]
# where levels can be one of:
#  0 .. error, output always
#  1 .. warning
#  2 .. info
#  3 .. debug
function log_msg() {
    local msg_level="$1"
    local msg_level_string
    shift

    case "$msg_level" in
        0) msg_level_string="ERROR";;
        1) msg_level_string="warning";;
        2) msg_level_string="info";;
        3) msg_level_string="debug";;
        *)
            printf "%s: %s[%i]: %s: %s%s\n" \
                   "$(date '+%F %T')" "${0##*/}" "$$" "ERROR" \
                   "log_msg called with illegal message level" \
                   "'$msg_level', using 0 instead" >&2
            msg_level=0
            msg_level_string="ERROR"
    esac

    # do not log msg if log_level is set and msg_level > log_level
    if [ -v log_level ]
    then
        if [[ "$log_level" =~ ^[0123]$ ]]
        then
            (( msg_level <= log_level )) || return
        else
            printf "%s: %s[%i]: %s: %s%s\n" \
                   "$(date '+%F %T')" "${0##*/}" "$$" "ERROR" \
                   "log_msg called while log_level had" \
                   "illegal value '$log_level', logging anyway" >&2
        fi
    fi

    printf "%s: %s[%i]: %s: %s\n" \
           "$(date '+%F %T')" "${0##*/}" "$$" "${msg_level_string}" "$*" >&2
}

# fatal <exit-code> <msg> [..]
function fatal() {
    local -i exit_code="$1"
    [[ "$1" =~ ^-?[0-9]+$ ]] || {
        log_msg 0 "$0.fatal INTERNAL ERROR:" \
                "i was called with \$1 being not an integer, using 255!"
        exit_code=255
    }
    shift

    log_msg 0 "FATAL: $*"
    exit "$exit_code"
}

log_msg 2 "version $GROB_VERSION starting" \
        "on host $(hostname -f || echo UNKNOWN)"

# read variables (intil we get "start")
linenr=0
while true
do
    linenr="$((linenr + 1))"
    if read -r line
    then
        case "$line" in
            start) break;; # done reading variables
            "declare "*)
                log_msg 3 "declaring variable: $line"
                eval "$line"
                ;;
            "export "*)
                log_msg 3 "exporting variable: ${line#export }"
                export "${line#export }"
                ;;
            *)
                fatal 254 "illegal variable line $linenr: $line"
        esac
    else
        fatal 253 "eof while reading variable at line $linenr"
    fi
done

function assert_vars() {
    local v

    for v in "$@"
    do
        [ -v "$v" ] || \
            fatal 255 "expected variable not deliverd from remote side: $v"
    done
}

assert_vars borg_args client_type

# shellcheck disable=SC2154
case "$client_type" in
    tcp)
        assert_vars GROB_OTP GROB_TUNNEL_PORT
        export BORG_RSH="/usr/libexec/grob/grob-client-helper-tcp"
        ;;
    unix)
        export BORG_RSH="/usr/libexec/grob/grob-client-helper-unix"
        ;;
    *)
        fatal 252 "illegal client_type: $client_type"
esac

# check which file type our local /usr/bin/borg is
# we do this intentionally before pre hook, so it does not run if we fail here
declare -a borg_bin
if (( EUID == 0 )) # we are root, don't care about caps
then
    borg_bin=("/usr/bin/borg")
else # we use caps
    borg_bin_type=$(file -Lb /usr/bin/borg) || \
        fatal 255 "unable to get borg bin type"
    case "$borg_bin_type" in
        "Python script"*)
            borg_bin=("/usr/libexec/grob/grob-client-borg-cap-wrapper"
                      "/usr/bin/python3" "/usr/bin/borg")
            ;;
        ELF*)
            borg_bin=("/usr/libexec/grob/grob-client-borg-cap-wrapper"
                      "/usr/bin/borg")
            ;;
        *)
            fatal 255 "unknown /usr/bin/borg type: $borg_bin_type"
    esac
fi
log_msg 2 "borg_bin was detected: ${borg_bin[*]}"

# get borg version and fail out borg, if borg for some reason is not
# callable
local_borg_version=$("${borg_bin[@]}" --version) || \
    fatal 255 "failed to get client borg version"
local_borg_version="${local_borg_version##* }"
log_msg 2 "local borg version: ${local_borg_version}"

# shellcheck disable=SC2088
log_msg 2 "~/.cache/borg size is: $(du -sh ~/.cache/borg || true)"
log_msg 2 "cache details: $(du -sh ~/.cache/borg/*/ || true)"
log_msg 2 "chunk archives: $(du -sh ~/.cache/borg/*/chunks.archive.d || true)"
log_msg 2 "cache fs free: $(df -h ~/.cache/borg/ || true)"

# call pre hook
if [ -v grob_pre_hook ]
then
    log_msg 2 "running pre hook: $grob_pre_hook"
    eval "$grob_pre_hook" 2>&1 || \
        fatal 250 "pre hook failed, aborting"
fi

# shellcheck disable=SC2064
[ -z "${borg_passphrase-}" ] || {
    BORG_PASSPHRASE_FILE=$(mktemp ~/.borg.phrase.XXXXXXXXXXXX)
    trap "rm -f '$BORG_PASSPHRASE_FILE'" EXIT
    chmod 600 "$BORG_PASSPHRASE_FILE"
    echo "${borg_passphrase}" > "$BORG_PASSPHRASE_FILE"
    export BORG_PASSCOMMAND="cat $BORG_PASSPHRASE_FILE"
}

log_msg 3 "BORG_RSH=$BORG_RSH"

export BORG_RELOCATED_REPO_ACCESS_IS_OK=yes
export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes
export BORG_EXIT_CODES=modern

# shellcheck disable=SC2154
log_msg 3 "now launching: ${borg_bin[*]} ${borg_args[*]}"

borg_retval=0
"${borg_bin[@]}" "${borg_args[@]}" 2>&1 || borg_retval="$?"

log_msg 3 "borg call completed with retval=$borg_retval"

[ -z "${borg_passphrase-}" ] || rm -f "$BORG_PASSPHRASE_FILE"

if (( borg_retval == 1 || (borg_retval >= 100 && borg_retval <= 127) ))
then
    log_msg 0 "borg run returned with a warning ($borg_retval)" \
            "consult logs for details"
    borg_retval=0
elif (( borg_retval > 0 ))
then
     log_msg 0 "borg_run return with $borg_retval (ERROR)," \
             "consult logs for details"
fi

# call post hook
if [ -v grob_post_hook ]
then
    log_msg 2 "running post hook: $grob_post_hook"
    eval "$grob_post_hook" 2>&1 || \
        fatal 249 "post hook failed"
fi

exit "$borg_retval"
