Finance-Accounts/ar-aging-app/deploy/backup.sh

45 lines
2.1 KiB
Bash

#!/usr/bin/env bash
# Nightly backup: MySQL dump + uploads/exports -> S3 (versioned bucket).
#
# Install on the server (as the user that runs docker):
# crontab -e
# 30 2 * * * /opt/ar-aging/ar-aging-app/deploy/backup.sh >> /var/log/ar-backup.log 2>&1
#
# Requires: aws cli v2 on the host, an instance IAM role with s3:PutObject/ListBucket on
# the bucket (no access keys on disk), and .env.production next to docker-compose.prod.yml.
set -euo pipefail
APP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$APP_DIR"
# shellcheck disable=SC1091
set -a; source .env.production; set +a
BUCKET="${AR_BACKUP_S3_BUCKET:?AR_BACKUP_S3_BUCKET not set in .env.production}"
STAMP="$(date +%Y-%m-%d_%H%M)"
COMPOSE="docker compose --env-file .env.production -f docker-compose.prod.yml"
echo "[$STAMP] backup starting"
# 1) MySQL dump (single transaction: consistent snapshot without locking the app out).
$COMPOSE exec -T mysql sh -c \
'exec mysqldump --single-transaction --quick --routines \
-u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE"' \
| gzip > "/tmp/ar_aging_${STAMP}.sql.gz"
aws s3 cp "/tmp/ar_aging_${STAMP}.sql.gz" "$BUCKET/mysql/ar_aging_${STAMP}.sql.gz"
rm -f "/tmp/ar_aging_${STAMP}.sql.gz"
# 2) Uploaded source files + generated exports (the audit trail).
# The ar_data volume is mounted by the backend container; sync straight from it.
DATA_MOUNT="$(docker volume inspect -f '{{ .Mountpoint }}' \
"$(basename "$APP_DIR" | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z0-9')_ar_data" 2>/dev/null \
|| docker volume inspect -f '{{ .Mountpoint }}' ar-aging-app_ar_data)"
aws s3 sync "$DATA_MOUNT/uploads" "$BUCKET/data/uploads" --only-show-errors
aws s3 sync "$DATA_MOUNT/exports" "$BUCKET/data/exports" --only-show-errors
echo "[$STAMP] backup finished"
# Restore drill (run quarterly — a backup you never restored is a hope, not a backup):
# aws s3 cp "$BUCKET/mysql/<latest>.sql.gz" - | gunzip | \
# docker compose --env-file .env.production -f docker-compose.prod.yml exec -T mysql \
# sh -c 'exec mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE"'