#!/usr/bin/env bash
# Nightly database backup for one instance.
#
# Every instance (production, demo, nomad, tanym) runs its own mysqld in its
# own container against its own Docker volume, so backups are per-checkout:
# run this from the checkout whose database you want, passing that instance's
# compose project name. Reads DB_NAME / MYSQL_ROOT_PASSWORD (and the optional
# BACKUP_* settings) from this checkout's own root .env, same as
# scripts/init_branded_db.sh.
#
#   scripts/backup_db.sh <compose-project-name> [--compose-file <file>]
#
# Cron it (see README.md's "Backups" section) — 02:30 keeps it clear of the
# 23:59 / 00:15 scheduled billing jobs in laravel/bootstrap/app.php:
#
#   30 2 * * * /var/www/<checkout>/scripts/backup_db.sh <project> >> /var/log/axioma-backup.log 2>&1
#
# Restore with scripts/restore_db.sh. An untested backup isn't a backup —
# do the round-trip at least once per instance.

set -euo pipefail

PROJECT="${1:?Usage: scripts/backup_db.sh <compose-project-name> [--compose-file <file>]}"
shift

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
COMPOSE_FILE="$REPO_ROOT/docker-compose.yml"

while [ $# -gt 0 ]; do
    case "$1" in
        --compose-file)
            COMPOSE_FILE="$2"
            shift 2
            ;;
        *)
            echo "Unknown argument: $1" >&2
            exit 2
            ;;
    esac
done

# shellcheck disable=SC1091
source "$REPO_ROOT/.env"

: "${DB_NAME:?DB_NAME must be set in .env}"
: "${MYSQL_ROOT_PASSWORD:?MYSQL_ROOT_PASSWORD must be set in .env}"

BACKUP_DIR="${BACKUP_DIR:-/var/backups/axioma}"
BACKUP_KEEP_DAYS="${BACKUP_KEEP_DAYS:-14}"
BACKUP_REMOTE="${BACKUP_REMOTE:-}"

DEST_DIR="$BACKUP_DIR/$PROJECT"
STAMP="$(date +%Y-%m-%d-%H%M)"
DEST="$DEST_DIR/$DB_NAME-$STAMP.sql.gz"

COMPOSE=(docker compose -f "$COMPOSE_FILE" -p "$PROJECT")

mkdir -p "$DEST_DIR"

echo "[$(date '+%F %T')] Dumping '$DB_NAME' from project '$PROJECT' -> $DEST"

# The password is expanded INSIDE the container, so it never appears in this
# host's process list (which `mysqldump -p"$PASS"` from the host would leak to
# every user via ps). --single-transaction gives an InnoDB-consistent snapshot
# without locking the live site.
if ! "${COMPOSE[@]}" exec -T db sh -c \
    'exec mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" \
        --single-transaction --quick --routines --triggers \
        --default-character-set=utf8mb4 \
        "$0"' "$DB_NAME" | gzip -9 > "$DEST"
then
    echo "ERROR: mysqldump failed; removing partial $DEST" >&2
    rm -f "$DEST"
    exit 1
fi

# A dump that "succeeded" but produced nothing is the failure mode that goes
# unnoticed for months — treat an implausibly small file as an error.
SIZE=$(wc -c < "$DEST")
if [ "$SIZE" -lt 1024 ]; then
    echo "ERROR: $DEST is only ${SIZE}B — treating as a failed backup" >&2
    rm -f "$DEST"
    exit 1
fi

if ! gzip -t "$DEST" 2>/dev/null; then
    echo "ERROR: $DEST is not a valid gzip archive" >&2
    rm -f "$DEST"
    exit 1
fi

echo "[$(date '+%F %T')] Wrote $DEST (${SIZE}B)"

# Off-site copy. A dump on the same disk as the database protects against a
# dropped volume or a bad migration, NOT against losing the machine — this is
# the half that makes it a real backup. Missing config is a loud warning
# rather than a failure, so the local dump still counts as done.
if [ -n "$BACKUP_REMOTE" ]; then
    if command -v rclone >/dev/null 2>&1; then
        echo "[$(date '+%F %T')] Copying to $BACKUP_REMOTE"
        if rclone copy "$DEST" "$BACKUP_REMOTE/$PROJECT/"; then
            echo "[$(date '+%F %T')] Off-site copy done"
        else
            echo "WARNING: rclone copy to $BACKUP_REMOTE FAILED — this instance has only a local backup tonight" >&2
        fi
    else
        echo "WARNING: BACKUP_REMOTE is set but rclone is not installed — local backup only" >&2
    fi
else
    echo "WARNING: BACKUP_REMOTE is unset — local backup only, nothing survives losing this server" >&2
fi

# Retention. Only ever touches this project's own directory, and only files
# matching the dump name pattern.
DELETED=$(find "$DEST_DIR" -maxdepth 1 -name "$DB_NAME-*.sql.gz" -type f -mtime "+$BACKUP_KEEP_DAYS" -print -delete | wc -l)
if [ "$DELETED" -gt 0 ]; then
    echo "[$(date '+%F %T')] Pruned $DELETED dump(s) older than $BACKUP_KEEP_DAYS days"
fi

REMAINING=$(find "$DEST_DIR" -maxdepth 1 -name "$DB_NAME-*.sql.gz" -type f | wc -l)
echo "[$(date '+%F %T')] Done. $REMAINING dump(s) retained in $DEST_DIR"
