#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

readonly SCRIPT_NAME="docker_volume_cold_restic"
readonly SCRIPT_VERSION="1.0.0"

export RESTIC_REPOSITORY="s3:s3.ap-east-1.amazonaws.com/your-bucket-name/docker-volumes"
export RESTIC_PASSWORD="change-this-restic-password"
export AWS_ACCESS_KEY_ID="YOUR_AWS_ACCESS_KEY"
export AWS_SECRET_ACCESS_KEY="YOUR_AWS_SECRET_KEY"

readonly RESTIC_VERSION="0.18.1"
readonly MIN_RESTIC_VERSION="0.14.0"
readonly HELPER_IMAGE="debian:bookworm-slim"
readonly BACKUP_TAG="docker-volume-cold"
readonly STAGING_ROOT="/var/tmp/docker-volume-cold-restic"
readonly RESTORE_ROOT="/var/tmp/docker-volume-cold-restic-restore"
readonly LOG_ROOT="/var/log/docker-volume-cold-restic"
readonly LOCK_FILE="/var/run/docker-volume-cold-restic.lock"
readonly STOP_TIMEOUT=120
readonly KEEP_LAST=7
readonly KEEP_DAILY=14
readonly KEEP_WEEKLY=8
readonly KEEP_MONTHLY=6
readonly RESTIC_RETRIES=3
readonly PRUNE_AFTER_BACKUP=1
readonly REMOVE_STAGING_AFTER_BACKUP=1

declare -ar DEFAULT_INCLUDE_VOLUMES=()
declare -ar DEFAULT_EXCLUDE_VOLUMES=()

TIMESTAMP="$(date '+%Y-%m-%d_%H-%M-%S')"
readonly TIMESTAMP

readonly LOG_FILE="${LOG_ROOT}/${SCRIPT_NAME}_${TIMESTAMP}.log"

COMMAND=""
RESTORE_SNAPSHOT=""
FORCE_RESTORE=0
RUN_DIR=""
RESTORE_DIR=""
CONTAINERS_NEED_START=0

declare -a INCLUDE_VOLUMES=("${DEFAULT_INCLUDE_VOLUMES[@]}")
declare -a EXCLUDE_VOLUMES=("${DEFAULT_EXCLUDE_VOLUMES[@]}")
declare -a RUNNING_CONTAINERS=()

log() {
	local level="$1"
	local message="$2"
	local source="${FUNCNAME[1]:-main}"
	local line

	line="[$(date '+%Y-%m-%d %H:%M:%S')] [${level}] [${SCRIPT_NAME}.${source}] ${message}"
	printf '%s\n' "${line}" >&2
	if mkdir -p "${LOG_ROOT}" 2>/dev/null; then
		printf '%s\n' "${line}" >>"${LOG_FILE}" 2>/dev/null || true
	fi
}

die() {
	log "ERROR" "$1"
	exit 1
}

prepare_log_dir() {
	if ! mkdir -p "${LOG_ROOT}"; then
		printf '[%s] [ERROR] [%s.prepare_log_dir] Failed to create log directory path=%s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "${SCRIPT_NAME}" "${LOG_ROOT}" >&2
		exit 1
	fi
}

usage() {
	cat <<USAGE
${SCRIPT_NAME} ${SCRIPT_VERSION}

Usage:
  ${SCRIPT_NAME} backup [--include volume1,volume2] [--exclude volume3,volume4]
  ${SCRIPT_NAME} restore <snapshot-id|latest> [--force] [--include volume1,volume2] [--exclude volume3,volume4]
  ${SCRIPT_NAME} snapshots
  ${SCRIPT_NAME} list-volumes

Backup:
  Stops all currently running containers, exports selected Docker named volumes,
  uploads the export directory with restic, then starts only the containers that
  were running before the backup.

Restore:
  Restores selected volume archives from a restic snapshot. Existing volumes are
  protected by default. Use --force to stop running containers, clear existing
  volume content, and import the snapshot data.

Configure restic repository, password, and object storage credentials at the top
of this script before running it on a server.
USAGE
}

trim_value() {
	local value="$1"
	value="${value#"${value%%[![:space:]]*}"}"
	value="${value%"${value##*[![:space:]]}"}"
	printf '%s\n' "${value}"
}

append_csv_values() {
	local raw="$1"
	local target_name="$2"
	local -n target="${target_name}"
	local item
	local trimmed
	local values=()

	IFS=',' read -r -a values <<<"${raw}"
	for item in "${values[@]}"; do
		trimmed="$(trim_value "${item}")"
		[[ -n "${trimmed}" ]] && target+=("${trimmed}")
	done
}

parse_args() {
	if (($# == 0)); then
		usage
		exit 1
	fi

	if [[ "$1" == "-h" || "$1" == "--help" ]]; then
		usage
		exit 0
	fi

	COMMAND="$1"
	shift

	while (($# > 0)); do
		case "$1" in
		--include)
			[[ $# -ge 2 ]] || die "--include requires a comma-separated volume list."
			append_csv_values "$2" INCLUDE_VOLUMES
			shift 2
			;;
		--exclude)
			[[ $# -ge 2 ]] || die "--exclude requires a comma-separated volume list."
			append_csv_values "$2" EXCLUDE_VOLUMES
			shift 2
			;;
		--force)
			FORCE_RESTORE=1
			shift
			;;
		-h | --help)
			usage
			exit 0
			;;
		*)
			if [[ "${COMMAND}" == "restore" && -z "${RESTORE_SNAPSHOT}" ]]; then
				RESTORE_SNAPSHOT="$1"
				shift
			else
				die "Unknown argument: $1"
			fi
			;;
		esac
	done

	case "${COMMAND}" in
	backup | restore | snapshots | list-volumes | help) ;;
	*) die "Unknown command: ${COMMAND}" ;;
	esac

	if [[ "${COMMAND}" == "restore" && -z "${RESTORE_SNAPSHOT}" ]]; then
		die "restore requires a snapshot id or latest."
	fi
}

cleanup_on_exit() {
	local status=$?
	trap - EXIT
	if ((CONTAINERS_NEED_START == 1)); then
		set +e
		log "WARN" "Operation ended before containers were restarted; attempting to start previously running containers."
		start_recorded_containers
	fi
	exit "${status}"
}

handle_signal() {
	log "ERROR" "Interrupted by signal; exiting after cleanup."
	exit 130
}

require_root() {
	[[ "${EUID}" -eq 0 ]] || die "Root privileges are required because this script stops containers and manages Docker volumes."
}

require_command() {
	local command_name="$1"
	command -v "${command_name}" >/dev/null 2>&1 || die "${command_name} is required; install it before running this script."
}

install_packages() {
	local packages=("$@")

	if command -v apt-get >/dev/null 2>&1; then
		export DEBIAN_FRONTEND=noninteractive
		apt-get update -y -q >/dev/null
		apt-get install -y -q "${packages[@]}" >/dev/null
	elif command -v dnf >/dev/null 2>&1; then
		dnf install -y -q "${packages[@]}" >/dev/null
	elif command -v yum >/dev/null 2>&1; then
		yum install -y -q "${packages[@]}" >/dev/null
	else
		die "No supported package manager found; install packages manually: ${packages[*]}"
	fi
}

ensure_command_from_package() {
	local command_name="$1"
	local package_name="$2"

	if command -v "${command_name}" >/dev/null 2>&1; then
		return 0
	fi

	log "INFO" "Installing missing dependency command=${command_name} package=${package_name}."
	install_packages "${package_name}"
	command -v "${command_name}" >/dev/null 2>&1 || die "Failed to install dependency command=${command_name}."
}

version_at_least() {
	local current="$1"
	local minimum="$2"
	[[ "$(printf '%s\n%s\n' "${minimum}" "${current}" | sort -V | head -n 1)" == "${minimum}" ]]
}

restic_arch() {
	case "$(uname -m)" in
	x86_64) printf 'amd64\n' ;;
	aarch64 | arm64) printf 'arm64\n' ;;
	armv7l | armv6l) printf 'arm\n' ;;
	*) die "Unsupported architecture for automatic restic installation: $(uname -m)." ;;
	esac
}

ensure_restic() {
	local installed_version
	local architecture
	local filename
	local base_url
	local archive_path
	local sums_path
	local expected
	local actual

	if command -v restic >/dev/null 2>&1; then
		installed_version="$(restic version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n 1 || true)"
		if [[ -n "${installed_version}" ]] && version_at_least "${installed_version}" "${MIN_RESTIC_VERSION}"; then
			log "INFO" "restic is available version=${installed_version}."
			return 0
		fi
		log "WARN" "Installed restic is missing or too old version=${installed_version:-unknown}; installing version=${RESTIC_VERSION}."
	fi

	ensure_command_from_package curl curl
	ensure_command_from_package bunzip2 bzip2
	require_command sha256sum

	architecture="$(restic_arch)"
	filename="restic_${RESTIC_VERSION}_linux_${architecture}.bz2"
	base_url="https://github.com/restic/restic/releases/download/v${RESTIC_VERSION}"
	archive_path="/tmp/${filename}"
	sums_path="/tmp/restic_${RESTIC_VERSION}_SHA256SUMS"

	log "INFO" "Downloading restic archive=${filename}."
	curl -fsSL --retry 3 --retry-delay 2 -o "${archive_path}" "${base_url}/${filename}" || die "Failed to download restic archive=${filename}."
	curl -fsSL --retry 3 --retry-delay 2 -o "${sums_path}" "${base_url}/SHA256SUMS" || die "Failed to download restic checksums."

	expected="$(awk -v file="${filename}" '$2 == file {print $1}' "${sums_path}")"
	[[ -n "${expected}" ]] || die "Checksum entry was not found for restic archive=${filename}."
	actual="$(sha256sum "${archive_path}" | awk '{print $1}')"
	[[ "${actual}" == "${expected}" ]] || die "Restic checksum mismatch archive=${filename}."

	log "INFO" "Installing restic target=/usr/local/bin/restic."
	bunzip2 -c "${archive_path}" >/usr/local/bin/restic
	chmod 0755 /usr/local/bin/restic
	rm -f "${archive_path}" "${sums_path}"

	command -v restic >/dev/null 2>&1 || die "restic installation finished but the command is not available in PATH."
	log "INFO" "$(restic version | head -n 1) installed."
}

validate_restic_config() {
	[[ -n "${RESTIC_REPOSITORY:-}" ]] || die "RESTIC_REPOSITORY is empty; configure it at the top of this script."
	[[ -n "${RESTIC_PASSWORD:-}" ]] || die "RESTIC_PASSWORD is empty; configure it at the top of this script."
	[[ "${RESTIC_REPOSITORY}" != *"your-bucket-name"* ]] || die "RESTIC_REPOSITORY still contains the placeholder bucket name."
	[[ "${RESTIC_PASSWORD}" != "change-this-restic-password" ]] || die "RESTIC_PASSWORD still contains the placeholder value."

	if [[ "${RESTIC_REPOSITORY}" == s3:* ]]; then
		[[ "${AWS_ACCESS_KEY_ID:-}" != "YOUR_AWS_ACCESS_KEY" ]] || die "AWS_ACCESS_KEY_ID still contains the placeholder value."
		[[ "${AWS_SECRET_ACCESS_KEY:-}" != "YOUR_AWS_SECRET_KEY" ]] || die "AWS_SECRET_ACCESS_KEY still contains the placeholder value."
	fi
}

ensure_restic_repository() {
	if restic snapshots >/dev/null 2>&1; then
		log "INFO" "Restic repository is reachable repository=${RESTIC_REPOSITORY}."
		return 0
	fi

	log "INFO" "Restic repository is not initialized or not reachable; attempting init repository=${RESTIC_REPOSITORY}."
	restic init 2>&1 | tee -a "${LOG_FILE}" || die "Failed to initialize restic repository; check credentials, network, and bucket permissions."
}

require_restic_repository() {
	restic snapshots >/dev/null 2>&1 || die "Restic repository is not reachable; check repository, password, network, and credentials."
}

ensure_helper_image() {
	if docker image inspect "${HELPER_IMAGE}" >/dev/null 2>&1; then
		log "INFO" "Docker helper image is available image=${HELPER_IMAGE}."
		return 0
	fi

	log "INFO" "Pulling Docker helper image image=${HELPER_IMAGE}."
	docker pull "${HELPER_IMAGE}" 2>&1 | tee -a "${LOG_FILE}" || die "Failed to pull Docker helper image image=${HELPER_IMAGE}."
}

contains_value() {
	local needle="$1"
	shift
	local candidate

	for candidate in "$@"; do
		[[ "${candidate}" == "${needle}" ]] && return 0
	done
	return 1
}

select_existing_volumes() {
	local available=()
	local selected=()
	local volume

	if ((${#INCLUDE_VOLUMES[@]} > 0)); then
		for volume in "${INCLUDE_VOLUMES[@]}"; do
			docker volume inspect "${volume}" >/dev/null 2>&1 || die "Included volume does not exist volume=${volume}."
			selected+=("${volume}")
		done
	else
		mapfile -t available < <(docker volume ls -q | sort)
		for volume in "${available[@]}"; do
			contains_value "${volume}" "${EXCLUDE_VOLUMES[@]}" && continue
			selected+=("${volume}")
		done
	fi

	if ((${#INCLUDE_VOLUMES[@]} > 0 && ${#EXCLUDE_VOLUMES[@]} > 0)); then
		available=("${selected[@]}")
		selected=()
		for volume in "${available[@]}"; do
			contains_value "${volume}" "${EXCLUDE_VOLUMES[@]}" && continue
			selected+=("${volume}")
		done
	fi

	((${#selected[@]} > 0)) || die "No Docker volumes selected."
	printf '%s\n' "${selected[@]}"
}

record_running_containers() {
	mapfile -t RUNNING_CONTAINERS < <(docker ps -q)

	if [[ -n "${RUN_DIR}" ]]; then
		docker ps --format '{{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}' >"${RUN_DIR}/running_containers.tsv"
	elif [[ -n "${RESTORE_DIR}" ]]; then
		docker ps --format '{{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}' >"${RESTORE_DIR}/running_containers.tsv"
	fi

	log "INFO" "Recorded running containers count=${#RUNNING_CONTAINERS[@]}."
}

stop_running_containers() {
	if ((${#RUNNING_CONTAINERS[@]} == 0)); then
		log "INFO" "No running containers need to be stopped."
		return 0
	fi

	log "INFO" "Stopping running containers count=${#RUNNING_CONTAINERS[@]} timeout_seconds=${STOP_TIMEOUT}."
	CONTAINERS_NEED_START=1
	docker stop -t "${STOP_TIMEOUT}" "${RUNNING_CONTAINERS[@]}" 2>&1 | tee -a "${LOG_FILE}" || die "Failed to stop running containers; check Docker daemon state."
}

start_recorded_containers() {
	if ((${#RUNNING_CONTAINERS[@]} == 0)); then
		log "INFO" "No containers were recorded for restart."
		CONTAINERS_NEED_START=0
		return 0
	fi

	log "INFO" "Starting previously running containers count=${#RUNNING_CONTAINERS[@]}."
	if docker start "${RUNNING_CONTAINERS[@]}" 2>&1 | tee -a "${LOG_FILE}"; then
		CONTAINERS_NEED_START=0
		return 0
	fi

	log "ERROR" "Failed to restart one or more containers; run docker ps -a and inspect the stopped containers."
	return 1
}

prepare_backup_run_dir() {
	RUN_DIR="${STAGING_ROOT}/${TIMESTAMP}"
	mkdir -p "${RUN_DIR}/volumes"
	printf 'volume\tbytes\ttar_path\n' >"${RUN_DIR}/backup_manifest.tsv"
	{
		printf 'script_version\t%s\n' "${SCRIPT_VERSION}"
		printf 'timestamp\t%s\n' "${TIMESTAMP}"
		printf 'hostname\t%s\n' "$(hostname)"
		printf 'helper_image\t%s\n' "${HELPER_IMAGE}"
		printf 'backup_tag\t%s\n' "${BACKUP_TAG}"
	} >"${RUN_DIR}/backup_metadata.tsv"
	docker volume ls --format '{{.Name}}\t{{.Driver}}' >"${RUN_DIR}/docker_volumes.tsv"
}

export_volume() {
	local volume="$1"
	local volume_dir="${RUN_DIR}/volumes/${volume}"
	local tar_path="${volume_dir}/data.tar"
	local bytes

	mkdir -p "${volume_dir}"
	log "INFO" "Exporting Docker volume volume=${volume} tar=${tar_path}."

	docker run --rm \
		-v "${volume}:/volume:ro" \
		-v "${volume_dir}:/backup" \
		"${HELPER_IMAGE}" \
		sh -c 'set -eu; cd /volume; tar --numeric-owner --xattrs --acls -cpf /backup/data.tar .' \
		2>&1 | tee -a "${LOG_FILE}" || die "Failed to export Docker volume volume=${volume}."

	bytes="$(wc -c <"${tar_path}")"
	printf '%s\t%s\t%s\n' "${volume}" "${bytes}" "${tar_path}" >>"${RUN_DIR}/backup_manifest.tsv"
	log "INFO" "Exported Docker volume volume=${volume} bytes=${bytes}."
}

run_restic_backup() {
	local attempt=1

	restic unlock 2>&1 | tee -a "${LOG_FILE}" || true

	while ((attempt <= RESTIC_RETRIES)); do
		log "INFO" "Running restic backup attempt=${attempt} path=${RUN_DIR}."
		if restic backup \
			--compression max \
			--tag "${BACKUP_TAG}" \
			--tag "ts-${TIMESTAMP}" \
			"${RUN_DIR}" 2>&1 | tee -a "${LOG_FILE}"; then
			log "INFO" "Restic backup completed path=${RUN_DIR}."
			return 0
		fi

		log "WARN" "Restic backup attempt failed attempt=${attempt}."
		((attempt++))
		if ((attempt <= RESTIC_RETRIES)); then
			sleep 10
		fi
	done

	die "Restic backup failed after retries=${RESTIC_RETRIES}; staging data remains at ${RUN_DIR}."
}

prune_restic_snapshots() {
	if ((PRUNE_AFTER_BACKUP != 1)); then
		log "INFO" "Skipping restic prune because PRUNE_AFTER_BACKUP=${PRUNE_AFTER_BACKUP}."
		return 0
	fi

	log "INFO" "Pruning restic snapshots tag=${BACKUP_TAG}."
	restic forget \
		--tag "${BACKUP_TAG}" \
		--keep-last "${KEEP_LAST}" \
		--keep-daily "${KEEP_DAILY}" \
		--keep-weekly "${KEEP_WEEKLY}" \
		--keep-monthly "${KEEP_MONTHLY}" \
		--prune 2>&1 | tee -a "${LOG_FILE}" || log "WARN" "Restic prune did not complete cleanly; inspect repository health."
}

remove_backup_staging() {
	if ((REMOVE_STAGING_AFTER_BACKUP != 1)); then
		log "INFO" "Keeping local staging path=${RUN_DIR}."
		return 0
	fi

	rm -rf "${RUN_DIR}"
	log "INFO" "Removed local staging path=${RUN_DIR}."
}

backup_command() {
	local volumes=()
	local volume

	prepare_backup_run_dir
	mapfile -t volumes < <(select_existing_volumes)

	log "INFO" "Selected Docker volumes count=${#volumes[@]} volumes=${volumes[*]}."
	record_running_containers
	stop_running_containers

	for volume in "${volumes[@]}"; do
		export_volume "${volume}"
	done

	run_restic_backup
	prune_restic_snapshots
	start_recorded_containers
	remove_backup_staging
	log "INFO" "Cold Docker volume backup finished."
}

prepare_restore_dir() {
	local safe_snapshot="${RESTORE_SNAPSHOT//[^A-Za-z0-9_.-]/_}"
	RESTORE_DIR="${RESTORE_ROOT}/${TIMESTAMP}_${safe_snapshot}"
	mkdir -p "${RESTORE_DIR}"
}

restore_snapshot_files() {
	log "INFO" "Restoring restic snapshot snapshot=${RESTORE_SNAPSHOT} target=${RESTORE_DIR}."
	restic restore "${RESTORE_SNAPSHOT}" --target "${RESTORE_DIR}" 2>&1 | tee -a "${LOG_FILE}" || die "Failed to restore restic snapshot snapshot=${RESTORE_SNAPSHOT}."
}

find_restored_backup_root() {
	local manifests=()
	local manifest

	mapfile -t manifests < <(find "${RESTORE_DIR}" -type f -name 'backup_manifest.tsv' | sort)
	if ((${#manifests[@]} != 1)); then
		die "Expected exactly one backup manifest in restored snapshot count=${#manifests[@]} target=${RESTORE_DIR}."
	fi

	manifest="${manifests[0]}"
	dirname "${manifest}"
}

select_restored_volume_dirs() {
	local backup_root="$1"
	local volume_root="${backup_root}/volumes"
	local dirs=()
	local selected=()
	local dir
	local volume

	[[ -d "${volume_root}" ]] || die "Restored snapshot does not contain a volumes directory path=${volume_root}."

	mapfile -t dirs < <(find "${volume_root}" -mindepth 1 -maxdepth 1 -type d | sort)
	for dir in "${dirs[@]}"; do
		volume="$(basename "${dir}")"
		if ((${#INCLUDE_VOLUMES[@]} > 0)) && ! contains_value "${volume}" "${INCLUDE_VOLUMES[@]}"; then
			continue
		fi
		contains_value "${volume}" "${EXCLUDE_VOLUMES[@]}" && continue
		selected+=("${dir}")
	done

	((${#selected[@]} > 0)) || die "No restored volume archives selected."
	printf '%s\n' "${selected[@]}"
}

clear_volume() {
	local volume="$1"

	log "INFO" "Clearing existing Docker volume volume=${volume}."
	docker run --rm \
		-v "${volume}:/volume" \
		"${HELPER_IMAGE}" \
		sh -c 'set -eu; find /volume -mindepth 1 -maxdepth 1 -exec rm -rf {} \;' \
		2>&1 | tee -a "${LOG_FILE}" || die "Failed to clear Docker volume volume=${volume}."
}

import_volume() {
	local volume="$1"
	local volume_dir="$2"
	local tar_path="${volume_dir}/data.tar"

	[[ -f "${tar_path}" ]] || die "Volume archive is missing volume=${volume} tar=${tar_path}."

	if docker volume inspect "${volume}" >/dev/null 2>&1; then
		if ((FORCE_RESTORE != 1)); then
			die "Docker volume already exists volume=${volume}; rerun restore with --force to overwrite its content."
		fi
		clear_volume "${volume}"
	else
		log "INFO" "Creating Docker volume volume=${volume}."
		docker volume create "${volume}" >/dev/null
	fi

	log "INFO" "Importing Docker volume volume=${volume} tar=${tar_path}."
	docker run --rm \
		-v "${volume}:/volume" \
		-v "${volume_dir}:/backup:ro" \
		"${HELPER_IMAGE}" \
		sh -c 'set -eu; cd /volume; tar --numeric-owner --xattrs --acls -xpf /backup/data.tar' \
		2>&1 | tee -a "${LOG_FILE}" || die "Failed to import Docker volume volume=${volume}."
}

restore_command() {
	local backup_root
	local volume_dirs=()
	local volume_dir
	local volume

	prepare_restore_dir
	restore_snapshot_files
	backup_root="$(find_restored_backup_root)"
	mapfile -t volume_dirs < <(select_restored_volume_dirs "${backup_root}")

	log "INFO" "Selected restored Docker volumes count=${#volume_dirs[@]}."
	if ((FORCE_RESTORE == 1)); then
		record_running_containers
		stop_running_containers
	fi

	for volume_dir in "${volume_dirs[@]}"; do
		volume="$(basename "${volume_dir}")"
		import_volume "${volume}" "${volume_dir}"
	done

	if ((FORCE_RESTORE == 1)); then
		start_recorded_containers
	fi

	log "INFO" "Docker volume restore finished restored_files=${RESTORE_DIR}."
}

snapshots_command() {
	restic snapshots --tag "${BACKUP_TAG}" 2>&1 | tee -a "${LOG_FILE}"
}

list_volumes_command() {
	docker volume ls 2>&1 | tee -a "${LOG_FILE}"
}

acquire_lock() {
	exec 9>"${LOCK_FILE}"
	flock -n 9 || die "Another ${SCRIPT_NAME} process is already running lock=${LOCK_FILE}."
}

main() {
	parse_args "$@"
	[[ "${COMMAND}" == "help" ]] && usage && exit 0

	trap cleanup_on_exit EXIT
	trap handle_signal INT TERM

	require_root
	prepare_log_dir
	require_command flock
	acquire_lock
	require_command docker
	if [[ "${COMMAND}" != "list-volumes" ]]; then
		validate_restic_config
		ensure_restic
	fi

	case "${COMMAND}" in
	backup)
		ensure_helper_image
		ensure_restic_repository
		backup_command
		;;
	restore)
		ensure_helper_image
		require_restic_repository
		restore_command
		;;
	snapshots)
		require_restic_repository
		snapshots_command
		;;
	list-volumes)
		list_volumes_command
		;;
	esac

	log "INFO" "Command completed command=${COMMAND}."
}

main "$@"
