#!/usr/bin/env bash # /home/spuser/PassKeeper/scripts/backup_db.sh # # Automated MySQL backup for PassKeeper. # - Dumps the passkeeper database with mysqldump # - Compresses with gzip # - Retains the last 30 daily backups # - Logs all actions with timestamps # - Exits non-zero on any failure so cron can email on error # # Usage: # chmod +x scripts/backup_db.sh # # Test manually: # bash scripts/backup_db.sh # # Schedule via cron (see scripts/backup.cron) set -euo pipefail # ── Configuration ───────────────────────────────────────────────────────────── ENV_FILE="$(dirname "$(realpath "$0")")/../.env" # Load .env variables if [[ -f "$ENV_FILE" ]]; then set -o allexport # shellcheck disable=SC1090 source "$ENV_FILE" set +o allexport else echo "ERROR: .env file not found at $ENV_FILE" >&2 exit 1 fi BACKUP_DIR="${BACKUP_DIR:-/home/spuser/backups/passkeeper}" RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-30}" TIMESTAMP="$(date +%Y%m%d_%H%M%S)" BACKUP_FILE="${BACKUP_DIR}/passkeeper_${TIMESTAMP}.sql.gz" LOG_FILE="${BACKUP_DIR}/backup.log" # MySQL credentials from .env DB_HOST="${MYSQL_HOST:-127.0.0.1}" DB_PORT="${MYSQL_PORT:-3306}" DB_USER="${MYSQL_USER:-passkeeper}" DB_PASS="${MYSQL_PASSWORD}" DB_NAME="${MYSQL_DB:-passkeeper}" # ── Helpers ─────────────────────────────────────────────────────────────────── log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; } # ── Pre-flight ──────────────────────────────────────────────────────────────── mkdir -p "$BACKUP_DIR" chmod 700 "$BACKUP_DIR" log "INFO Starting backup → $BACKUP_FILE" # ── Dump ───────────────────────────────────────────────────────────────────── # --single-transaction: consistent snapshot without locking (InnoDB) # --routines --events: include stored procedures/events if any # --no-tablespaces: avoid PROCESS privilege requirement on MySQL 8+ mysqldump \ --host="$DB_HOST" \ --port="$DB_PORT" \ --user="$DB_USER" \ --password="$DB_PASS" \ --single-transaction \ --routines \ --events \ --no-tablespaces \ "$DB_NAME" \ | gzip -9 > "$BACKUP_FILE" BACKUP_SIZE="$(du -sh "$BACKUP_FILE" | cut -f1)" log "INFO Backup complete — size: $BACKUP_SIZE" # ── Verify the file is non-empty ────────────────────────────────────────────── if [[ ! -s "$BACKUP_FILE" ]]; then log "ERROR Backup file is empty — aborting retention cleanup" exit 1 fi # ── Retention: delete backups older than RETENTION_DAYS ────────────────────── DELETED=$(find "$BACKUP_DIR" -maxdepth 1 -name 'passkeeper_*.sql.gz' \ -mtime +"$RETENTION_DAYS" -print -delete | wc -l) log "INFO Retention cleanup: removed $DELETED file(s) older than ${RETENTION_DAYS} days" log "INFO Backup finished successfully"