#!/usr/bin/env bash

set -euo pipefail

while test $# -gt 0; do
  case "$1" in
    -h|--help)
      echo ""
      echo "$0 [options]"
      echo ""
      echo "This script parses one or more compose files and extracts the content of the 'volumes:' variable of each service. For these volumes, it performs a backup or a restore."
      echo ""
      echo "actions:"
      echo "   --create                 create a backup of volumes"
      echo "   --restore                restore a backup of volumes"
      echo ""
      echo "options:"
      echo "   --backup-dir <X>         path to backup directory, defaults to './backups'"
      echo "   --backup-name <X>        backup name, defaults to current date"
      echo "   --compose-dir <X>        path to compose directory, defaults to '.'"
      echo "   --compose-files <X>      list of compose files relative to directory (comma-separated), defaults to 'docker-compose.yml'"
      echo "   --compress               use gzip compression for backup"
      echo "   --services-exclude <X>   list of services to exclude from backup (comma-separated)"
      echo ""
      echo "misc:"
      echo "-h --help                   show brief help"
      echo ""
      exit 0
      ;;
    # actions
    --create)
      ACTION_CREATE=1
      shift
      ;;
    --restore)
      ACTION_RESTORE=1
      shift
      ;;

    # options
    --backup-dir)
      shift
      BACKUP_BACKUP_DIR="$1"
      shift
      ;;
    --backup-name)
      shift
      BACKUP_BACKUP_NAME="$1"
      shift
      ;;
    --compose-dir)
      shift
      BACKUP_COMPOSE_DIR="$1"
      shift
      ;;
    --compose-files)
      shift
      BACKUP_COMPOSE_FILE_LIST="$1"
      shift
      ;;
    --compress)
      # shellcheck disable=2034
      BACKUP_COMPRESS=1
      shift
      ;;
    --services-exclude)
      shift
      BACKUP_SERVICES_EXCLUDE_LIST="$1"
      shift
      ;;

    # misc
    *)
      break
      ;;
  esac
done

########################################

if [ -z ${ACTION_CREATE:+x} ] && [ -z ${ACTION_RESTORE:+x} ]; then
  echo "error - action parameter required"
  exit 1
fi

########################################

BACKUP_BACKUP_DIR="$(readlink -mv "${BACKUP_BACKUP_DIR:-"./backups"}")"
BACKUP_BACKUP_NAME="${BACKUP_BACKUP_NAME:-"$(date +"%Y-%m-%d_%H-%M-%S")"}"
BACKUP_COMPOSE_DIR="$(readlink -ev "${BACKUP_COMPOSE_DIR:-"."}")"
BACKUP_COMPOSE_FILE_LIST="$(echo "${BACKUP_COMPOSE_FILE_LIST:-"docker-compose.yml"}" | tr "," "\n")"
BACKUP_SERVICES_EXCLUDE_LIST="$(echo "${BACKUP_SERVICES_EXCLUDE_LIST}" | tr "," "\n")"

echo "start time: $(date +"%Y-%m-%d_%H-%M-%S")"
echo
echo "use backup name '${BACKUP_BACKUP_NAME}'"
echo "use backup directory '${BACKUP_BACKUP_DIR}'"
echo "use compose directory '${BACKUP_COMPOSE_DIR}'"

mkdir -p "${BACKUP_BACKUP_DIR}"

########################################

echo
echo "generate compose file list and volume list"

h1=""
h2=""
while IFS= read -r i; do
  j="$(readlink -ev "${BACKUP_COMPOSE_DIR}/${i}")"
  echo "use compose file '${j}'"
  h1+="-f ${j} "
  # shellcheck disable=2002
  h2+="$(cat "${j}" | docker run --rm -i docker.io/mikefarah/yq:4.35.2 eval ".services[].volumes[].source"  | awk -F ":" "{print \$1}")
"
done < <(printf '%s\n' "${BACKUP_COMPOSE_FILE_LIST}")
BACKUP_COMPOSE_FILE_CMD="${h1}"
BACKUP_VOLUME_LIST="$(echo "${h2}" | sed "/^$/d; /^---$/d; s|^\./|${BACKUP_COMPOSE_DIR}/|g" | sort | uniq)"

########################################

echo
echo "check which volume to use"

while IFS= read -r i; do
  if /usr/bin/env mountpoint -q "${i}" &> /dev/null; then
    echo "skip volume (is mountpoint) '${i}'"
    # shellcheck disable=2001
    BACKUP_VOLUME_LIST="$(echo "${BACKUP_VOLUME_LIST}" | sed "s|${i}||g; /^$/d")"
  elif [ -S "${i}" ]; then
    echo "skip volume (is socket) '${i}'"
    # shellcheck disable=2001
    BACKUP_VOLUME_LIST="$(echo "${BACKUP_VOLUME_LIST}" | sed "s|${i}||g; /^$/d")"
  else
    for s in ${BACKUP_SERVICES_EXCLUDE_LIST}; do
      if echo "${i}" | grep -q "${s}"; then
        echo "skip volume (is on exclude list) '${i}'"
        # shellcheck disable=2001
        BACKUP_VOLUME_LIST="$(echo "${BACKUP_VOLUME_LIST}" | sed "s|${i}||g; /^$/d")"
        break
      fi
    done
    echo "use volume '${i}'"
  fi
done < <(printf '%s\n' "${BACKUP_VOLUME_LIST}")

########################################

cd "${BACKUP_COMPOSE_DIR}"

# shellcheck disable=2086
BACKUP_RUNNING_SERVICES=$(docker-compose ${BACKUP_COMPOSE_FILE_CMD} ps --filter "status=running" --services | tr "\n" " "| sed "/^ $/d")
echo "running services: '${BACKUP_RUNNING_SERVICES:-"none"}'"

echo "shutdown services"

# shellcheck disable=2086
docker-compose ${BACKUP_COMPOSE_FILE_CMD} down

docker-compose-up() {
  if [ -z ${BACKUP_RUNNING_SERVICES:+x} ]; then
    echo "no services were running, skip start"
  else
    # shellcheck disable=2086
    docker-compose ${BACKUP_COMPOSE_FILE_CMD} up -d ${BACKUP_RUNNING_SERVICES}
  fi
}

trap "docker-compose-up" SIGINT SIGTERM ERR EXIT

########################################

# shellcheck disable=2236
if [ ! -z ${ACTION_CREATE:+x} ]; then
  echo "create backup"

  while IFS= read -r i; do
    j="${BACKUP_BACKUP_DIR}"/"${BACKUP_BACKUP_NAME}"_backup_"$(basename "${i}")".tar"${BACKUP_COMPRESS:+".gz"}"
    if ! readlink -e "${i}" &> /dev/null; then
      echo "skip create backup from volume (not found) '${i}'"
    else
      echo "create backup from volume '${i}' to archive '${j}'"
      time tar -C "${i}" -c"${BACKUP_COMPRESS:+"z"}"f "${j}" .
    fi
  done < <(printf '%s\n' "${BACKUP_VOLUME_LIST}")
fi

########################################

# shellcheck disable=2236
if [ ! -z ${ACTION_RESTORE:+x} ]; then
  echo "restore backup"

  while IFS= read -r i; do
    j="${BACKUP_BACKUP_DIR}"/"${BACKUP_BACKUP_NAME}"_backup_"$(basename "${i}")".tar"${BACKUP_COMPRESS:+".gz"}"
    if ! readlink -e "${j}" &> /dev/null; then
      echo "skip restore backup from archive (not found) '${j}'"
    else
      echo "restore backup from archive '${j}' to volume '${i}'"
      rm -rf "${i}"
      mkdir -p "${i}"
      tar -C "${i}" -x"${BACKUP_COMPRESS:+"z"}"f "${j}" .
    fi
  done < <(printf '%s\n' "${BACKUP_VOLUME_LIST}")
fi

########################################

echo "start services"
# shellcheck disable=2086
docker-compose-up
trap "" SIGINT SIGTERM ERR EXIT

########################################

echo "end time: $(date +"%Y-%m-%d_%H-%M-%S")"
