Backup Monitoring

Complete examples for monitoring backup jobs

PostgreSQL Backup

Complete script for monitoring PostgreSQL backups with error detection:

#!/bin/bash
# postgres-backup.sh

set -e

# Configuration
DB_NAME="production"
BACKUP_DIR="/backups/postgres"
MONITOR_URL="https://telemetry.host/ping/PROJECT_KEY/timeout/26h/postgres-backup?create=1"
RETENTION_DAYS=30

# Create backup directory
mkdir -p "$BACKUP_DIR"

# Generate backup filename
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/${DB_NAME}_${TIMESTAMP}.sql.gz"

# Start time
START_TIME=$(date +%s)

# Perform backup
echo "Starting PostgreSQL backup..."
if pg_dump "$DB_NAME" | gzip > "$BACKUP_FILE"; then
    # Calculate duration
    END_TIME=$(date +%s)
    DURATION=$((END_TIME - START_TIME))
    
    # Get backup size
    SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
    
    # Clean old backups
    find "$BACKUP_DIR" -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete
    REMAINING=$(find "$BACKUP_DIR" -name "*.sql.gz" | wc -l)
    
    # Report success
    curl -X POST "$MONITOR_URL" \
        -H "Content-Type: application/json" \
        -d "{
            \"status\": \"success\",
            \"message\": \"Backup completed: $SIZE in ${DURATION}s\",
            \"duration\": $DURATION,
            \"metadata\": {
                \"file\": \"$BACKUP_FILE\",
                \"size\": \"$SIZE\",
                \"backups_retained\": $REMAINING
            }
        }"
    
    echo "Backup completed successfully"
else
    # Report failure
    curl -X POST "$MONITOR_URL" \
        -d '{"status":"error","message":"Backup failed"}'
    
    echo "Backup failed!" >&2
    exit 1
fi

Add to crontab:

# Daily at 2 AM
0 2 * * * /usr/local/bin/postgres-backup.sh

MySQL Backup with Verification

#!/bin/bash
# mysql-backup-verified.sh

DB_NAME="myapp"
BACKUP_DIR="/backups/mysql"
MONITOR_URL="https://telemetry.host/ping/YOUR_MONITOR_ID"

# Backup
BACKUP_FILE="$BACKUP_DIR/${DB_NAME}_$(date +%Y%m%d).sql.gz"
mysqldump "$DB_NAME" | gzip > "$BACKUP_FILE"

# Verify backup integrity
if gunzip -t "$BACKUP_FILE" 2>/dev/null; then
    SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
    curl -X POST "$MONITOR_URL" \
        -d "{\"status\":\"success\",\"message\":\"Backup verified: $SIZE\"}"
else
    curl -X POST "$MONITOR_URL" \
        -d '{"status":"error","message":"Backup verification failed"}'
    exit 1
fi

File System Backup with rsync

#!/bin/bash
# rsync-backup.sh

SOURCE="/var/www"
DEST="/mnt/backup/www"
MONITOR_URL="https://telemetry.host/ping/YOUR_MONITOR_ID"

# Create log file
LOG_FILE="/tmp/rsync-backup-$(date +%Y%m%d).log"

# Perform rsync
if rsync -avz --delete "$SOURCE/" "$DEST/" > "$LOG_FILE" 2>&1; then
    # Extract stats
    FILES=$(grep "Number of files transferred" "$LOG_FILE" | awk '{print $5}')
    SIZE=$(grep "Total transferred file size" "$LOG_FILE" | awk '{print $5,$6}')
    
    # Send log and stats
    {
        echo "Backup completed"
        echo "Files transferred: $FILES"
        echo "Total size: $SIZE"
        echo ""
        cat "$LOG_FILE"
    } | curl -X POST "$MONITOR_URL" \
        -H "Content-Type: text/plain" \
        --data-binary @-
else
    # Send error
    cat "$LOG_FILE" | curl -X POST "$MONITOR_URL" \
        -H "Content-Type: text/plain" \
        --data-binary @-
    exit 1
fi

rm "$LOG_FILE"

S3 Backup

#!/bin/bash
# s3-backup.sh

SOURCE_DIR="/data"
S3_BUCKET="s3://my-backups/daily"
MONITOR_URL="https://telemetry.host/ping/YOUR_MONITOR_ID"

# Sync to S3
OUTPUT=$(aws s3 sync "$SOURCE_DIR" "$S3_BUCKET" --delete 2>&1)
EXIT_CODE=$?

if [ $EXIT_CODE -eq 0 ]; then
    # Parse S3 output
    UPLOADED=$(echo "$OUTPUT" | grep "upload:" | wc -l)
    DELETED=$(echo "$OUTPUT" | grep "delete:" | wc -l)
    
    curl -X POST "$MONITOR_URL" \
        -d "{\"status\":\"success\",\"message\":\"S3 sync: $UPLOADED uploaded, $DELETED deleted\"}"
else
    echo "$OUTPUT" | curl -X POST "$MONITOR_URL" \
        -H "Content-Type: text/plain" \
        --data-binary @-
    exit 1
fi

Docker Volume Backup

#!/bin/bash
# docker-volume-backup.sh

VOLUME_NAME="postgres_data"
BACKUP_DIR="/backups/volumes"
MONITOR_URL="https://telemetry.host/ping/YOUR_MONITOR_ID"

# Create backup directory
mkdir -p "$BACKUP_DIR"

# Backup volume
BACKUP_FILE="$BACKUP_DIR/${VOLUME_NAME}_$(date +%Y%m%d).tar.gz"

docker run --rm \
    -v "$VOLUME_NAME":/source:ro \
    -v "$BACKUP_DIR":/backup \
    alpine \
    tar czf "/backup/$(basename $BACKUP_FILE)" -C /source .

if [ $? -eq 0 ]; then
    SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
    curl -X POST "$MONITOR_URL" \
        -d "{\"status\":\"success\",\"message\":\"Volume backup: $SIZE\"}"
else
    curl -X POST "$MONITOR_URL" \
        -d '{"status":"error","message":"Volume backup failed"}'
    exit 1
fi

Comprehensive Backup Script

Full-featured backup with retry logic and notifications:

#!/bin/bash
# comprehensive-backup.sh

set -euo pipefail

# Configuration
DB_NAME="production"
BACKUP_DIR="/backups"
MONITOR_URL="https://telemetry.host/ping/YOUR_MONITOR_ID"
MAX_RETRIES=3
RETRY_DELAY=60

# Function to send check-in
send_checkin() {
    local status=$1
    local message=$2
    local metadata=${3:-"{}"}
    
    curl -s -X POST "$MONITOR_URL" \
        -H "Content-Type: application/json" \
        -d "{
            \"status\": \"$status\",
            \"message\": \"$message\",
            \"metadata\": $metadata
        }"
}

# Function to perform backup with retry
backup_with_retry() {
    local attempt=1
    
    while [ $attempt -le $MAX_RETRIES ]; do
        echo "Backup attempt $attempt of $MAX_RETRIES..."
        
        if pg_dump "$DB_NAME" | gzip > "$BACKUP_FILE"; then
            return 0
        fi
        
        echo "Attempt $attempt failed"
        attempt=$((attempt + 1))
        
        if [ $attempt -le $MAX_RETRIES ]; then
            echo "Retrying in ${RETRY_DELAY}s..."
            sleep $RETRY_DELAY
        fi
    done
    
    return 1
}

# Main backup process
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/${DB_NAME}_${TIMESTAMP}.sql.gz"
START_TIME=$(date +%s)

echo "Starting backup process..."

if backup_with_retry; then
    END_TIME=$(date +%s)
    DURATION=$((END_TIME - START_TIME))
    SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
    
    # Verify backup
    if gunzip -t "$BACKUP_FILE" 2>/dev/null; then
        send_checkin "success" "Backup completed and verified" \
            "{\"size\":\"$SIZE\",\"duration\":$DURATION,\"file\":\"$BACKUP_FILE\"}"
        echo "Backup successful: $SIZE in ${DURATION}s"
    else
        send_checkin "error" "Backup verification failed"
        echo "Backup verification failed!" >&2
        exit 1
    fi
else
    send_checkin "error" "Backup failed after $MAX_RETRIES attempts"
    echo "Backup failed after $MAX_RETRIES attempts!" >&2
    exit 1
fi

Monitoring Backup Age

Check that backups aren’t stale:

#!/bin/bash
# check-backup-age.sh

BACKUP_DIR="/backups"
MAX_AGE_HOURS=25
MONITOR_URL="https://telemetry.host/ping/YOUR_MONITOR_ID"

# Find most recent backup
LATEST_BACKUP=$(find "$BACKUP_DIR" -name "*.sql.gz" -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" ")

if [ -z "$LATEST_BACKUP" ]; then
    curl -X POST "$MONITOR_URL" \
        -d '{"status":"error","message":"No backups found"}'
    exit 1
fi

# Check age
BACKUP_AGE_SECONDS=$(( $(date +%s) - $(stat -c %Y "$LATEST_BACKUP") ))
BACKUP_AGE_HOURS=$(( BACKUP_AGE_SECONDS / 3600 ))

if [ $BACKUP_AGE_HOURS -gt $MAX_AGE_HOURS ]; then
    curl -X POST "$MONITOR_URL" \
        -d "{\"status\":\"error\",\"message\":\"Latest backup is ${BACKUP_AGE_HOURS}h old\"}"
    exit 1
else
    curl -X POST "$MONITOR_URL" \
        -d "{\"status\":\"success\",\"message\":\"Latest backup is ${BACKUP_AGE_HOURS}h old\"}"
fi

Next Steps