Linux: مشروع المرحلة 4 المصغّر — سكريبت نسخ احتياطي آلي

هذا المشروع الختامي للمرحلة 4. ادمج إدارة العمليات، وإدارة الحزم، والأرشفة/الضغط، وكل مهارات سكريبت Bash لبناء أداة نسخ احتياطي آلي قابلة للاستخدام.

📋 المتطلبات المسبقة: يجب إكمال

1. ما ستتعلمه


2. قرص خادم شياو مينغ تعطّل

(1) نقطة الألم: قرص تعطّل، بيانات ضاعت

قرص خادم شياو مينغ تعطّل فجأة، خاسرًا 3 أيام من البيانات. أدرك أنه لم يكن لديه نسخ احتياطية — والنسخ اليدوي ممل جدًا، كان ينسى دائمًا.

(2) حل نسخ احتياطي آلي

استخدم مهارات المرحلة 4 لكتابة أداة نسخ احتياطي كاملة:

BASH
# نسخ احتياطي محلي
./backup.sh -s /var/www -d /backup

# نسخ احتياطي بعيد
./backup.sh -s /var/www -d user@backup:/backups

# إضافة إلى cron للتنفيذ اليومي
0 2 * * * /opt/scripts/backup.sh -s /var/www -d /backup

(3) الفائدة: لا مزيد من الخوف من فقدان البيانات

سكريبت النسخ الاحتياطي يعمل تلقائيًا كل يوم الساعة 2 صباحًا، مزامنًا تزايديًا إلى موقع بعيد. أخيرًا يستطيع شياو مينغ النوم بسلام.


3. نقاط المعرفة

(1) تصميم بنية سكريبت النسخ الاحتياطي

سكريبت نسخ احتياطي جيد يجب أن يكون نمطيًا:

TEXT

backup.sh
├── تحميل الإعدادات
├── فحوصات (دليل/اعتماديات/مساحة)
├── ضغط (المصدر → tar.gz)
├── مزامنة (rsync محلي أو بعيد)
├── تنظيف (حذف نسخ منتهية)
├── تسجيل
└── إشعارات (نجاح/فشل)

(2) استراتيجية النسخ الاحتياطي التزايدي

TEXT
# الطريقة 1: rsync --link-dest (موصى بها)
# تستخدم روابط صلبة؛ كل يوم يبدو كدليل كامل لكنه يخزن التغييرات فقط
rsync -av --link-dest=/backup/yesterday/ /data/ /backup/today/

> 💡 نصيحة: مبدأ النسخ الاحتياطي التزايدي لـ`rsync --link-dest` يستفيد من الروابط الصلبة — الملفات غير المتغيرة في نسخة "اليوم" هي مجرد روابط صلبة تشير إلى ملفات "أمس"، لا تستهلك مساحة قرص إضافية؛ فقط الملفات المتغيرة تُكتب فعليًا كبيانات جديدة. ظاهريًا، كل يوم يبدو كدليل كامل، لكن استخدام القرص قريب من التزايدي.

# الطريقة 2: النقل التزايدي بrsync
# rsync ينقل الأجزاء المتغيرة فقط افتراضيًا
rsync -avz --delete /data/ user@host:/backup/

> ⚠️ تحذير: `rsync --delete` يجعل الوجهة مطابقة تمامًا للمصدر — الملفات في الوجهة التي لا وجود لها في المصدر ستُحذف. إذا أخطأت في مسار المصدر (مثل نسيان `/` اللاحقة)، قد تُفرغ الوجهة. استخدم دائمًا `--dry-run` أولًا للمعاينة.

# الطريقة 3: أرشيفات بأسماء تواريخ
backup-2026-07-07.tar.gz
backup-2026-07-08.tar.gz  # ملفان مستقلان، لكن أكثر استهلاكًا للمساحة

ℹ️ ملاحظة: استراتيجيات النسخ الاحتياطي الاحترافية تتبع "قاعدة 3-2-1" — 3 نسخ من البيانات، على وسيطين تخزين مختلفين، ونسخة واحدة خارج الموقع. النسخ الاحتياطي المحلي + rsync بعيد يشكلان أساس استراتيجية 3-2-1. الاعتماد فقط على tar.gz محلي لا يكفي — تعطل القرص سيُدمر البيانات الأصلية والنسخة الاحتياطية معًا.

(3) جدولة cron

TEXT
# صياغة cron: دقيقة ساعة يوم شهر يوم_أسبوع أمر
# تشغيل النسخ الاحتياطي يوميًا الساعة 2:00 صباحًا
0 2 * * * /opt/scripts/backup.sh > /dev/null 2>&1

# تشغيل كل ساعة
0 * * * * /opt/scripts/backup-hourly.sh

# كل اثنين الساعة 3:00 صباحًا
0 3 * * 1 /opt/scripts/backup-weekly.sh

(4) ▶ مثال: فحص مساحة النسخ الاحتياطي

BASH
#!/bin/bash
# فحص ما إذا كان دليل النسخ الاحتياطي يملك مساحة كافية

check_space() {
    local dest="$1"
    local min_gb="${2:-1}"
    
    local available=$(df -BG "$dest" | tail -1 | awk '{print $4}' | tr -d 'G')
    
    if [ "$available" -lt "$min_gb" ]; then
        echo "Error: Insufficient disk space (available: ${available}G, needed: ${min_gb}G)"
        return 1
    fi
    
    echo "Disk space sufficient: ${available}G"
    return 0
}

الإخراج:

TEXT
Disk space sufficient: 50G

(5) ▶ مثال: دالة التسجيل

TEXT
#!/bin/bash

LOG_FILE="/var/log/backup.log"

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

log "INFO" "Backup started"
log "ERROR" "Backup failed: insufficient disk space"
log "INFO" "Backup complete"

(6) ▶ مثال: آلية القفل لمنع التنفيذ المزدوج

TEXT
#!/bin/bash

LOCK_FILE="/tmp/backup.lock"

# محاولة الحصول على القفل
exec 200>"$LOCK_FILE"
flock -n 200 || {
    echo "Backup script is already running (lock file: $LOCK_FILE)"
    exit 1
}

# ... منطق النسخ الاحتياطي ...

# يُحرَّر القفل تلقائيًا عند خروج السكريبت

(7) ▶ مثال: تنظيف النسخ الاحتياطية المنتهية

TEXT
#!/bin/bash

cleanup_old_backups() {
    local backup_dir="$1"
    local retention_days="${2:-30}"
    
    log "INFO" "Cleaning backups older than $retention_days days..."
    
    local count=0
    while IFS= read -r -d '' file; do
        rm -f "$file"
        count=$((count + 1))
    done < <(find "$backup_dir" -name "*.tar.gz" -type f -mtime +$retention_days -print0)
    
    if [ $count -gt 0 ]; then
        log "INFO" "Cleaned up $count expired files"
    fi
}

(8) ▶ مثال شامل: سكريبت أداة نسخ احتياطي كامل

BASH
#!/bin/bash
# backup.sh - أداة نسخ احتياطي آلي
# الاستخدام: ./backup.sh -s <source_dir> -d <dest_dir> [-r <retention_days>] [-e <exclude_pattern>] [-v]

set -euo pipefail

# ====== الإعدادات ======
VERSION="1.0.0"
LOG_FILE="/var/log/backup.log"
LOCK_FILE="/tmp/backup.lock"
DEFAULT_RETENTION=30

# ====== الألوان ======
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

# ====== الدوال ======

usage() {
    cat << EOF
Backup Tool v${VERSION}

Usage: $0 -s <source_dir> -d <dest_dir> [options]

Required:
  -s <source_dir>      Source directory to back up
  -d <dest_dir>        Backup destination (local path or user@host:/path)

Options:
  -r <days>            Retention days (default: $DEFAULT_RETENTION)
  -e <pattern>         Exclude pattern (can be used multiple times, e.g., -e node_modules -e .git)
  -v                   Verbose output
  -t                   Test mode (no actual backup created)
  -h                   Show help
EOF
    exit 0
}

log() {
    local level="$1"
    local message="$2"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo -e "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}

info()    { log "INFO" "$1"; }
success() { log "INFO" "${GREEN}$1${NC}"; }
warn()    { log "WARN" "${YELLOW}$1${NC}"; }
error()   { log "ERROR" "${RED}$1${NC}"; }

check_deps() {
    for cmd in rsync tar; do
        if ! command -v "$cmd" &> /dev/null; then
            error "Required command '$cmd' not found"
            exit 1
        fi
    done
}

check_disk_space() {
    local dir="$1"
    local min_gb="${2:-1}"
    
    if [[ "$dir" != *:* ]]; then
        local avail=$(df -BG "$dir" 2>/dev/null | tail -1 | awk '{print $4}' | tr -d 'G')
        if [ "$avail" -lt "$min_gb" ]; then
            error "Insufficient disk space: ${avail}G available, ${min_gb}G needed"
            return 1
        fi
        info "Disk space: ${avail}G available"
    fi
}

cleanup_old() {
    local dir="$1"
    local days="$2"
    
    if [[ "$dir" != *:* ]]; then
        local count=$(find "$dir" -name "*.tar.gz" -type f -mtime +$days -delete -print | wc -l)
        if [ $count -gt 0 ]; then
            info "Cleaned up $count expired backups"
        fi
    fi
}

# ====== آلية القفل ======
exec 200>"$LOCK_FILE"
flock -n 200 || {
    error "Backup script is already running (PID: $(cat $LOCK_FILE 2>/dev/null))"
    exit 1
}
echo $$ > "$LOCK_FILE"

# ====== معالجة الإشارات ======
cleanup() {
    rm -f "$LOCK_FILE"
    info "Backup script exiting"
}
trap cleanup EXIT INT TERM

# ====== تحليل المعاملات ======
SOURCE=""
DEST=""
RETENTION=$DEFAULT_RETENTION
EXCLUDE=()
VERBOSE=false
DRY_RUN=false

while getopts "s:d:r:e:vth" opt; do
    case $opt in
        s) SOURCE="$OPTARG" ;;
        d) DEST="$OPTARG" ;;
        r) RETENTION="$OPTARG" ;;
        e) EXCLUDE+=("$OPTARG") ;;
        v) VERBOSE=true ;;
        t) DRY_RUN=true ;;
        h) usage ;;
        *) usage ;;
    esac
done

# ====== التحقق ======
if [ -z "$SOURCE" ] || [ -z "$DEST" ]; then
    error "Must specify -s (source directory) and -d (destination)"
    usage
fi

if [ ! -d "$SOURCE" ]; then
    error "Source directory '$SOURCE' does not exist"
    exit 1
fi

# ====== المنطق الرئيسي ======
info "========================================"
info "Backup Tool v${VERSION}"
info "Source: $SOURCE"
info "Destination: $DEST"
info "Retention: $RETENTION days"
if [ "$DRY_RUN" = true ]; then
    warn "Test mode - no actual backup will be created"
fi
info "========================================"

# 1. فحص الاعتماديات والمساحة
check_deps
check_disk_space "$DEST"

# 2. توليد اسم الملف
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="backup_$(basename "$SOURCE")_${TIMESTAMP}.tar.gz"

# 3. بناء معاملات الاستثناء
EXCLUDE_ARGS=""
for pattern in "${EXCLUDE[@]}"; do
    EXCLUDE_ARGS="$EXCLUDE_ARGS --exclude=$pattern"
done

# 4. إنشاء نسخة احتياطية مضغوطة
if [ "$DRY_RUN" = false ]; then
    info "Creating compressed backup: $BACKUP_NAME"
    
    # نسخ احتياطي محلي
    if [[ "$DEST" != *:* ]]; then
        mkdir -p "$DEST"
        eval tar -czf "${DEST}/${BACKUP_NAME}" $EXCLUDE_ARGS "$SOURCE"
        success "✅ Local backup complete: ${DEST}/${BACKUP_NAME}"
        
        # مزامنة إلى بعيد
        if [ "$VERBOSE" = true ]; then
            ls -lh "${DEST}/${BACKUP_NAME}"
        fi
    else
        # نسخ احتياطي بعيد - ضغط محليًا ثم rsync
        local_temp="/tmp/${BACKUP_NAME}"
        eval tar -czf "$local_temp" $EXCLUDE_ARGS "$SOURCE"
        info "Transferring to remote: $DEST"
        rsync -avz --progress "$local_temp" "${DEST}/${BACKUP_NAME}"
        rm -f "$local_temp"
        success "✅ Remote backup complete: ${DEST}/${BACKUP_NAME}"
    fi
else
    warn "Test mode: Skipping backup creation"
fi

# 5. تنظيف النسخ المنتهية
if [ "$DRY_RUN" = false ] && [ $RETENTION -gt 0 ]; then
    cleanup_old "$DEST" $RETENTION
fi

# 6. ملخص النسخ الاحتياطي
info "Backup summary:"
info "  Source: $SOURCE"
info "  Destination: $DEST"
if [ "$DRY_RUN" = false ]; then
    info "  File: ${BACKUP_NAME}"
    if [[ "$DEST" != *:* ]]; then
        info "  Size: $(du -h "${DEST}/${BACKUP_NAME}" | cut -f1)"
    fi
fi
info "========================================"

success "✅ Backup operation complete"
TEXT
# أمثلة استخدام

# 1. استخدام أساسي
./backup.sh -s /var/www/myapp -d /backup

# 2. استثناء أدلة + احتفاظ 7 أيام
./backup.sh -s /var/www/myapp -d /backup -e node_modules -e .git -r 7

# 3. نسخ احتياطي بعيد
./backup.sh -s /var/www -d user@backup-server:/backups -v

# 4. وضع الاختبار (بدون نسخ فعلي)
./backup.sh -s /var/www -d /backup -t

# 5. إضافة إلى cron (يوميًا الساعة 2 صباحًا)
crontab -e
# أضف:
0 2 * * * /opt/scripts/backup.sh -s /var/www/myapp -d /backup -e node_modules -r 30 > /dev/null 2>&1

❓ أسئلة شائعة

س: كيف أمنع سكريبت النسخ الاحتياطي من العمل مرتين في وقت واحد؟ ج: استخدم قفل الملفات (flock): عند بدء السكريبت، يحاول إنشاء ملف قفل. إذا كان القفل موجودًا فعلًا، يخرج. هكذا، حتى لو أطلق cron مرة أخرى قبل انتهاء النسخ الاحتياطي، لن يعمل نسخان متزامنان.

س: كم يجب الاحتفاظ بملفات النسخ الاحتياطي؟ ج: يعتمد على مساحة التخزين ومتطلبات الامتثال. استراتيجية شائعة: نسخ يومية لـ30 يومًا، نسخ أسبوعية لـ3 أشهر، نسخ شهرية لسنة. استخدم --link-dest أو ترددات cron مختلفة للتطبيق.

س: كيف أتحقق من سلامة النسخ الاحتياطي؟ ج: استخدم sha256sum لتوليد ملف بصمة: sha256sum backup.tar.gz > backup.sha256. عند الاستعادة، تحقق بـsha256sum -c backup.sha256.

س: هل يستأنف rsync النقل إذا قُوطع؟ ج: نعم. rsync ينقل تزايديًا افتراضيًا. حتى لو قُوطع نقل ملف جارٍ، تشغيل rsync التالي سينقل الجزء غير المنتهي فقط. أضف --partial للاحتفاظ بالملفات المنقولة جزئيًا.

س: ما الذي يجب تهيئته للنسخ الاحتياطي لخادم بعيد؟ ج: 1) مصادقة مفتاح SSH (ssh-keygen + ssh-copy-id). 2) دليل الوجهة البعيد موجود. 3) المستخدم البعيد يملك صلاحيات كتابة. 4) اختياري: تهيئة دخول SSH بدون كلمة مرور.


📖 ملخص


📝 تمارين

  1. أساسي (⭐): أنشئ دليل اختبار وملفات، واستخدم backup.sh للنسخ الاحتياطي إلى /tmp/test-backup/، ثم أضف -e لاستثناء ملفات *.log وتأكد أن الاستثناء عمل
  2. متوسط (⭐⭐): هيّئ cron لتشغيل نسخ احتياطي كل ساعة (راقب 3+ تشغيلات)، وغيّر النسخ الاحتياطي إلى rsync بعيد (إذا كان لديك عدة آلات متاحة)
  3. متقدم (⭐⭐⭐): أضف إشعارات بريد إلكتروني أو نظام (wall أو mail أو webhook) إلى السكريبت، ونفّذ تحقق سلامة النسخ الاحتياطي (sha256sum)، وتنبيه تلقائي عند فشل النسخ
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%