دليلك العملي لكتابة سكربتات أتمتة النسخ الاحتياطي وصيانة خوادم لينكس
إدارة الخوادم دون أتمتة شاملة لمهام الصيانة والنسخ الاحتياطي تعني انتظار وقوع كارثة محققة؛ فامتلاء الأقراص المفاجئ، أو تلف قواعد البيانات دون وجود نسخة سليمة ومُختبرة، يهدد استمرارية أي مشروع برمجي. لا تقتصر كتابة سكربتات الأتمتة على رصف أوامر الشل داخل ملف تنفيذي، بل تتطلب معمارية دفاعية تعترض الأخطاء وتتحقق من سلامة البيانات وترسل التنبيهات اللحظية. يستعرض هذا الدليل كيفية صياغة سكربتات صيانة ونسخ احتياطي احترافية جاهزة للعمل في بيئات الإنتاج.
الركائز المعمارية للنسخ الاحتياطي الآمن
أي استراتيجية نسخ احتياطي تفتقر إلى خطة واضحة ومجربة مسبقاً هي مجرد وهم بالأمان. لتجنب فقدان البيانات بالكامل، تعتمد الفرق الهندسية على معايير صارمة تحدد كيفية حفظ الملفات ونقلها:
- قاعدة 3-2-1 الذهبية: تنص هذه القاعدة على الاحتفاظ بثلاث نسخ على الأقل من بياناتك المهمة، على وسيطين تخزينيين مختلفين (مثل أقراص الخادم المحلية ووحدة تخزين شبكية NAS)، مع إبقاء نسخة واحدة على الأقل خارج موقع الخادم تماماً (Off-site)، مثل خدمات التخزين السحابي الكائني المتوافقة مع S3.
- النسخ الكامل (Full Backup): أرشفة جميع الملفات وقواعد البيانات المستهدفة في ملف واحد. يوفر هذا الأسلوب أسرع وقت للاستعادة، لكنه يستهلك مساحة تخزين ونطاق ترددي هائلين إذا نُفّذ يومياً.
- النسخ التزايدي (Incremental Backup): نسخ الملفات التي تغيرت فقط منذ آخر عملية نسخ، سواء كانت كاملة أو تزايدية. يوفر مساحة كبيرة ويقلل استهلاك موارد الخادم، ويُنفّذ بكفاءة عالية عبر أدوات مثل
rsyncباستخدام المعامل--link-destلإنشاء روابط صلبة للملفات غير المعدلة. - تناسق بيانات التخزين (Data Consistency): نسخ مجلدات قواعد البيانات الحية مباشرة أثناء عمل الخادم (مثل محتويات
/var/lib/mysql) يؤدي إلى فساد حتمي في البيانات نتيجة وجود عمليات كتابة غير مكتملة في الذاكرة المؤقتة. يجب استخدام الأدوات المنطقية للتصدير مثلmysqldumpمع معامل--single-transactionلقواعد بيانات InnoDB أوpg_dumpلقواعد بيانات PostgreSQL.
البرمجة الدفاعية في Bash: صياغة سكربتات لا تنهار بصمت
تتجاهل سكربتات Bash الافتراضية الأخطاء وتستمر في تنفيذ الأسطر التالية حتى لو فشلت الأوامر الحيوية السابقة، مما قد يؤدي إلى حذف ملفات خاطئة أو الاعتقاد بنجاح نسخة احتياطية فارغة. لتفادي هذا السلوك الخطير، نطبّق ما يُعرف بنمط البرمجة الصارم (Bash Strict Mode).
يبدأ أي سكربت إنتاجي بتفعيل الأمر set -euo pipefail:
-e: يوقف السكربت فوراً إذا أعاد أي أمر كود خروج غير صفري (Exit Code يدل على خطأ).-u: يمنع استخدام المتغيرات غير المعرفة؛ فإذا استدعيت متغيراً غير موجود بالخطأ في أمر مثلrm -rf "${BACKUP_DIR}/*"، فلن يتحول المسار إلى/*ويمسح نظام التشغيل بالكامل، بل سيتوقف السكربت معلناً الخطأ.-o pipefail: في السلاسل البرمجية (Pipelines)، يعيد Bash عادة كود خروج آخر أمر فقط؛ تضمن هذه الخاصية إيقاف التنفيذ إذا فشل أي أمر يقع ضمن السلسلة، حتى لو نجح الأمر الأخير.
إلى جانب الوضع الصارم، يجب توظيف آلية اعتراض الإشارات trap. تتيح لك هذه الدالة مراقبة أي انهيار غير متوقع للسكربت لتنفيذ عمليات تنظيف فورية للملفات المؤقتة وإرسال إشعار فوري للفريق الهندسي قبل أن يُغلق السكربت عملياته.
بناء سكربت إنتاجي متكامل للنسخ الاحتياطي والتحقق
يقوم هذا السكربت بتصدير قاعدة البيانات دون قفل الجداول، أرشفة ملفات التطبيق، فحص سلامة الأرشيف عبر بصمات التجزئة، تطبيق سياسة تدوير الملفات لحماية سعة القرص، ثم إرسال تنبيه فوري عبر Webhook.
#!/usr/bin/env bash
# ==============================================================================
# Script Name : production_backup.sh
# Purpose : Automated application & database backup with verification
# ==============================================================================
set -euo pipefail
# Configuration parameters
readonly BACKUP_DATE=$(date +"%Y-%m-%d_%H-%M-%S")
readonly DEST_DIR="/var/backups/daily"
readonly APP_DIR="/var/www/my_application"
readonly DB_NAME="production_db"
readonly RETENTION_DAYS=7
readonly LOG_FILE="/var/log/app_backup.log"
readonly ARCHIVE_FILE="${DEST_DIR}/backup_${BACKUP_DATE}.tar.gz"
readonly WEBHOOK_URL="https://discord.com/api/webhooks/123456789/your-webhook-token"
# Logging function
log() {
local msg="$1"
echo "[$(date +'%Y-%m-%d %H:%M:%S')] ${msg}" | tee -a "${LOG_FILE}"
}
# Notification handler
send_notification() {
local status="$1"
local text="$2"
curl -s -X POST -H "Content-Type: application/json" \
-d "{\"content\": \"**[${status}]** - Server: $(hostname)\n${text}\"}" \
"${WEBHOOK_URL}" > /dev/null || true
}
# Error trap handler
on_failure() {
local failed_line="$1"
log "CRITICAL: Script execution failed on line ${failed_line}."
send_notification "FAILURE" "Backup operation failed at line ${failed_line}."
rm -f "/tmp/${DB_NAME}_${BACKUP_DATE}.sql"
exit 1
}
trap 'on_failure ${LINENO}' ERR
# Main execution logic
main() {
log "Starting daily backup process..."
mkdir -p "${DEST_DIR}"
# Step 1: Dump database safely
log "Dumping MySQL database: ${DB_NAME}..."
mysqldump --single-transaction --quick "${DB_NAME}" > "/tmp/${DB_NAME}_${BACKUP_DATE}.sql"
# Step 2: Compress application source files and SQL dump together
log "Creating compressed tar archive..."
tar -czf "${ARCHIVE_FILE}" \
-C "${APP_DIR}" . \
-C "/tmp" "${DB_NAME}_${BACKUP_DATE}.sql"
# Clean up temporary database dump immediately
rm -f "/tmp/${DB_NAME}_${BACKUP_DATE}.sql"
# Step 3: Verify archive integrity and compute checksum
log "Verifying archive integrity..."
tar -tzf "${ARCHIVE_FILE}" > /dev/null
sha256sum "${ARCHIVE_FILE}" > "${ARCHIVE_FILE}.sha256"
log "Integrity verified. Checksum generated."
# Step 4: Apply retention policy (delete archives older than 7 days)
log "Purging backups older than ${RETENTION_DAYS} days..."
find "${DEST_DIR}" -type f \( -name "*.tar.gz" -o -name "*.sha256" \) \
-mtime +"${RETENTION_DAYS}" -exec rm -f {} \;
log "Backup completed successfully."
send_notification "SUCCESS" "Archive created: backup_${BACKUP_DATE}.tar.gz"
}
main
شرح أجزاء السكربت ومسار التنفيذ:
- تهيئة الثوابت والمجلدات: استخدمنا الأمر
readonlyلحماية المسارات وأسماء الملفات من أي تغيير عرضي لاحق أثناء تنفيذ العمليات. المتغيرBACKUP_DATEينشئ طابعاً زمنياً فريداً يشمل السنة والشهر واليوم والدقيقة والثانية. - دالة اعتراض الأخطاء
on_failure: تستقبل رقم السطر المعطّل عبر${LINENO}، وتسجل رسالة الخطأ، ثم ترسل إشعار الفشل عبر Webhook، مع حذف ملف التصدير المؤقت في/tmpلضمان عدم استهلاك مساحة القرص عند الانهيار. - تصدير قاعدة البيانات المتناسق: ينفّذ
mysqldumpعملية القراءة دون قفل الجداول بفضل--single-transaction، مما يتيح للتطبيق مواصلة كتابة وقراءة البيانات أثناء عمل السكربت دون توقف الخدمة، بينما يقلل المعامل--quickمن استهلاك الذاكرة العشوائية للخادم. - التحقق من سلامة الأرشيف: الأمر
tar -tzfيختبر فك الضغط وقراءة الفهرس الداخلي للأرشيف؛ فإذا كان الملف معطوباً، سيفشل الأمر فوراً ويُشغّل دالةtrap. بعد ذلك، ينشئsha256sumبصمة رقمية فريدة للأرشيف لاستخدامها لاحقاً عند استعادة البيانات للتحقق من عدم تلفها أثناء النقل. - تدوير الملفات وحذف القديم: يبحث أمر
findعن ملفات الضغط والبصمات التي مر على إنشائها أكثر من المدة المحددة فيRETENTION_DAYSثم يحذفها مباشرة لمنع تراكم الملفات.
أتمتة صيانة الخادم: السجلات والذاكرة المؤقتة ومساحات التخزين
إلى جانب النسخ الاحتياطي، تحتاج خوادم لينكس إلى صيانة وقائية دورية لتنظيف المخلفات البرمجية التي قد تفضي إلى امتلاء الأقراص وتعطيل الخدمات الأساسية. يشمل ذلك:
1. تدوير سجلات النظام وتقليص Journalctl
تسجل خدمة systemd-journald ملايين الأسطر يومياً. إذا تركت دون قيود، قد تلتهم عشرات الجيجابايت من مساحة التخزين. يمكن أتمتة تقليص حجم هذه السجلات بواسطة سكربت أسبوعي ينفّذ الأمر التالي:
journalctl --vacuum-time=14d --vacuum-size=500M
هذا الأمر يتخلص من أي سجلات أقدم من أسبوعين، ويضمن ألا يتجاوز الحجم الإجمالي للسجلات 500 ميغابايت.
2. تنظيف مخابئ الحزم البرمجية والتبعيات المهجورة
في الأنظمة المعتمدة على مدير الحزم apt (مثل Ubuntu وDebian)، تتراكم ملفات التثبيت .deb داخل المسار /var/cache/apt/archives، بالإضافة إلى حزم النواة القديمة (Old Kernels). يتضمن سكربت الصيانة الدوري الأوامر التالية بأمان كامل:
export DEBIAN_FRONTEND=noninteractive
apt-get clean
apt-get autoremove --purge -y
3. مراقبة مساحة القرص وإطلاق التحذيرات
من أهم أجزاء سكربت الصيانة فحص مساحة القسم الجذري / ومقارنته بحد أمان مسموح به (مثلاً 85%). إذا تخطى الاستهلاك هذا الحد، يُرسل السكربت تنبيهاً حرجاً عبر نظام المراقبة أو البريد الإلكتروني للتدخل قبل خروج الخادم عن الخدمة.
جدولة المهام باحترافية: لماذا تتفوق مؤقتات systemd على cron؟
يعد cron الأداة التاريخية الأكثر شهرة لجدولة المهام في لينكس، إلا أنه يعاني من عيوب جوهرية في بيئات الإنتاج المعاصرة: بيئة تشغيل معزولة تفتقر إلى مسارات النظام القياسية، تسجيل أحداث ضعيف وغير مترابط، وعدم القدرة على إدارة الاعتماديات بين الخدمات.
كبديل حديث ومتفوق، توفر توزيعات لينكس الحديثة مؤقتات systemd timers، والتي تمتاز بالمزايا الآتية:
- تسجيل مخرجات السكربت وأخطائه مركزياً داخل
journalctlدون الحاجة لإعادة توجيه المسارات يدوياً. - إمكانية ضبط التبعيات، كأن تشترط عدم بدء سكربت النسخ الاحتياطي إلا بعد اتصال الشبكة بالكامل وتوافر وحدة التخزين المشتركة عبر
After=network-online.target. - القدرة على تنفيذ خدمة تنبيه فورية عند فشل السكربت باستخدام خيار
OnFailure=notify-failure.service.
لإنشاء جدولة باستخدام systemd، نقوم ببناء ملفين داخل المسار /etc/systemd/system/:
1. ملف الخدمة: /etc/systemd/system/backup.service
[Unit]
Description=Daily Application and Database Backup
After=network.target
[Service]
Type=oneshot
User=root
ExecStart=/usr/local/bin/production_backup.sh
2. ملف المؤقت: /etc/systemd/system/backup.timer
[Unit]
Description=Run daily backup at 02:00 AM UTC
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target
تضمن خاصية Persistent=true تنفيذ السكربت فور إقلاع الخادم إذا كان مغلقاً أثناء الموعد المجدول. لتفعيل هذا النظام، نكتفي بتشغيل الأوامر التالية:
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
أبرز الأخطاء الشائعة في سكربتات النسخ والصيانة
هناك أخطاء متكررة يقع فيها مهندسو النظم ومطورو DevOps تفرغ عمليات النسخ الاحتياطي من قيمتها التشغيلية:
- فخ “نسخة شرودنجر” الاحتياطية (Schrödinger’s Backup): حالة تطلق على ملف النسخ الاحتياطي الذي تفترض أنه سليم حتى تحاول استعادته وقت الكارثة فتكتشف أنه أرشيف فارغ أو تالف. الحل: لا تعتمد نجاح السكربت على كود خروج أمر الضغط فقط؛ اختبر فتح الأرشيف عبر
tar -tzfفور إنشائه، واعتمد روتيناً دورياً (شهرياً أو ربع سنوي) لفك النسخة الاحتياطية تلقائياً في بيئة اختبار والتأكد من إمكانية تشغيل التطبيق منها. - الاعتماد على المسارات النسبية داخل السكربت: استدعاء مسارات مثل
./dataبدلاً من/var/www/app/data، أو استدعاء برامج دون التأكد من توفرها فيPATHالخاص بالمجدول. الحل: عرّف جميع المسارات كمسارات مطلقة كاملة (Absolute Paths)، أو عيّن بيئة المسار صراحة داخل السكربت عبرexport PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin". - تضمين بيانات الاعتماد نصاً صريحاً (Hardcoded Secrets): كتابة كلمات مرور قواعد البيانات ورموز Webhooks داخل نص السكربت مباشرة، مما يجعلها عرضة للكشف في حال حفظ السكربت داخل Git أو عند قراءة العمليات النشطة بـ
ps aux. الحل: عزل البيانات الحساسة في ملف تكوين منفصل ومؤمن (مثل~/.my.cnfللاتصال بقواعد بيانات MySQL بصلاحياتchmod 600) أو قراءتها من متغيرات بيئة محمية. - غياب سياسات الاحتفاظ التلقائي بالملفات (Retention Policy): إنشاء نسخ احتياطية جديدة يومياً دون حذف النسخ القديمة. يؤدي هذا بالضرورة إلى امتلاء القرص بنسبة 100%، مما يوقف قواعد البيانات ويمنع الخادم من العمل. الحل: تضمين أمر
find ... -mtime +X -deleteداخل السكربت نفسه لضمان إدارة المساحات بانتظام.
متى تعتمد على سكربتاتك الخاصة ومتى تتجه للأدوات الجاهزة؟
توفر سكربتات Bash المكتوبة بعناية حلاً مثالياً، خفيف الوزن ومرناً، عندما تدير خادماً واحداً أو بيئة متوسطة لا تتطلب تعقيدات شبكية، حيث تتيح لك التحكم الدقيق في مجريات التنفيذ دون تنصيب برمجيات إضافية تستهلك موارد النظام. كما تمنحك الحرية المطلقة في دمج التنبيهات والتعامل مع خصوصيات التطبيق المحلية بدقة وسرعة.
في المقابل، إذا كانت بنيتك التحتية ممتدة عبر عدة خوادم سحابية، أو كنت تتعامل مع أحجام بيانات تتجاوز عدة تيرابايتات، فإن الاعتماد على سكربتات مخصصة يصبح عبئاً تقنياً خطيراً. في هذه الحالات، يجب الانتقال فوراً إلى أدوات المؤسسات المتخصصة مثل Restic أو BorgBackup أو الحلول السحابية المدارة (Managed Cloud Backups). توفر هذه الأدوات تقنيات متقدمة يصعب بناؤها عبر سكربت بسيط، مثل إلغاء التكرار على مستوى الكتل (Block-level Deduplication)، التشفير عالي الأمان في جهة العميل من طرف إلى طرف (End-to-End Encryption)، والتخزين غير القابل للتعديل أو الحذف (Object Lock & Immutability) الذي يحمي النسخ من هجمات برمجيات الفدية (Ransomware).


