#!/bin/bash
# /usr/local/sbin/web1-usb-backup.sh

set -Eeuo pipefail

BACKUP_ROOT="/mnt/backup-usb"
SNAPSHOT_ROOT="${BACKUP_ROOT}/snapshots"
DB_ROOT="${BACKUP_ROOT}/database"
LOG_ROOT="${BACKUP_ROOT}/logs"
LOCK_FILE="/var/lock/web1-usb-backup.lock"
KEEP_SNAPSHOTS="21"
KEEP_DB_DUMPS="30"
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
SNAPSHOT_DIR="${SNAPSHOT_ROOT}/${TIMESTAMP}"
FILES_DIR="${SNAPSHOT_DIR}/files"
DB_DUMP_FILE="${DB_ROOT}/${TIMESTAMP}-all-databases.sql.gz"
LOG_FILE="${LOG_ROOT}/web1-usb-backup.log"

mkdir -p "${SNAPSHOT_ROOT}" "${DB_ROOT}" "${LOG_ROOT}"
exec 9>"${LOCK_FILE}"
flock -n 9 || { echo "Backup already running."; exit 1; }

log() {
  local msg="$1"
  printf '%s %s\n' "$(date -Iseconds)" "${msg}" | tee -a "${LOG_FILE}"
}

require_mount() {
  if ! findmnt -n "${BACKUP_ROOT}" >/dev/null 2>&1; then
    log "ERROR: ${BACKUP_ROOT} is not mounted."
    exit 1
  fi
}

sync_path() {
  local src="$1"
  if [ -d "${src}" ]; then
    mkdir -p "${FILES_DIR}${src}"
    rsync -aHAX --numeric-ids --delete "${src}/" "${FILES_DIR}${src}/"
    log "Synced directory ${src}"
  elif [ -f "${src}" ]; then
    mkdir -p "${FILES_DIR}$(dirname "${src}")"
    rsync -aHAX --numeric-ids "${src}" "${FILES_DIR}${src}"
    log "Synced file ${src}"
  else
    log "Skipped missing path ${src}"
  fi
}

prune_old() {
  local target_dir="$1"
  local keep_count="$2"
  local type="$3"
  python3 - <<PY
from pathlib import Path
import shutil

target = Path(${target_dir@Q})
keep = int(${keep_count@Q})
kind = ${type@Q}

items = []
for p in target.iterdir():
    if kind == 'dir' and p.is_dir() and p.name != 'latest':
        items.append(p)
    elif kind == 'file' and p.is_file() and p.name != 'latest-all-databases.sql.gz':
        items.append(p)

items.sort(key=lambda x: x.stat().st_mtime, reverse=True)
for old in items[keep:]:
    if old.is_dir():
        shutil.rmtree(old, ignore_errors=True)
    else:
        old.unlink(missing_ok=True)
PY
}

main() {
  require_mount
  log "Starting backup run ${TIMESTAMP}"

  mkdir -p "${FILES_DIR}"

  sync_path "/var/www"
  sync_path "/etc/nginx"
  sync_path "/etc/php/8.3/fpm"
  sync_path "/etc/mysql/mariadb.conf.d"
  sync_path "/etc/letsencrypt"
  sync_path "/etc/systemd/system"
  sync_path "/usr/local/sbin"
  sync_path "/etc/fstab"
  sync_path "/etc/hosts"
  sync_path "/etc/hostname"

  log "Dumping MariaDB databases"
  mysqldump --single-transaction --routines --events --triggers --all-databases | gzip -1 > "${DB_DUMP_FILE}"

  cat > "${SNAPSHOT_DIR}/manifest.txt" <<MANIFEST
Snapshot: ${TIMESTAMP}
Host: $(hostname -f)
Created: $(date -Iseconds)
Files root: ${FILES_DIR}
Database dump: ${DB_DUMP_FILE}
MANIFEST

  ln -sfn "${TIMESTAMP}" "${SNAPSHOT_ROOT}/latest"
  ln -sfn "$(basename "${DB_DUMP_FILE}")" "${DB_ROOT}/latest-all-databases.sql.gz"

  prune_old "${SNAPSHOT_ROOT}" "${KEEP_SNAPSHOTS}" "dir"
  prune_old "${DB_ROOT}" "${KEEP_DB_DUMPS}" "file"

  log "Backup completed successfully: ${TIMESTAMP}"
}

main "$@"
