Linux: مشروع المرحلة 4 المصغّر — سكريبت نسخ احتياطي آلي
هذا المشروع الختامي للمرحلة 4. ادمج إدارة العمليات، وإدارة الحزم، والأرشفة/الضغط، وكل مهارات سكريبت Bash لبناء أداة نسخ احتياطي آلي قابلة للاستخدام.
📋 المتطلبات المسبقة: يجب إكمال
- الدرس 20: أساسيات سكريبت Bash
- الدرس 21: سكريبت Bash المتقدم
1. ما ستتعلمه
- تصميم بنية السكريبت (تغليف الدوال)
- استراتيجية النسخ الاحتياطي التزايدي
- مزامنة rsync بعيدة
- التسجيل ومعالجة الأخطاء
- التنفيذ المجدول بcron
2. قرص خادم شياو مينغ تعطّل
(1) نقطة الألم: قرص تعطّل، بيانات ضاعت
قرص خادم شياو مينغ تعطّل فجأة، خاسرًا 3 أيام من البيانات. أدرك أنه لم يكن لديه نسخ احتياطية — والنسخ اليدوي ممل جدًا، كان ينسى دائمًا.
(2) حل نسخ احتياطي آلي
استخدم مهارات المرحلة 4 لكتابة أداة نسخ احتياطي كاملة:
# نسخ احتياطي محلي
./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) تصميم بنية سكريبت النسخ الاحتياطي
سكريبت نسخ احتياطي جيد يجب أن يكون نمطيًا:
backup.sh
├── تحميل الإعدادات
├── فحوصات (دليل/اعتماديات/مساحة)
├── ضغط (المصدر → tar.gz)
├── مزامنة (rsync محلي أو بعيد)
├── تنظيف (حذف نسخ منتهية)
├── تسجيل
└── إشعارات (نجاح/فشل)
(2) استراتيجية النسخ الاحتياطي التزايدي
# الطريقة 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
# صياغة 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) ▶ مثال: فحص مساحة النسخ الاحتياطي
#!/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
}
الإخراج:
TEXTDisk space sufficient: 50G
(5) ▶ مثال: دالة التسجيل
#!/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) ▶ مثال: آلية القفل لمنع التنفيذ المزدوج
#!/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) ▶ مثال: تنظيف النسخ الاحتياطية المنتهية
#!/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) ▶ مثال شامل: سكريبت أداة نسخ احتياطي كامل
#!/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"
# أمثلة استخدام
# 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 بدون كلمة مرور.
📖 ملخص
- بنية سكريبت النسخ الاحتياطي: فحص ← ضغط ← مزامنة ← تنظيف ← تسجيل
flockيمنع التنفيذ المزدوجrsync --deleteيزامن ويحذف الملفات الزائدة في الوجهة- سياسة الاحتفاظ:
find ... -mtime +N -deleteينظف النسخ المنتهية - جدولة cron:
0 2 * * * /path/to/backup.sh
📝 تمارين
- أساسي (⭐): أنشئ دليل اختبار وملفات، واستخدم
backup.shللنسخ الاحتياطي إلى/tmp/test-backup/، ثم أضف-eلاستثناء ملفات*.logوتأكد أن الاستثناء عمل - متوسط (⭐⭐): هيّئ cron لتشغيل نسخ احتياطي كل ساعة (راقب 3+ تشغيلات)، وغيّر النسخ الاحتياطي إلى rsync بعيد (إذا كان لديك عدة آلات متاحة)
- متقدم (⭐⭐⭐): أضف إشعارات بريد إلكتروني أو نظام (
wallأوmailأو webhook) إلى السكريبت، ونفّذ تحقق سلامة النسخ الاحتياطي (sha256sum)، وتنبيه تلقائي عند فشل النسخ