12.2. Maintenance & Backup#
12.2.1. Common Pitfalls#
1. Backup files getting too large
# Bad: No size limits, fills disk
tar -czf full_backup.tar.gz /
# Disk fills up, backup fails
# Better: Check space before backing up
available=$(df /backups | awk 'NR==2 {print $4}')
needed=$(($(du -s /home | awk '{print $1}') * 2))
[[ $available -gt $needed ]] || {
echo "Insufficient space for backup" >&2
exit 1
}
2. Not testing backup restoration
# Bad: Never verify backups work
tar -czf backup.tar.gz /data
# Better: Periodically test restore
tar -tzf backup.tar.gz > /dev/null # At minimum
# Even better: Actually restore to test system
3. Backup strategy mismatch
# Bad: Backing up too frequently (disk waste) or too infrequently (data loss)
# Weekly backup with growing data = slow, expensive
# Better: Tiered approach
# - Daily incremental (fast, cheap)
# - Weekly full (can restore completely)
# - Monthly archival (compliance/long-term)
4. Storing backups in same location as data
# Bad: Both in /home (if disk fails, backup is gone)
tar -czf /home/backup.tar.gz /home
# Better: Different disk/location
tar -czf /backups/backup.tar.gz /home
# Better: Off-site backup
rsync -az /backups/ remote:/backups/
5. Not logging backup results
# Bad: No record of backup success
0 2 * * * /backup.sh
# Better: Log results
0 2 * * * /backup.sh >> /var/log/backup.log 2>&1
# Even better: Email on failure
0 2 * * * /backup.sh >> /var/log/backup.log 2>&1 || \
mail -s "Backup Failed" admin@example.com
12.2.2. Real-World Example: Complete Maintenance Suite#
#!/bin/bash
set -euo pipefail
# COMPREHENSIVE MAINTENANCE SCRIPT FOR SERVERS
# Run daily via cron: 0 2 * * * /usr/local/bin/maintenance.sh
readonly LOG_FILE="/var/log/maintenance.log"
readonly BACKUP_DIR="/backups"
readonly RETENTION_DAYS=30
readonly REPORT_EMAIL="admin@example.com"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
# 1. Backup system
backup_system() {
log "Starting system backup..."
local backup_file="$BACKUP_DIR/system_$(date +%Y%m%d).tar.gz"
mkdir -p "$BACKUP_DIR"
tar -czf "$backup_file" /etc /home /opt 2>/dev/null || {
log "ERROR: Backup failed"
return 1
}
# Verify
tar -tzf "$backup_file" > /dev/null || {
log "ERROR: Backup corrupted"
rm -f "$backup_file"
return 1
}
log "Backup completed: $(du -h $backup_file | cut -f1)"
}
# 2. Cleanup old backups
cleanup_backups() {
log "Cleaning up backups older than $RETENTION_DAYS days..."
find "$BACKUP_DIR" -name "system_*.tar.gz" -mtime +$RETENTION_DAYS -delete
}
# 3. Rotate logs
rotate_logs() {
log "Rotating system logs..."
find /var/log -name "*.log" -mtime +7 ! -name "*.gz" | \
while read -r f; do gzip "$f"; done
}
# 4. Database maintenance
maintain_db() {
if command -v mysqld &>/dev/null; then
log "Optimizing MySQL databases..."
mysql -u root -e "SELECT @@version" &>/dev/null && {
mysqlcheck -u root --all-databases --auto-repair
log "Database maintenance completed"
}
fi
}
# 5. Disk usage report
report_usage() {
log "Disk usage summary:"
df -h | grep -E "^/dev" | awk '{print $6 " " $5}'
}
# Run all tasks
{
log "=== Daily Maintenance Started ==="
backup_system
cleanup_backups
rotate_logs
maintain_db
report_usage
log "=== Daily Maintenance Completed ==="
} | tee -a "$LOG_FILE"
# Email report on errors
grep ERROR "$LOG_FILE" | tail -1 | \
mail -s "Maintenance Report" "$REPORT_EMAIL" 2>/dev/null || true
12.2.3. System Maintenance Tasks#
12.2.3.1. Log Rotation and Cleanup#
#!/bin/bash
cleanup_logs() {
local log_dir=$1
local retention_days=${2:-30}
echo "Cleaning logs in $log_dir older than $retention_days days..."
# Delete old logs
find "$log_dir" -name "*.log" -mtime +$retention_days -delete
# Compress recent logs
find "$log_dir" -name "*.log" -mtime +7 ! -name "*.gz" | \
while read -r logfile; do
gzip "$logfile"
done
# Report disk space saved
du -sh "$log_dir"
}
# Cron entry
# @daily /usr/local/bin/cleanup_logs.sh /var/log 30
12.2.3.2. Database Maintenance#
#!/bin/bash
maintain_database() {
local db_name=$1
local db_user="root"
echo "Optimizing database: $db_name"
# Check and optimize all tables
mysql -u "$db_user" -e "
USE $db_name;
SELECT CONCAT('CHECK TABLE ', table_name, ';')
FROM information_schema.tables
WHERE table_schema = '$db_name'
INTO OUTFILE '/tmp/check.sql';
"
mysql -u "$db_user" < /tmp/check.sql
# Repair any corrupted tables
mysqlcheck -u "$db_user" --auto-repair "$db_name"
# Optimize tables
mysql -u "$db_user" -e "
SELECT CONCAT('OPTIMIZE TABLE ', table_name, ';')
FROM information_schema.tables
WHERE table_schema = '$db_name'
INTO OUTFILE '/tmp/optimize.sql';
"
mysql -u "$db_user" < /tmp/optimize.sql
rm -f /tmp/check.sql /tmp/optimize.sql
}
12.2.4. Retention and Cleanup#
12.2.4.1. Implementing Retention Policies#
#!/bin/bash
cleanup_old_backups() {
local backup_dir=$1
local retention_days=${2:-30}
local max_backups=${3:-10}
echo "Cleaning up backups older than $retention_days days..."
# Delete by age
find "$backup_dir" -name "backup_*.tar.gz" -mtime +$retention_days -delete
# Delete excess backups (keep only most recent N)
local count=$(find "$backup_dir" -name "backup_*.tar.gz" | wc -l)
if [[ $count -gt $max_backups ]]; then
# Delete oldest backups
find "$backup_dir" -name "backup_*.tar.gz" -type f | \
sort | head -n $((count - max_backups)) | xargs rm -f
fi
# Report remaining backups
ls -lh "$backup_dir"/backup_*.tar.gz 2>/dev/null | wc -l | xargs echo "Backups remaining:"
}
cleanup_old_backups /backups 30 10
12.2.4.2. Backup with Rotation#
# Keep only last 7 daily backups, 4 weekly, 12 monthly
backup_with_rotation() {
local backup_dir=$1
mkdir -p "$backup_dir"/{daily,weekly,monthly}
local today=$(date +%Y%m%d)
local daily_backup="$backup_dir/daily/backup_$today.tar.gz"
# Create daily backup
tar -czf "$daily_backup" /home /etc || return 1
# Keep only last 7 daily backups
find "$backup_dir/daily" -name "backup_*.tar.gz" -mtime +7 -delete
# Weekly backup on Sunday
if [[ $(date +%u) -eq 7 ]]; then
local weekly_backup="$backup_dir/weekly/backup_week_$(date +%Y_W%V).tar.gz"
cp "$daily_backup" "$weekly_backup"
find "$backup_dir/weekly" -mtime +30 -delete # Keep 4+ weeks
fi
# Monthly backup on 1st of month
if [[ $(date +%d) -eq 1 ]]; then
local monthly_backup="$backup_dir/monthly/backup_$(date +%Y_%m).tar.gz"
cp "$daily_backup" "$monthly_backup"
find "$backup_dir/monthly" -mtime +365 -delete # Keep 12+ months
fi
}
12.2.5. Building Reliable Backup Scripts#
12.2.5.1. Backup Strategy and Types#
# Full backup: Copy everything
backup_full() {
local timestamp=$(date +%Y%m%d_%H%M%S)
tar -czf "full_backup_$timestamp.tar.gz" /home /etc /opt
}
# Incremental backup: Only files changed since last backup
backup_incremental() {
local timestamp=$(date +%Y%m%d_%H%M%S)
local level_file="/var/log/backup_level"
# Create marker for next incremental
tar -czf "incremental_$timestamp.tar.gz" \
--newer="$level_file" /home /etc
touch "$level_file"
}
# Differential backup: Changed since last full backup
backup_differential() {
local timestamp=$(date +%Y%m%d_%H%M%S)
local last_full="/var/log/last_full_backup"
tar -czf "differential_$timestamp.tar.gz" \
--newer="$last_full" /home /etc
}
12.2.5.2. Backup with Verification#
#!/bin/bash
set -euo pipefail
backup_with_verify() {
local source=$1
local dest=$2
local backup_file="$dest/backup_$(date +%Y%m%d_%H%M%S).tar.gz"
# Create backup
echo "Creating backup..."
tar -czf "$backup_file" "$source" || {
echo "Backup failed" >&2
return 1
}
# Verify integrity
echo "Verifying backup..."
tar -tzf "$backup_file" > /dev/null || {
echo "Backup verification failed" >&2
rm -f "$backup_file"
return 1
}
# Check size (ensure not empty)
local size=$(stat -c%s "$backup_file" 2>/dev/null || echo 0)
[[ $size -gt 1000 ]] || {
echo "Backup file suspiciously small: $size bytes" >&2
return 1
}
echo "Backup verified: $backup_file ($size bytes)"
return 0
}