Skip to content

Backup and Recovery

This document provides comprehensive guidance for backup and disaster recovery procedures for the Stratpoint Timesheet Application, ensuring business continuity and data protection across all system components.

Backup Strategy Overview

Backup Architecture

graph TB
    A[Application Data] --> B[Backup Orchestrator]
    C[Database] --> B
    D[File Storage] --> B
    E[Configuration] --> B

    B --> F[Local Backups]
    B --> G[Cloud Storage]
    B --> H[Offsite Storage]

    F --> I[Daily Backups]
    F --> J[Weekly Backups]
    F --> K[Monthly Backups]

    G --> L[AWS S3]
    G --> M[Azure Blob]

    H --> N[Geographic Replication]
    H --> O[Disaster Recovery Site]

Backup Types and Retention

Backup Type Frequency Retention Storage Location
Database Full Daily 30 days local, 1 year cloud Local + S3
Database Incremental Every 6 hours 7 days Local + S3
Application Files Daily 30 days local, 6 months cloud Local + S3
Configuration Weekly 90 days local, 1 year cloud Local + S3
System State Weekly 30 days local, 3 months cloud Local + S3
Archive Monthly 7 years S3 Glacier

Database Backup and Recovery

Automated Database Backup

#!/bin/bash
# scripts/backup/database_backup.sh

set -e

# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../config/backup.conf"

# Logging
LOG_FILE="/var/log/backup/database_$(date +%Y%m%d).log"
mkdir -p "$(dirname "$LOG_FILE")"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# Backup configuration
DB_HOST="${DB_HOST:-localhost}"
DB_NAME="${DB_NAME:-stratpoint_timesheet}"
DB_USER="${DB_BACKUP_USER:-timesheet_backup}"
DB_PASSWORD="${DB_BACKUP_PASSWORD}"
BACKUP_DIR="${BACKUP_DIR:-/backups/database}"
S3_BUCKET="${S3_BACKUP_BUCKET:-stratpoint-timesheet-backups}"

# Create backup directory
mkdir -p "$BACKUP_DIR"

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

log "Starting database backup"

# Pre-backup validation
if ! mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASSWORD" -e "SELECT 1;" > /dev/null 2>&1; then
    log "ERROR: Database connection failed"
    exit 1
fi

# Create full backup with consistency
log "Creating full database backup"
mysqldump \
    --host="$DB_HOST" \
    --user="$DB_USER" \
    --password="$DB_PASSWORD" \
    --single-transaction \
    --routines \
    --triggers \
    --events \
    --hex-blob \
    --quick \
    --lock-tables=false \
    --add-drop-database \
    --flush-logs \
    --master-data=2 \
    --databases "$DB_NAME" > "$BACKUP_FILE" 2>> "$LOG_FILE"

if [ $? -ne 0 ]; then
    log "ERROR: Database backup failed"
    exit 1
fi

# Compress backup
log "Compressing backup file"
gzip "$BACKUP_FILE"

# Verify backup integrity
log "Verifying backup integrity"
if gunzip -t "$COMPRESSED_FILE" 2>> "$LOG_FILE"; then
    log "Backup compression verified successfully"
else
    log "ERROR: Backup compression verification failed"
    exit 1
fi

# Calculate checksums
MD5_CHECKSUM=$(md5sum "$COMPRESSED_FILE" | awk '{print $1}')
SHA256_CHECKSUM=$(sha256sum "$COMPRESSED_FILE" | awk '{print $1}')

# Create metadata file
METADATA_FILE="$BACKUP_DIR/db_full_$TIMESTAMP.metadata"
cat > "$METADATA_FILE" << EOF
{
    "backup_type": "database_full",
    "timestamp": "$TIMESTAMP",
    "database": "$DB_NAME",
    "file_size": $(stat -c%s "$COMPRESSED_FILE"),
    "md5_checksum": "$MD5_CHECKSUM",
    "sha256_checksum": "$SHA256_CHECKSUM",
    "mysql_version": "$(mysql --version)",
    "backup_method": "mysqldump",
    "compression": "gzip"
}
EOF

# Upload to cloud storage
log "Uploading backup to cloud storage"
aws s3 cp "$COMPRESSED_FILE" "s3://$S3_BUCKET/database/daily/" \
    --storage-class STANDARD_IA \
    --metadata "md5=$MD5_CHECKSUM,sha256=$SHA256_CHECKSUM"

aws s3 cp "$METADATA_FILE" "s3://$S3_BUCKET/database/daily/"

# Weekly and monthly backups
if [ $(date +%u) -eq 7 ]; then
    log "Creating weekly backup copy"
    aws s3 cp "$COMPRESSED_FILE" "s3://$S3_BUCKET/database/weekly/"
    aws s3 cp "$METADATA_FILE" "s3://$S3_BUCKET/database/weekly/"
fi

if [ $(date +%d) -eq 01 ]; then
    log "Creating monthly backup copy"
    aws s3 cp "$COMPRESSED_FILE" "s3://$S3_BUCKET/database/monthly/" \
        --storage-class GLACIER
    aws s3 cp "$METADATA_FILE" "s3://$S3_BUCKET/database/monthly/"
fi

# Cleanup old local backups
log "Cleaning up old local backups"
find "$BACKUP_DIR" -name "db_full_*.sql.gz" -mtime +30 -delete
find "$BACKUP_DIR" -name "db_full_*.metadata" -mtime +30 -delete

# Log completion
BACKUP_SIZE=$(du -h "$COMPRESSED_FILE" | cut -f1)
log "Database backup completed successfully"
log "Backup file: $COMPRESSED_FILE"
log "Backup size: $BACKUP_SIZE"
log "MD5: $MD5_CHECKSUM"
log "SHA256: $SHA256_CHECKSUM"

# Send notification
send_notification "Database backup completed" "Size: $BACKUP_SIZE, File: $(basename $COMPRESSED_FILE)"

Database Recovery Procedures

#!/bin/bash
# scripts/recovery/database_recovery.sh

set -e

# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../config/backup.conf"

# Parameters
BACKUP_FILE="$1"
RECOVERY_TYPE="${2:-full}"  # full, point-in-time
RECOVERY_TIME="$3"          # For point-in-time recovery

if [ -z "$BACKUP_FILE" ]; then
    echo "Usage: $0 <backup_file> [recovery_type] [recovery_time]"
    echo "Recovery types: full, point-in-time"
    echo "Recovery time format: 'YYYY-MM-DD HH:MM:SS'"
    exit 1
fi

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

# Pre-recovery validation
log "Starting database recovery process"
log "Backup file: $BACKUP_FILE"
log "Recovery type: $RECOVERY_TYPE"

if [ "$RECOVERY_TYPE" = "point-in-time" ] && [ -z "$RECOVERY_TIME" ]; then
    log "ERROR: Recovery time required for point-in-time recovery"
    exit 1
fi

# Verify backup file
if [ ! -f "$BACKUP_FILE" ]; then
    log "ERROR: Backup file not found: $BACKUP_FILE"
    exit 1
fi

# Verify backup integrity
log "Verifying backup integrity"
if [[ "$BACKUP_FILE" == *.gz ]]; then
    if ! gunzip -t "$BACKUP_FILE"; then
        log "ERROR: Backup file integrity check failed"
        exit 1
    fi
else
    if ! head -n 1 "$BACKUP_FILE" | grep -q "MySQL dump"; then
        log "ERROR: Invalid backup file format"
        exit 1
    fi
fi

# Stop application services
log "Stopping application services"
systemctl stop timesheet-app
systemctl stop timesheet-queue
systemctl stop nginx

# Create recovery database
RECOVERY_DB="${DB_NAME}_recovery_$(date +%Y%m%d_%H%M%S)"
log "Creating recovery database: $RECOVERY_DB"

mysql -u root -p << EOF
CREATE DATABASE $RECOVERY_DB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
EOF

# Restore from backup
log "Restoring database from backup"
if [[ "$BACKUP_FILE" == *.gz ]]; then
    gunzip -c "$BACKUP_FILE" | sed "s/$DB_NAME/$RECOVERY_DB/g" | mysql -u root -p
else
    sed "s/$DB_NAME/$RECOVERY_DB/g" "$BACKUP_FILE" | mysql -u root -p
fi

# Point-in-time recovery
if [ "$RECOVERY_TYPE" = "point-in-time" ]; then
    log "Performing point-in-time recovery to: $RECOVERY_TIME"

    # Find and apply binary logs
    BINLOG_DIR="/var/log/mysql"
    BACKUP_TIME=$(mysql -u root -p -e "SELECT backup_time FROM $RECOVERY_DB.backup_metadata ORDER BY id DESC LIMIT 1;" -s -N)

    for binlog in $(ls -1 "$BINLOG_DIR"/mysql-bin.* | sort); do
        log "Processing binary log: $binlog"

        mysqlbinlog \
            --start-datetime="$BACKUP_TIME" \
            --stop-datetime="$RECOVERY_TIME" \
            --database="$DB_NAME" \
            "$binlog" | sed "s/$DB_NAME/$RECOVERY_DB/g" | mysql -u root -p
    done
fi

# Validate recovered data
log "Validating recovered data"
TABLE_COUNT=$(mysql -u root -p -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$RECOVERY_DB';" -s -N)
log "Recovered tables: $TABLE_COUNT"

# Data consistency checks
mysql -u root -p "$RECOVERY_DB" << EOF
-- Check for orphaned records
SELECT 'Orphaned timelogs' as check_type, COUNT(*) as count
FROM timelogs t
LEFT JOIN users u ON t.user_id = u.id
WHERE u.id IS NULL;

SELECT 'Orphaned project assignments' as check_type, COUNT(*) as count
FROM project_users pu
LEFT JOIN users u ON pu.user_id = u.id
LEFT JOIN projects p ON pu.project_id = p.id
WHERE u.id IS NULL OR p.id IS NULL;
EOF

# Prompt for confirmation
read -p "Recovery validation complete. Replace production database? (yes/no): " confirm
if [ "$confirm" != "yes" ]; then
    log "Recovery cancelled by user"
    mysql -u root -p -e "DROP DATABASE $RECOVERY_DB;"
    exit 1
fi

# Replace production database
log "Replacing production database"
mysql -u root -p << EOF
DROP DATABASE $DB_NAME;
CREATE DATABASE $DB_NAME CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
EOF

mysqldump -u root -p "$RECOVERY_DB" | mysql -u root -p "$DB_NAME"

# Cleanup recovery database
mysql -u root -p -e "DROP DATABASE $RECOVERY_DB;"

# Start application services
log "Starting application services"
systemctl start nginx
systemctl start timesheet-app
systemctl start timesheet-queue

# Verify application
log "Verifying application functionality"
sleep 10
if curl -f http://localhost/health > /dev/null 2>&1; then
    log "Application health check passed"
else
    log "WARNING: Application health check failed"
fi

log "Database recovery completed successfully"
send_notification "Database recovery completed" "Recovery type: $RECOVERY_TYPE"

Application File Backup

Application File Backup Script

#!/bin/bash
# scripts/backup/application_backup.sh

set -e

# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../config/backup.conf"

APP_DIR="${APP_DIR:-/var/www/timesheet}"
BACKUP_DIR="${BACKUP_DIR:-/backups/application}"
S3_BUCKET="${S3_BACKUP_BUCKET:-stratpoint-timesheet-backups}"

# Create backup directory
mkdir -p "$BACKUP_DIR"

# Generate backup filename
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/app_files_$TIMESTAMP.tar.gz"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

log "Starting application file backup"

# Create file list for backup
INCLUDE_PATTERNS=(
    "storage/app/*"
    "storage/logs/*"
    "public/uploads/*"
    ".env*"
    "config/*"
)

EXCLUDE_PATTERNS=(
    "storage/framework/cache/*"
    "storage/framework/sessions/*"
    "storage/framework/views/*"
    "node_modules/*"
    "vendor/*"
    ".git/*"
    "*.log"
)

# Build tar command
TAR_CMD="tar -czf $BACKUP_FILE -C $APP_DIR"

# Add include patterns
for pattern in "${INCLUDE_PATTERNS[@]}"; do
    TAR_CMD="$TAR_CMD --include='$pattern'"
done

# Add exclude patterns
for pattern in "${EXCLUDE_PATTERNS[@]}"; do
    TAR_CMD="$TAR_CMD --exclude='$pattern'"
done

TAR_CMD="$TAR_CMD ."

# Execute backup
log "Creating application file backup"
eval "$TAR_CMD"

# Verify backup
log "Verifying backup integrity"
if tar -tzf "$BACKUP_FILE" > /dev/null 2>&1; then
    log "Backup verification successful"
else
    log "ERROR: Backup verification failed"
    exit 1
fi

# Calculate checksums
MD5_CHECKSUM=$(md5sum "$BACKUP_FILE" | awk '{print $1}')
SHA256_CHECKSUM=$(sha256sum "$BACKUP_FILE" | awk '{print $1}')

# Upload to cloud storage
log "Uploading to cloud storage"
aws s3 cp "$BACKUP_FILE" "s3://$S3_BUCKET/application/daily/" \
    --metadata "md5=$MD5_CHECKSUM,sha256=$SHA256_CHECKSUM"

# Cleanup old backups
find "$BACKUP_DIR" -name "app_files_*.tar.gz" -mtime +30 -delete

BACKUP_SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
log "Application file backup completed: $BACKUP_SIZE"

Disaster Recovery Procedures

Complete System Recovery

#!/bin/bash
# scripts/recovery/disaster_recovery.sh

set -e

# Configuration
RECOVERY_TYPE="$1"  # full, partial
RECOVERY_DATE="$2"  # YYYY-MM-DD

if [ -z "$RECOVERY_TYPE" ]; then
    echo "Usage: $0 <recovery_type> [recovery_date]"
    echo "Recovery types: full, partial"
    exit 1
fi

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

log "Starting disaster recovery: $RECOVERY_TYPE"

# Step 1: Infrastructure preparation
log "Preparing infrastructure"
./scripts/setup/prepare_infrastructure.sh

# Step 2: Database recovery
log "Recovering database"
if [ -n "$RECOVERY_DATE" ]; then
    LATEST_BACKUP=$(aws s3 ls s3://$S3_BUCKET/database/daily/ | grep "$RECOVERY_DATE" | tail -1 | awk '{print $4}')
else
    LATEST_BACKUP=$(aws s3 ls s3://$S3_BUCKET/database/daily/ | tail -1 | awk '{print $4}')
fi

aws s3 cp "s3://$S3_BUCKET/database/daily/$LATEST_BACKUP" /tmp/
./scripts/recovery/database_recovery.sh "/tmp/$LATEST_BACKUP" full

# Step 3: Application recovery
log "Recovering application files"
if [ -n "$RECOVERY_DATE" ]; then
    LATEST_APP_BACKUP=$(aws s3 ls s3://$S3_BUCKET/application/daily/ | grep "$RECOVERY_DATE" | tail -1 | awk '{print $4}')
else
    LATEST_APP_BACKUP=$(aws s3 ls s3://$S3_BUCKET/application/daily/ | tail -1 | awk '{print $4}')
fi

aws s3 cp "s3://$S3_BUCKET/application/daily/$LATEST_APP_BACKUP" /tmp/
tar -xzf "/tmp/$LATEST_APP_BACKUP" -C /var/www/timesheet/

# Step 4: Configuration recovery
log "Recovering configuration"
./scripts/recovery/configuration_recovery.sh

# Step 5: Service startup
log "Starting services"
systemctl start mysql
systemctl start redis
systemctl start nginx
systemctl start timesheet-app
systemctl start timesheet-queue

# Step 6: Verification
log "Verifying system recovery"
./scripts/verification/system_health_check.sh

log "Disaster recovery completed"
send_notification "Disaster recovery completed" "Type: $RECOVERY_TYPE, Date: $RECOVERY_DATE"

Recovery Testing

#!/bin/bash
# scripts/testing/recovery_test.sh

set -e

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

log "Starting recovery test"

# Create test environment
TEST_ENV="recovery_test_$(date +%Y%m%d_%H%M%S)"
mkdir -p "/tmp/$TEST_ENV"

# Test database recovery
log "Testing database recovery"
LATEST_BACKUP=$(aws s3 ls s3://$S3_BUCKET/database/daily/ | tail -1 | awk '{print $4}')
aws s3 cp "s3://$S3_BUCKET/database/daily/$LATEST_BACKUP" "/tmp/$TEST_ENV/"

# Verify backup integrity
gunzip -t "/tmp/$TEST_ENV/$LATEST_BACKUP"

# Test application file recovery
log "Testing application file recovery"
LATEST_APP_BACKUP=$(aws s3 ls s3://$S3_BUCKET/application/daily/ | tail -1 | awk '{print $4}')
aws s3 cp "s3://$S3_BUCKET/application/daily/$LATEST_APP_BACKUP" "/tmp/$TEST_ENV/"

# Verify application backup integrity
tar -tzf "/tmp/$TEST_ENV/$LATEST_APP_BACKUP" > /dev/null

# Cleanup
rm -rf "/tmp/$TEST_ENV"

log "Recovery test completed successfully"

This comprehensive backup and recovery system ensures data protection and business continuity for the Stratpoint Timesheet Application with automated procedures, integrity verification, and disaster recovery capabilities.