#!/usr/bin/env bash
#
# omv-microvm-backup — back up a VM's disk (and its live memory/device
# state, if running) to an admin-chosen directory, with optional retention.
#
# Delegates the actual capture to omv-microvm-snapshot-create, so a backup
# gets exactly the same warm (paused, memory-state) or cold behavior as a
# manual snapshot — this script only adds a chosen destination, a
# meta.json sidecar, a list file for the backup browser UI, and retention.
#
# Usage: omv-microvm-backup -v <vm-name> -d <backup-dir> [-k <keep>]
#                            [-n <name>] [-N <notes>] [-e onerror|always]
#                            [-C <comment>]

set -uo pipefail

if [[ $(id -u) -ne 0 ]]; then
  echo "This script must be executed as root or using sudo."
  exit 99
fi

export LC_ALL=C.UTF-8

declare -i keep=0

backupVm=""
backupDir=""
backupName=""
backupNotes=""
email_mode=""
email_comment=""

logDir="/var/log"
logFile="${logDir}/omv-microvm-backup.log"
listFile="/etc/omv-microvm-backup.list"

_log() {
  echo "[$(date +'%Y-%m-%d %H:%M:%S%z')] [backup] ${1}" | tee -a "${logFile}" >&2
}

_json_escape() {
  printf '%s' "${1}" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr -d '\n'
}

while getopts "C:d:e:hk:n:N:v:" opt; do
  case "${opt}" in
    C)
      email_comment="${OPTARG}"
      ;;
    d)
      backupDir="${OPTARG}"
      ;;
    e)
      email_mode="${OPTARG}"
      ;;
    h)
      echo "Use the following flags:"
      echo "  -d dir"
      echo "    backup destination directory"
      echo "  -e onerror|always"
      echo "    send email on error only or always"
      echo "  -k #"
      echo "    keep the most recent # backups for this VM (0 keeps all)"
      echo "  -n name"
      echo "    optional label for this backup"
      echo "  -N notes"
      echo "    optional notes for this backup"
      echo "  -v vm-name"
      echo "    VM name to back up"
      echo ""
      echo "Example:"
      echo "  omv-microvm-backup -v testvm1 -d /srv/backup/ -k 5"
      echo ""
      exit 100
      ;;
    k)
      keep=${OPTARG}
      ;;
    n)
      backupName="${OPTARG}"
      ;;
    N)
      backupNotes="${OPTARG}"
      ;;
    v)
      backupVm="${OPTARG}"
      ;;
    \?)
      echo "Invalid option: -${OPTARG}"
      exit 1
      ;;
  esac
done

tmpfile=""
if [ -n "${email_mode}" ]; then
  tmpfile=$(mktemp)
  exec > "${tmpfile}" 2>&1
fi

_send_email() {
  local rc="$1"
  if [ -n "${tmpfile}" ] && [ -f "${tmpfile}" ]; then
    if [ "${email_mode}" = "always" ] || { [ "${email_mode}" = "onerror" ] && [ "${rc}" -ne 0 ]; }; then
      mail -E -s "Cron${email_comment:+ - ${email_comment}}" -a "From: Cron Daemon <root>" root < "${tmpfile}" > /dev/null 2>&1
    fi
    rm -f "${tmpfile}"
  fi
}
trap '_send_email $?' EXIT

if [ -z "${backupVm}" ]; then
  _log "No VM specified!"
  exit 4
fi

if [ ! -d "${backupDir}" ]; then
  _log "Backup directory '${backupDir}' does not exist!"
  exit 3
fi

if ! command -v omv-microvm-snapshot-create > /dev/null 2>&1; then
  _log "omv-microvm-snapshot-create not found!"
  exit 2
fi

date="$(date +'%Y-%m-%d_%H-%M-%S')"
start=$(date +%s)
_log "Starting backup of '${backupVm}' ..."

vmBackupDir="${backupDir%/}/${backupVm}/${date}"

if ! omv-microvm-snapshot-create "${backupVm}" "${vmBackupDir}"; then
  _log "Backup capture failed!"
  exit 5
fi

size=0
for f in "${vmBackupDir}"/memfile "${vmBackupDir}"/vmstate "${vmBackupDir}"/rootfs.ext4 "${vmBackupDir}"/disk-*.img; do
  fsize=$(stat -c%s "${f}" 2>/dev/null || echo 0)
  size=$((size + fsize))
done

warm="false"
if [ -f "${vmBackupDir}/vmstate" ] && [ -f "${vmBackupDir}/memfile" ]; then
  warm="true"
fi

cat > "${vmBackupDir}/meta.json" <<EOF
{"name":"$(_json_escape "${backupName}")","notes":"$(_json_escape "${backupNotes}")","created":"$(date -Iseconds)","size_bytes":${size},"warm":${warm}}
EOF

_log "Adding entry to list file '${listFile}'"
echo "$(cat /proc/sys/kernel/random/uuid),${backupDir},${backupVm},${date},${size}" >> "${listFile}"

if [ ${keep} -gt 0 ]; then
  _log "Keeping the most recent ${keep} backups..."
  keepDir="${backupDir%/}/${backupVm}/"
  OFS=$IFS
  IFS=$'\n'
  delDirs=$(find "${keepDir}" -mindepth 1 -maxdepth 1 -type d | sort -r | awk "NR>${keep}")
  for d in ${delDirs}; do
    delDate="$(basename "${d}")"
    _log "Removing ${d}..."
    rm -rf "${d}"
    sed -i "\|${backupDir%/}/\?,${backupVm},${delDate}|d" "${listFile}"
  done
  IFS=$OFS
fi

end=$(date +%s)
_log "Backup size :: ${size} bytes"
_log "Backup time :: $((end-start)) secs"
_log "Done."

exit 0
