#!/bin/bash

rsyncOptions='-vxcaHAXF'
rsync --help | \
  grep -q -- --may-modify-others && \
  rsyncOptions="${rsyncOptions} --may-modify-others"

[ -r "/etc/backup.conf" ] && \
  . "/etc/backup.conf"

usage()
{
  >&2 echo \
'Usage:  hardlinked-backup /tmp/pidFile /path/to/destination/ user@source:path [proxy_user@ssh_host]
Backup files from remote with rsync, possibly via SSH-tunnel.

With no arguments, information is expected to be in array $backups in /etc/backup.conf with name of executable (e.g. a symlink) as key.
With one argument, information is expected to be in array ${backups[$1]} in /etc/backup.conf.

Options:
  /tmp/pidFile           location of file to store PID in
  /path/to/destination   location to store backups in
  user@source:path       remote data to back up
  proxy_user@ssh_host    ssh login to proxy node (optional)
  --help                 display this help and exit
  --version              display version and exit'
  [ -z "$1" ] && exit 1
  exit $1
}

extract_ssh_host() {
  {
    printf '%s\n' "$1"
    if [ -f ~/.ssh/config ]; then
      sed '
        /^Host\s\+'"$1"'$/,/^Host\s/ {
          s/^\s*Hostname\s\+//
          t
        }
        d
      ' ~/.ssh/config
    fi
  } \
    | tail -n1
}

extract_ssh_ip_protocols() {
  getent ahosts "$(extract_ssh_host "$1")" \
    | sed '
      s/^\([0-9]\+\.\)\{3\}[0-9]\+\s\+STREAM\(\s.*\)\?$/4/
      t
      s/^[0-9a-f][0-9a-f:]\+\s\+STREAM\(\s.*\)\?$/6/
      t
      d
    ' \
    | sort -u \
    | if [ -f ~/.ssh/config ]; then
      grep -vxF "$(
        sed '
          /^Host\s\+'"$1"'$/,/^Host\s/ {
            s/^\s*AddressFamily\s\+inet\s*$/6/
            t
            s/^\s*AddressFamily\s\+inet6\s*$/4/
            t
          }
          d
        ' ~/.ssh/config
      )"
    else
      cat
    fi
}

connected_ip_versions() {
  ip -o addr show scope global \
    | awk '{print $4}' \
    | sed '
      s@^\([0-9]\+\.\)\{3\}[0-9]\+\(/[0-9]\+\)\?$@4@
      t
      s@^[0-9a-f][0-9a-f:]\+\(/[0-9]\+\)\?$@6@
      t
      d
    ' \
    | sort -u
}

if [ $# -eq 1 ]; then
  if [ "$1" == "--help" ]; then
    usage 0
  elif [ "$1" == "--version" ]; then
    echo '2.2.3'
    exit
  fi
fi

if ! tty -s \
&& command -v shutdownasap >/dev/null; then
  if ! shutdownasap -g \
  | grep -qxF none; then
    >&2 echo 'shutdownasap in progress. aborting'
    exit
  fi
fi

seldom=false
if [ $# -eq 0 ] || [ $# -eq 1 ]; then
  if [ $# -eq 0 ]; then
    backupID="$(basename $0)"
  else
    backupID="$1"
  fi
  [ -z "${backups[${backupID}]}" ] && usage
  set /tmp/${backupID}.pid ${backups[${backupID}]}
  if printf '%s\n' "${seldomBackups[@]}" | \
    grep -qxF "${backupID}"; then
    seldom=true
  fi
else
  backupID=''
fi

Basis="$2"
pidFile="$1"
QuellIP=$(echo "$3" | sed "s|^[a-zA-Z]*://||; s|^[a-zA-Z]*@||; s|:\?/.*$||")

if [ "$#" -eq 3 ]; then
  Quelle="$3"
  ipVer=$(
    {
      extract_ssh_ip_protocols "${QuellIP}" \
        | sort -n
      extract_ssh_ip_protocols "${QuellIP}" \
        | grep -xF "$(connected_ip_versions)" \
        | sort -n
    } \
      | tail -n1
  )
elif [ "$#" -eq 4 ]; then
  sshHopp="$4"
  lokPort=$[$RANDOM/2+8000]
  HoppIP="${sshHopp#*@}"
  ipVer=$(
    {
      extract_ssh_ip_protocols "${QuellIP}"
      extract_ssh_ip_protocols "${HoppIP}"
    } \
      | sort -n \
      | uniq -d \
      | tail -n1
  )
  if [ -z ${ipVer} ]; then
    >&2 echo 'not reachable'
    exit 11
  fi
  if [ ${ipVer} -eq 4 ]; then
    localAddress='127.0.0.1'
  elif [ ${ipVer} -eq 6 ]; then
    localAddress='[::1]'
  else
    >&2 echo 'ip version of hopp and target must be the same'
    exit 1
  fi
  rsyncShell="-e ssh -${ipVer} -p${lokPort} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
  tunnelBefehl="ssh -${ipVer} -t -t -L${localAddress}:${lokPort}:${QuellIP}:22 ${sshHopp} while sleep 60; do date; done"
  Quelle="$(echo "$3" | sed "s|${QuellIP}|${localAddress}|")"
else
  usage
fi

rsyncOptions="${rsyncOptions}${ipVer}"
if [ -n "${backupLimits["${backupID}"]}" ]; then
  rsyncOptions="${rsyncOptions} --bwlimit=${backupLimits["${backupID}"]}"
fi

if [ ! -d ${Basis} ]; then
  for neededMount in "${neededMounts[@]}"; do
    if ! mountpoint -q "${neededMount}"; then
      >&2 printf 'Mountpoint %s is not available.\n' "${neededMount}"
      exit 11
    fi
  done
  exit 2
fi

neues_Datum="${Basis}/$(date "+%Y_%m_%d")"
neues="${Basis}/aktuell"
linkdests=""
for s in $(ls -1 "${Basis}" | sort -r | grep -vxF -m 20 aktuell ); do
  linkdests="${linkdests} --link-dest ${Basis}/${s}"
done

if [ ! "$(whoami)" == "root" ]; then
  echo "I need to be root."
  exit 3
fi

[ -e "${neues_Datum}" ] && exit 4
if ${seldom}; then
  for date_diff in $(seq ${seldomness}); do
    if [ -e "${Basis}/$(date '+%Y_%m_%d' -d@$(($(date '+%s')-24*60*60*date_diff)))" ]; then
      exit 4
    fi
  done
fi
[ -w "${Basis}" ] || exit 11
[ -s "${pidFile}" ] && [ -d "/proc/$(cat "${pidFile}")" ] && exit 5

echo $$ > "${pidFile}"

if [ -n "${tunnelBefehl}" ]; then
  preConnectHook
  ${tunnelBefehl} &
  backgroundPid=$!
  sleep 4
fi

for toExclude in "${excludes[@]}"; do
  excludeArgs="${excludeArgs} --exclude ${toExclude}"
done

mkdir -p "${neues}/wip"
chmod 750 "${neues}"{,/wip}
chown root:root "${neues}"{,/wip}
if [ -z "${rsyncShell}" ]; then
  preConnectHook
  if [ -z "${Quelle#*@*:/}" ]; then
    ssh "-${ipVer}" "${Quelle%:/}" '
      pacman-conf Architecture
      find /var/lib/pacman/local -name desc -exec cat {} +
    ' \
    | {
      read -r arch
      sed -n '
        /^%\(NAME\|VERSION\|ARCH\)%/ {
          N
          s/\n/ /
          p
        }
      ' \
      | sed '
        N
        N
        s@^%NAME% \(\S\+\)\n%VERSION% \(\S\+\)\n%ARCH% \(\S\+\)$@\1-\2-\3.pkg.tar.zst@
        t
        w /dev/stderr
        d
      ' \
      | while read -r file; do
        cache_file='/var/cache/pacman/pkg/'"${file}"
        if [ ! -f "${cache_file}" ]; then
          case "${arch}" in
            'x86_64')
              for url in \
                'https://mirror.f4st.host/archlinux/pool/packages/'"${file}" \
                'https://archive.archlinux.org/packages/'"${file:0:1}"'/'"${file%-*}"'/'"${file}" \
                'https://arch.eckner.net/os/'"${arch}"'/'"${file}"; do
                curl -Lo "${cache_file}" "${url}" || continue
                tar --zstd -tf "${cache_file}" >/dev/null 2>&1 && break
              done
            ;;
            'i486'|'i686'|'pentium4')
              for url in \
                'https://mirror.archlinux32.org/pool/'"${file}" \
                'https://archive.archlinux32.org/packages/'"${file:0:1}"'/'"${file%-*}"'/'"${file}" \
                'https://arch.eckner.net/os/'"${arch}"'/'"${file}"; do
                curl -Lo "${cache_file}" "${url}" || continue
                tar --zstd -tf "${cache_file}" >/dev/null 2>&1 && break
              done
            ;;
            'armv6h')
              for url in \
                'http://software.is.never.null/arch/'"${arch}"'/'{extra,community,core,alarm,aur}'/'"${file}" \
                'https://arch.eckner.net/os/'"${arch}"'/'"${file}"; do
                curl -Lo "${cache_file}" "${url}" || continue
                tar --zstd -tf "${cache_file}" >/dev/null 2>&1 && break
              done
            ;;
            'armv7h'|'aarch64')
              for url in \
                'https://mirror.archlinuxarm.org/'"${arch}"'/'{extra,community,core,alarm,aur}'/'"${file}" \
                'https://arch.eckner.net/os/'"${arch}"'/'"${file}"; do
                curl -Lo "${cache_file}" "${url}" || continue
                tar --zstd -tf "${cache_file}" >/dev/null 2>&1 && break
              done
            ;;
          esac
        fi
        if [ -f "${cache_file}" ]; then
          tar -C "${neues:?}/wip/" --zstd -xkf "${cache_file}"
        fi
      done
    }
  fi
  rsync ${rsyncOptions} \
    ${linkdests} \
    ${excludeArgs} \
    ${Quelle} "${neues}/wip/"
  sleep 1
  preConnectHook
  rsync ${Quelle}
else
  preConnectHook
  rsync "${rsyncShell}" \
    ${rsyncOptions} \
    ${linkdests} \
    ${excludeArgs} \
    ${Quelle} "${neues}/wip/"
  sleep 1
  preConnectHook
  rsync "${rsyncShell}" ${Quelle}
fi
erg=$?

[ -n "${backgroundPid}" ] && kill "${backgroundPid}"

if [ ${erg} -eq 0 ] || [ ${erg} -eq 24 ] && ! rmdir "${neues}/wip" 2>/dev/null; then
  chmod o-rwx "${neues}/wip"
  neueres_Datum="${Basis}/$(date "+%Y_%m_%d")"
  if [ ! -e "${neueres_Datum}" ]; then
    neues_Datum="${neueres_Datum}"
  fi
  mv "${neues}/wip" "${neues_Datum}"
  rmdir "${neues}"
  rm "${pidFile}"
else
  rm "${pidFile}"
  exit 11
fi
