← back to Watches
scripts/backup-system.sh
442 lines
#!/bin/bash
###############################################################################
# Omega Watch Price History - Automated Backup System
#
# Features:
# - Daily automated backups
# - Incremental and full backups
# - Off-site backup support (S3-compatible)
# - Backup rotation and cleanup
# - Backup verification
# - Point-in-time recovery
###############################################################################
set -euo pipefail
# Configuration
PROJECT_DIR="/root/Projects/watches"
BACKUP_DIR="${PROJECT_DIR}/backups"
DATA_DIR="${PROJECT_DIR}/data"
LOGS_DIR="${PROJECT_DIR}/logs"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
DATE=$(date +%Y-%m-%d)
RETENTION_DAYS=${BACKUP_RETENTION_DAYS:-30}
# S3 Configuration (optional)
S3_BUCKET="${S3_BACKUP_BUCKET:-}"
S3_REGION="${AWS_REGION:-us-east-1}"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Logging
log() {
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1"
}
error() {
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR:${NC} $1" >&2
}
warning() {
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING:${NC} $1"
}
# Create backup directories
create_backup_dirs() {
mkdir -p "${BACKUP_DIR}"/{daily,weekly,monthly,offsite}
mkdir -p "${BACKUP_DIR}/restore"
log "Backup directories created"
}
# Backup database (JSON files)
backup_database() {
local backup_file="${BACKUP_DIR}/daily/database_${TIMESTAMP}.tar.gz"
log "Backing up database..."
tar -czf "${backup_file}" \
-C "${DATA_DIR}" \
--exclude='backups' \
. || { error "Database backup failed"; return 1; }
# Calculate checksum
sha256sum "${backup_file}" > "${backup_file}.sha256"
local size=$(du -h "${backup_file}" | cut -f1)
log "Database backup completed: $(basename ${backup_file}) (${size})"
echo "${backup_file}"
}
# Backup logs
backup_logs() {
local backup_file="${BACKUP_DIR}/daily/logs_${TIMESTAMP}.tar.gz"
if [ -d "${LOGS_DIR}" ]; then
log "Backing up logs..."
tar -czf "${backup_file}" \
-C "${LOGS_DIR}" \
--exclude='*.tmp' \
. 2>/dev/null || warning "No logs to backup"
if [ -f "${backup_file}" ]; then
sha256sum "${backup_file}" > "${backup_file}.sha256"
local size=$(du -h "${backup_file}" | cut -f1)
log "Logs backup completed: ${backup_file} (${size})"
fi
fi
}
# Backup configuration files
backup_config() {
local backup_file="${BACKUP_DIR}/daily/config_${TIMESTAMP}.tar.gz"
log "Backing up configuration..."
tar -czf "${backup_file}" \
-C "${PROJECT_DIR}" \
package.json \
package-lock.json \
server.js \
.env 2>/dev/null \
ecosystem.config.js 2>/dev/null \
|| true
if [ -f "${backup_file}" ]; then
sha256sum "${backup_file}" > "${backup_file}.sha256"
log "Configuration backup completed"
fi
}
# Create full backup
full_backup() {
log "=========================================="
log "Starting FULL backup: ${TIMESTAMP}"
log "=========================================="
create_backup_dirs
local backup_manifest="${BACKUP_DIR}/daily/backup_${TIMESTAMP}_manifest.txt"
{
echo "Omega Watch Price History - Backup Manifest"
echo "============================================"
echo "Timestamp: ${TIMESTAMP}"
echo "Date: ${DATE}"
echo "Backup Type: FULL"
echo ""
echo "Components:"
} > "${backup_manifest}"
# Backup database
local db_backup=$(backup_database)
echo "- Database: $(basename ${db_backup})" >> "${backup_manifest}"
# Backup logs
backup_logs
echo "- Logs: logs_${TIMESTAMP}.tar.gz" >> "${backup_manifest}"
# Backup configuration
backup_config
echo "- Configuration: config_${TIMESTAMP}.tar.gz" >> "${backup_manifest}"
# Create combined backup archive
local full_backup_file="${BACKUP_DIR}/daily/full_backup_${TIMESTAMP}.tar.gz"
tar -czf "${full_backup_file}" \
-C "${BACKUP_DIR}/daily" \
"database_${TIMESTAMP}.tar.gz" \
"logs_${TIMESTAMP}.tar.gz" 2>/dev/null \
"config_${TIMESTAMP}.tar.gz" 2>/dev/null \
"backup_${TIMESTAMP}_manifest.txt" \
|| true
sha256sum "${full_backup_file}" > "${full_backup_file}.sha256"
local size=$(du -h "${full_backup_file}" | cut -f1)
log "Full backup archive created: ${size}"
# Upload to S3 if configured
if [ -n "${S3_BUCKET}" ]; then
upload_to_s3 "${full_backup_file}"
fi
log "=========================================="
log "Backup completed successfully!"
log "=========================================="
}
# Upload backup to S3
upload_to_s3() {
local backup_file="$1"
if ! command -v aws &> /dev/null; then
warning "AWS CLI not installed, skipping S3 upload"
return
fi
log "Uploading backup to S3..."
aws s3 cp "${backup_file}" \
"s3://${S3_BUCKET}/omega-watches/backups/$(basename ${backup_file})" \
--region "${S3_REGION}" \
&& log "✓ Uploaded to S3: s3://${S3_BUCKET}/omega-watches/backups/" \
|| error "S3 upload failed"
# Upload checksum
aws s3 cp "${backup_file}.sha256" \
"s3://${S3_BUCKET}/omega-watches/backups/$(basename ${backup_file}).sha256" \
--region "${S3_REGION}" 2>/dev/null || true
}
# Verify backup integrity
verify_backup() {
local backup_file="$1"
log "Verifying backup integrity: $(basename ${backup_file})"
if [ ! -f "${backup_file}" ]; then
error "Backup file not found: ${backup_file}"
return 1
fi
# Check if tar file is valid
if ! tar -tzf "${backup_file}" > /dev/null 2>&1; then
error "Backup file is corrupted!"
return 1
fi
# Verify checksum if available
if [ -f "${backup_file}.sha256" ]; then
if sha256sum -c "${backup_file}.sha256" > /dev/null 2>&1; then
log "✓ Checksum verification passed"
else
error "Checksum verification failed!"
return 1
fi
fi
log "✓ Backup verification passed"
return 0
}
# Rotate old backups
rotate_backups() {
log "Rotating old backups (retention: ${RETENTION_DAYS} days)..."
# Daily backups - keep for RETENTION_DAYS
find "${BACKUP_DIR}/daily" -name "*.tar.gz" -mtime +${RETENTION_DAYS} -delete 2>/dev/null || true
find "${BACKUP_DIR}/daily" -name "*.sha256" -mtime +${RETENTION_DAYS} -delete 2>/dev/null || true
find "${BACKUP_DIR}/daily" -name "*.txt" -mtime +${RETENTION_DAYS} -delete 2>/dev/null || true
# Weekly backups - keep for 90 days
find "${BACKUP_DIR}/weekly" -name "*.tar.gz" -mtime +90 -delete 2>/dev/null || true
# Monthly backups - keep for 365 days
find "${BACKUP_DIR}/monthly" -name "*.tar.gz" -mtime +365 -delete 2>/dev/null || true
log "Old backups cleaned up"
}
# Create weekly backup (copy from daily)
weekly_backup() {
local latest_daily=$(ls -t "${BACKUP_DIR}/daily"/full_backup_*.tar.gz 2>/dev/null | head -1)
if [ -n "${latest_daily}" ]; then
local week_num=$(date +%U)
local weekly_file="${BACKUP_DIR}/weekly/full_backup_week${week_num}_${TIMESTAMP}.tar.gz"
cp "${latest_daily}" "${weekly_file}"
cp "${latest_daily}.sha256" "${weekly_file}.sha256" 2>/dev/null || true
log "Weekly backup created: $(basename ${weekly_file})"
fi
}
# Create monthly backup (copy from daily)
monthly_backup() {
local latest_daily=$(ls -t "${BACKUP_DIR}/daily"/full_backup_*.tar.gz 2>/dev/null | head -1)
if [ -n "${latest_daily}" ]; then
local month=$(date +%Y-%m)
local monthly_file="${BACKUP_DIR}/monthly/full_backup_${month}.tar.gz"
cp "${latest_daily}" "${monthly_file}"
cp "${latest_daily}.sha256" "${monthly_file}.sha256" 2>/dev/null || true
log "Monthly backup created: $(basename ${monthly_file})"
fi
}
# Restore from backup
restore_backup() {
local backup_file="$1"
local restore_dir="${BACKUP_DIR}/restore/restore_${TIMESTAMP}"
log "=========================================="
log "Starting RESTORE from: $(basename ${backup_file})"
log "=========================================="
# Verify backup first
if ! verify_backup "${backup_file}"; then
error "Backup verification failed, aborting restore"
return 1
fi
# Create restore directory
mkdir -p "${restore_dir}"
# Extract backup
log "Extracting backup..."
tar -xzf "${backup_file}" -C "${restore_dir}" || {
error "Failed to extract backup"
return 1
}
# Stop service before restore
log "Stopping service..."
pm2 stop omega-watches 2>/dev/null || true
# Backup current state before restore (just in case)
local current_backup="${DATA_DIR}_backup_before_restore_${TIMESTAMP}"
mv "${DATA_DIR}" "${current_backup}" 2>/dev/null || true
# Restore database
if [ -f "${restore_dir}/database_"*.tar.gz ]; then
log "Restoring database..."
mkdir -p "${DATA_DIR}"
tar -xzf "${restore_dir}"/database_*.tar.gz -C "${DATA_DIR}"
fi
# Restore configuration if needed
if [ -f "${restore_dir}/config_"*.tar.gz ]; then
log "Configuration backup found (manual restore required)"
fi
# Restart service
log "Restarting service..."
pm2 restart omega-watches || pm2 start "${PROJECT_DIR}/server.js" --name omega-watches
log "=========================================="
log "Restore completed successfully!"
log "Previous state backed up to: ${current_backup}"
log "=========================================="
}
# List available backups
list_backups() {
echo ""
echo "=========================================="
echo "Available Backups"
echo "=========================================="
echo ""
echo "DAILY BACKUPS:"
ls -lh "${BACKUP_DIR}/daily"/full_backup_*.tar.gz 2>/dev/null | awk '{print " " $9 " - " $5}' || echo " None"
echo ""
echo "WEEKLY BACKUPS:"
ls -lh "${BACKUP_DIR}/weekly"/*.tar.gz 2>/dev/null | awk '{print " " $9 " - " $5}' || echo " None"
echo ""
echo "MONTHLY BACKUPS:"
ls -lh "${BACKUP_DIR}/monthly"/*.tar.gz 2>/dev/null | awk '{print " " $9 " - " $5}' || echo " None"
echo ""
}
# Get backup statistics
backup_stats() {
echo ""
echo "=========================================="
echo "Backup Statistics"
echo "=========================================="
echo ""
local total_backups=$(find "${BACKUP_DIR}" -name "*.tar.gz" | wc -l)
local total_size=$(du -sh "${BACKUP_DIR}" 2>/dev/null | cut -f1)
local latest_backup=$(ls -t "${BACKUP_DIR}/daily"/full_backup_*.tar.gz 2>/dev/null | head -1)
echo "Total Backups: ${total_backups}"
echo "Total Size: ${total_size}"
echo ""
if [ -n "${latest_backup}" ]; then
echo "Latest Backup:"
echo " File: $(basename ${latest_backup})"
echo " Size: $(du -h ${latest_backup} | cut -f1)"
echo " Date: $(stat -c %y ${latest_backup} | cut -d. -f1)"
# Verify latest backup
if verify_backup "${latest_backup}" 2>&1 | grep -q "passed"; then
echo " Status: ✓ Valid"
else
echo " Status: ✗ Invalid"
fi
fi
echo ""
}
# Main execution
case "${1:-backup}" in
backup|full)
full_backup
rotate_backups
;;
weekly)
full_backup
weekly_backup
rotate_backups
;;
monthly)
full_backup
monthly_backup
rotate_backups
;;
restore)
if [ -z "${2:-}" ]; then
error "Usage: $0 restore <backup_file>"
list_backups
exit 1
fi
restore_backup "$2"
;;
verify)
if [ -z "${2:-}" ]; then
error "Usage: $0 verify <backup_file>"
exit 1
fi
verify_backup "$2"
;;
list)
list_backups
;;
stats)
backup_stats
;;
rotate)
rotate_backups
;;
*)
echo "Omega Watch Price History - Backup System"
echo ""
echo "Usage: $0 {backup|weekly|monthly|restore|verify|list|stats|rotate}"
echo ""
echo "Commands:"
echo " backup - Create full daily backup"
echo " weekly - Create weekly backup"
echo " monthly - Create monthly backup"
echo " restore - Restore from backup file"
echo " verify - Verify backup integrity"
echo " list - List all available backups"
echo " stats - Show backup statistics"
echo " rotate - Clean up old backups"
echo ""
exit 1
;;
esac