یک شب پنجشنبه، وسط تمیز کردن داده‌های تکراری، یک DELETE را روی سرور اصلی اجرا کردیم که شرطش ناقص نوشته شده بود. بخش بزرگی از جدول سفارش‌ها رفت. اولین کاری که کردیم باز کردن بخش بک‌آپ سی‌پنل بود. تنها چیزی که آنجا بود، بک‌آپ خودکار خود هاستینگ از دو روز قبل بود؛ روی همان دیسک، و برای برگرداندنش باید تیکت می‌زدیم و منتظر می‌ماندیم.

آن شب فهمیدیم چیزی که «بک‌آپ» صدایش می‌کردیم یک وعده بود، نه یک برنامه. از فردایش یک اسکریپت بک‌آپ خودکار دیتابیس روی همان هاست سی‌پنل نوشتیم که خودمان کنترلش می‌کنیم: هر شب یک دامپ فشرده با نام تاریخ‌دار، چرخش نسخه‌ها، لاگ، و از همه مهم‌تر یک تست بازیابی که هر ماه واقعاً اجرا می‌شود.

این مقاله همان اسکریپت است، تمیزشده. فرض‌ها: هاست اشتراکی با سی‌پنل، دسترسی SSH یا دست‌کم بخش Cron Jobs، بدون دسترسی root، دیتابیس MySQL یا MariaDB، و یک اپلیکیشن لاراول که فایل .env دارد. قطعه‌ها همین شش‌تاست: خواندن اطلاعات اتصال، گرفتن دامپ، فشرده‌سازی در همان لوله، ذخیره با نام تاریخ‌دار، حذف نسخه‌های کهنه، و ثبت نتیجه در لاگ.

زنجیره‌ی اجرای اسکریپت بک‌آپ از خواندن env تا ثبت لاگ

چرا بک‌آپ خودکار خودِ سی‌پنل کافی نیست#

بک‌آپ هاستینگ بد نیست. فقط برای نقشی که ما به آن داده بودیم ساخته نشده. سه مشکل دارد.

چرخه‌اش کوتاه است و شما تنظیمش نمی‌کنید. بیشتر هاست‌های اشتراکی یک نسخه‌ی روزانه و شاید یک نسخه‌ی هفتگی نگه می‌دارند. اینکه دقیقاً چند نسخه و چند روز، به سیاست همان شرکت بستگی دارد و می‌تواند بدون اطلاع شما عوض شود. اگر خرابی داده را دیر بفهمید — که حالت رایج است — ممکن است هر نسخه‌ای که مانده، خودش هم خراب باشد.

روی همان دیسک است. بک‌آپ‌های محلی سی‌پنل معمولاً در همان اکانت یا همان سرور نگه داشته می‌شوند. این نسخه شما را از پاک‌کردن اشتباهی نجات می‌دهد، ولی از خرابی دیسک، تعلیق اکانت، یا از دست رفتن دسترسی به اکانت نجات نمی‌دهد. بک‌آپی که با اصلْ یک سرنوشت دارد، بک‌آپ نیست.

بازیابی‌اش کند و همه‌یاهیچ است. خیلی وقت‌ها فقط یک جدول را می‌خواهید، نه کل اکانت را. با یک فایل .sql.gz که خودتان ساخته‌اید، می‌توانید همان جدول را در یک دیتابیس موقت بالا بیاورید و فقط داده‌ی لازم را بردارید. با ریستور کامل اکانت، این کار یعنی برگرداندن همه‌چیز به دو روز قبل.

نتیجه‌ی عملی: بک‌آپ هاستینگ را نگه دارید، ولی به آن تکیه نکنید. بک‌آپ خودتان لایه‌ی اول است.

خواندن اطلاعات اتصال از فایل env#

اسم دیتابیس را در اسکریپت هاردکد نکنید. روی سی‌پنل نام دیتابیس با پیشوند نام کاربری ساخته می‌شود و موقع مهاجرت یا ساخت محیط staging عوض می‌شود. منبع درست همان .env است.

یک تابع کوچک برای خواندن هر کلید:

Bash
ENV_FILE="$HOME/app/.env"

env_get() {
  sed -n "s/^$1=//p" "$ENV_FILE" | head -n1 | tr -d '\r' | sed -e 's/^"//' -e 's/"$//'
}

DB_NAME="$(env_get DB_DATABASE)"

سه نکته در همین چهار خط هست.

head -n1 می‌گذاریم چون فایل .env ممکن است یک کلید را دوبار داشته باشد (مثلاً یک نسخه‌ی کامنت‌شده که کامنتش پاک شده). کلید تکراری در .env خودش یک ابهام است و بهتر است اصلاً نگذاریدش؛ ما در این اسکریپت صریحاً اولین مقدار را برمی‌داریم تا رفتار قابل پیش‌بینی باشد.

sed -e 's/^"//' -e 's/"$//' گیومه‌ی احتمالی دور مقدار را برمی‌دارد. اگر رمز شما کاراکتر # یا فاصله دارد، احتمالاً در .env داخل گیومه است و آن گیومه نباید وارد رشته‌ی اتصال شود.

و مهم‌ترینش: tr -d '\r'. اگر فایل .env یک بار با ویرایشگر ویندوزی ذخیره شده باشد، انتهای هر خط \r\n است. مقداری که می‌خوانید یک کاراکتر اضافه‌ی نامرئی دارد و اسکریپت با خطایی شکست می‌خورد که هیچ ربطی به علت ندارد — مثلاً «Unknown database» با نامی که دقیقاً درست به‌نظر می‌رسد. این دقیقاً همان تله‌ای است که قبلاً سرش وقت گذاشتیم و در پایان‌خط ویندوزی در فایل .env جدا نوشتیمش. اگر یک بار پیش بیاید، همیشه یادتان می‌ماند.

رمز دیتابیس را روی خط فرمان ننویسید#

وسوسه‌ی اول این است:

Bash
mysqldump -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" > dump.sql

این کار می‌کند و همین باعث می‌شود دیر متوجه اشکالش شوید. دو مشکل دارد.

اول، mysqldump خودش هشدار می‌دهد که دادن رمز روی خط فرمان ناامن است، و راست می‌گوید: روی سرور اشتراکی، فهرست پروسه‌ها لزوماً برای شما تنها نیست. رمز در آرگومان‌های پروسه دیده می‌شود.

دوم، رمز داخل فایل اسکریپت می‌ماند و آن فایل ممکن است در گیت برود، در بک‌آپ فایل‌ها کپی شود، یا با دسترسی اشتباه ساخته شود.

راه درست، فایل تنظیمات کلاینت است. یک بار بسازید:

INI
[client]
user = "cpuser_shop"
password = "Th3-r34l-p@ss"
default-character-set = utf8mb4

آن را در ~/.my.cnf ذخیره کنید و دسترسی‌اش را ببندید:

Bash
chmod 600 ~/.my.cnf

اگر فایل تنظیمات برای گروه یا دیگران قابل نوشتن باشد، کلاینت MySQL آن را نادیده می‌گیرد؛ ضمن اینکه رمز نباید برای هیچ کاربر دیگری روی سرور خواندنی باشد. پس این chmod تشریفاتی نیست.

بعد در اسکریپت:

Bash
mysqldump --defaults-extra-file="$HOME/.my.cnf" "$DB_NAME"

یک تله‌ی ریز اینجا هست که وقت‌گیر است: --defaults-extra-file باید اولین آرگومان بعد از نام دستور باشد. اگر بعد از سوییچ‌های دیگر بیاید، کلاینت با خطای «unknown variable» بالا نمی‌آید. همین یک قاعده را رعایت کنید و رد شوید.

اگر رمز را جای دیگری هم لازم دارید، همان env_get را برای DB_USERNAME و DB_PASSWORD صدا بزنید و فایل .my.cnf را با اسکریپت بسازید؛ ولی ساده‌ترین حالت این است که یک بار دستی بسازیدش و بعد از هر تغییر رمز به‌روزش کنید.

سوییچ‌های mysqldump که واقعاً فرق می‌کنند#

دستور نهایی ما این است:

Bash
mysqldump --defaults-extra-file="$HOME/.my.cnf" \
  --single-transaction \
  --quick \
  --no-tablespaces \
  --routines \
  --default-character-set=utf8mb4 \
  "$DB_NAME"

هر کدام دلیل دارد.

--single-transaction دامپ را داخل یک تراکنش با ایزولیشن repeatable read می‌گیرد. یعنی همه‌ی جدول‌های InnoDB از یک لحظه‌ی واحد خوانده می‌شوند، بدون قفل‌کردن سایت. این همان چیزی است که جای --lock-tables را می‌گیرد.

و همین‌جا جواب سؤال رایج: روی InnoDB به --lock-tables نیاز ندارید. --lock-tables جدول‌ها را برای خواندن قفل می‌کند و در عمل نوشتن روی سایت را تا پایان دامپ می‌خواباند. برای موتور MyISAM چاره‌ای نیست، ولی اگر جدول‌هایتان InnoDB است — که در لاراول مدرن پیش‌فرض است — این سوییچ فقط ضرر دارد. ضمناً --single-transaction و --lock-tables با هم جمع نمی‌شوند؛ دومی اولی را خنثی می‌کند. اگر مطمئن نیستید موتور جدول‌ها چیست، یک بار چک کنید:

SQL
SELECT TABLE_NAME, ENGINE FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE();

--quick می‌گوید ردیف‌ها را ردیف‌به‌ردیف بخوان و کل جدول را در حافظه نگه ندار. روی هاست اشتراکی که سقف حافظه دارید، این تفاوت بین «کار کرد» و «پروسه کشته شد» است. این سوییچ بخشی از --opt است و به‌صورت پیش‌فرض روشن است؛ صریح نوشتنش فقط برای این است که اگر جایی --skip-opt وارد دستور شد، از قلم نیفتد.

--no-tablespaces روی نسخه‌های تازه‌تر لازم می‌شود. mysqldump جدید برای نوشتن دستور CREATE TABLESPACE به دسترسی PROCESS نیاز دارد و کاربر شما روی هاست اشتراکی این دسترسی را ندارد. علامتش یک خطای «Access denied; you need (at least one of) the PROCESS privilege(s)» است که وسط دامپ می‌آید و فایل ناقص می‌ماند. این سوییچ همان بخش را کلاً حذف می‌کند.

--default-character-set=utf8mb4 تضمین می‌کند اتصالِ دامپ روی utf8mb4 است. اگر این را ننویسید و کلاینت روی latin1 یا utf8 قدیمی بیاید، متن فارسی می‌تواند در فایل خروجی خراب ذخیره شود و شما تازه موقع ریستور بفهمید. این همان خانواده‌ی مشکلاتی است که در بحث کاراکترست و collation فارسی سراغش را دارید.

--routines رویه‌ها و توابع ذخیره‌شده را هم می‌آورد. تریگرها به‌صورت پیش‌فرض می‌آیند. --events را عمداً ننوشتیم: روی خیلی از هاست‌های اشتراکی دسترسی EVENT را نداده‌اند و دامپ با خطا می‌ایستد. اگر ایونت دارید، اضافه‌اش کنید و تست کنید؛ اگر خطا داد، بردارید.

فشرده‌سازی با gzip و نام‌گذاری با تاریخ#

دامپ SQL متن است و متن خوب فشرده می‌شود. فایل واسط ننویسید؛ همان‌جا از لوله رد کنید:

Bash
STAMP="$(date +%F-%H%M)"
OUT="$DAILY/${DB_NAME}-${STAMP}.sql.gz"

mysqldump --defaults-extra-file="$HOME/.my.cnf" ... "$DB_NAME" | gzip -9 > "$OUT"

date +%F-%H%M چیزی مثل 2026-09-07-0305 می‌دهد. این قالب دو خاصیت دارد: در ls مرتب‌شده به ترتیب زمانی می‌آید، و بدون فکر کردن می‌فهمید کدام فایل برای کِی است.

یک تله‌ی جدی در لوله هست: کد خروجی یک لوله در بش، کد خروجی آخرین دستور است. اگر mysqldump وسط کار بترکد ولی gzip سالم تمام شود، اسکریپت فکر می‌کند همه‌چیز خوب بوده و یک فایل ناقص می‌ماند که ظاهرش سالم است. علاجش این خط در ابتدای اسکریپت است:

Bash
set -euo pipefail

pipefail کاری می‌کند که شکست هر حلقه‌ی لوله، کل لوله را شکست‌خورده حساب کند. بعد از نوشتن فایل هم سلامت آرشیو را بررسی کنید:

Bash
gzip -t "$OUT"

و اندازه‌ی فایل را نگاه کنید. یک دامپ سالم از یک دیتابیس واقعی هیچ‌وقت چند صد بایت نیست. اگر ناگهان فایل خیلی کوچک شد، یعنی چیزی شکسته — معمولاً دسترسی یا نام دیتابیس.

اسکریپت کامل بک‌آپ دیتابیس#

این همان چیزی است که روی سرور می‌گذاریم، مثلاً در ~/scripts/db-backup.sh:

Bash
#!/bin/bash
set -euo pipefail

APP_DIR="$HOME/app"
ENV_FILE="$APP_DIR/.env"
BASE="$HOME/backups"
DAILY="$BASE/daily"
WEEKLY="$BASE/weekly"
MONTHLY="$BASE/monthly"
LOG="$HOME/logs/db-backup.log"
MYCNF="$HOME/.my.cnf"
MYSQLDUMP="/usr/bin/mysqldump"

mkdir -p "$DAILY" "$WEEKLY" "$MONTHLY" "$(dirname "$LOG")"

log() { printf '%s %s\n' "$(date '+%F %T')" "$*" >> "$LOG"; }

env_get() {
  sed -n "s/^$1=//p" "$ENV_FILE" | head -n1 | tr -d '\r' | sed -e 's/^"//' -e 's/"$//'
}

DB_NAME="$(env_get DB_DATABASE)"
if [ -z "$DB_NAME" ]; then
  log "ERROR: DB_DATABASE not found in $ENV_FILE"
  exit 1
fi

STAMP="$(date +%F-%H%M)"
OUT="$DAILY/${DB_NAME}-${STAMP}.sql.gz"

"$MYSQLDUMP" --defaults-extra-file="$MYCNF" \
  --single-transaction --quick --no-tablespaces \
  --routines --default-character-set=utf8mb4 \
  "$DB_NAME" | gzip -9 > "$OUT"

if ! gzip -t "$OUT"; then
  log "ERROR: corrupt archive $OUT"
  rm -f "$OUT"
  exit 1
fi

SIZE="$(stat -c %s "$OUT")"
if [ "$SIZE" -lt 20000 ]; then
  log "WARN: dump looks too small ($SIZE bytes) $OUT"
fi

log "OK $OUT ($SIZE bytes)"

قابل اجرا کنید:

Bash
chmod +x ~/scripts/db-backup.sh
~/scripts/db-backup.sh

بار اول را حتماً دستی اجرا کنید و به لاگ نگاه کنید. اگر همین‌جا کار نکند، در کرون هم کار نمی‌کند — فقط ساکت‌تر شکست می‌خورد.

چرخش نسخه‌ها: هفت روزانه، چهار هفتگی، سه ماهانه#

اگر فقط فایل بسازید و پاک نکنید، یک روز دیسک اکانت پر می‌شود و سایت از کار می‌افتد. بک‌آپی که هاست را می‌خواباند، خودش تبدیل به حادثه شده.

طرحی که برای ما جواب داده ساده است: هفت نسخه‌ی روزانه برای اشتباه‌های تازه، چهار نسخه‌ی هفتگی برای خرابی‌هایی که چند روز بعد کشف می‌شوند، و سه نسخه‌ی ماهانه برای وقتی که می‌خواهید بفهمید یک داده از کِی خراب شده.

طرح نگه‌داری نسخه‌ها در سه ردیف روزانه، هفتگی و ماهانه

این تکه را به انتهای همان اسکریپت اضافه کنید:

Bash
# هفت نسخه‌ی روزانه
find "$DAILY" -maxdepth 1 -type f -name '*.sql.gz' -mtime +7 -delete

# جمعه‌ها یک کپی هفتگی، چهار هفته نگه‌داری
if [ "$(date +%u)" = "5" ]; then
  cp "$OUT" "$WEEKLY/"
  find "$WEEKLY" -maxdepth 1 -type f -name '*.sql.gz' -mtime +28 -delete
fi

# روز اول هر ماه میلادی یک کپی ماهانه، حدود سه ماه نگه‌داری
if [ "$(date +%d)" = "01" ]; then
  cp "$OUT" "$MONTHLY/"
  find "$MONTHLY" -maxdepth 1 -type f -name '*.sql.gz' -mtime +93 -delete
fi

چند نکته که در عمل به آن خوردیم.

-mtime +7 یعنی فایل‌هایی که بیش از هفت شبانه‌روز کامل از آخرین تغییرشان گذشته. پس در عمل هشت نسخه روی دیسک می‌بینید نه هفت‌تا. اگر دقت روز برایتان مهم است، عدد را با همین ذهنیت انتخاب کنید.

-maxdepth 1 و -type f را بی‌خود نگذاشته‌ایم. find با -delete ابزار بی‌رحمی است؛ محدودش کنید به همان پوشه و همان الگوی نام. و قبل از اینکه -delete را بنویسید، یک بار بدون آن اجرا کنید تا فهرست فایل‌هایی که قرار است حذف شوند را ببینید:

Bash
find "$HOME/backups/daily" -maxdepth 1 -type f -name '*.sql.gz' -mtime +7

date +%u شماره‌ی روز هفته را از دوشنبه (۱) تا یکشنبه (۷) می‌دهد، پس 5 یعنی جمعه. برای نسخه‌ی ماهانه از روز اول ماه میلادی استفاده کرده‌ایم چون date استاندارد سرور شمسی نمی‌شناسد؛ اگر روز اول ماه شمسی برایتان مهم است، باید تاریخ را خودتان حساب کنید و آن یک بحث دیگر است.

بک‌آپ روی همان دیسک، بک‌آپ نیست#

تا اینجا فقط جای فایل‌ها را عوض کرده‌ایم. اگر دیسک سرور خراب شود یا اکانت تعلیق شود، هم سایت رفته هم بک‌آپ. این مرحله را جدی بگیرید، چون همان چیزی است که اکثراً به «بعداً» موکول می‌شود.

سه قاعده‌ی ساده.

بک‌آپ را هیچ‌وقت زیر public_html نگذارید. یک فایل .sql.gz که از اینترنت قابل دانلود است، یعنی کل دیتابیس شما عمومی است — رمزهای هش‌شده، ایمیل‌ها، آدرس‌ها. پوشه‌ی ~/backups بیرون از ریشه‌ی وب باشد.

یک نسخه را دوره‌ای از سرور بیرون بکشید. ساده‌ترین راهی که نیاز به سرویس اضافه ندارد این است که از یک ماشین دیگر (لپ‌تاپ خودتان یا یک سرور کوچک) با rsync روی SSH بکشید:

Bash
rsync -avz --delete \
  -e "ssh -p 22" \
  cpuser@example.com:~/backups/weekly/ \
  /home/me/offsite/shop-weekly/

کشیدن بهتر از فرستادن است: اگر سرور سایت را کسی در دست بگیرد، کلید مقصد روی آن سرور نیست. اگر SSH ندارید، همان کار را دوره‌ای با FTP و به‌صورت دستی انجام دهید — دستی و ماهانه، بی‌نهایت بهتر از هرگز است.

بدانید نسخه‌ی بیرونی چقدر عقب است. اگر ماهی یک بار بیرون می‌کشید، یعنی در بدترین حالت یک ماه داده از دست می‌رود. این عدد را آگاهانه انتخاب کنید، نه اتفاقی.

کرون‌جاب بک‌آپ روی سی‌پنل: مسیر مطلق و لاگ خطا#

در بخش Cron Jobs سی‌پنل یک ورودی روزانه بسازید:

Bash
5 3 * * * /bin/bash /home/cpuser/scripts/db-backup.sh >> /home/cpuser/logs/cron-db-backup.log 2>&1

چند نکته که همه‌شان یک بار ما را زده‌اند.

همه‌ی مسیرها مطلق. شل کرون یک شل غیرتعاملی است: نه .bashrc شما را می‌خواند، نه PATH کاملتان را دارد، نه alias‌هایتان را می‌شناسد. چیزی که در ترمینال بی‌عیب کار می‌کند ممکن است در کرون اصلاً پیدا نشود. همین قصه برای نسخه‌ی PHP هم صادق است و جدا نوشتیمش: تله‌ی alias در شل غیرتعاملی کرون. به همین دلیل در اسکریپت هم MYSQLDUMP="/usr/bin/mysqldump" را صریح نوشتیم. اگر مطمئن نیستید مسیرش کجاست، یک بار command -v mysqldump بزنید.

خروجی را خاموش کنید، ولی خطا را نه. نوشتن >/dev/null 2>&1 وسوسه‌انگیز است چون جلوی ایمیل‌های روزانه‌ی کرون را می‌گیرد. ولی همان دستور خطاها را هم دور می‌ریزد. آن‌وقت اسکریپت شما می‌تواند ماه‌ها بی‌صدا شکست بخورد و شما دقیقاً روزی بفهمید که به بک‌آپ نیاز دارید. راه درست >> فایل 2>&1 است: خروجی از ایمیل بیرون می‌رود ولی روی دیسک می‌ماند.

ایمیل کرون را در پنل خالی کنید (یا MAILTO="" بگذارید) تا هر شب یک ایمیل بی‌محتوا نگیرید و در نتیجه ایمیل‌های کرون را نادیده نگیرید.

گاهی به لاگ سر بزنید. یک لاگی که کسی نمی‌خواندش با نبودنش فرقی ندارد. ساده‌ترین عادت: هر بار که به SSH وصل شدید، tail -n 20 ~/logs/db-backup.log.

و یک هشدار: اگر وسوسه شدید بک‌آپ را از داخل خود لاراول و با shell_exec بگیرید، احتمالاً به دیوار disable_functions روی هاست اشتراکی می‌خورید. روی بسیاری از هاست‌های اشتراکی exec و shell_exec و proc_open بسته‌اند. جای این کار، کرون است.

تست بازیابی؛ بک‌آپ تست‌نشده بک‌آپ نیست#

این بخش را کسی انجام نمی‌دهد و دقیقاً به همین دلیل مهم‌ترین بخش مقاله است. تا وقتی یک فایل را برنگردانده باشید، نمی‌دانید بک‌آپ دارید یا فقط یک پوشه فایل .gz دارید.

اول یک نگاه سرسری به محتوا:

Bash
gunzip -c ~/backups/daily/cpuser_shop-2026-09-05-0305.sql.gz | head -n 40

باید هدر mysqldump، نسخه‌ی سرور، و اولین CREATE TABLE را ببینید. اگر خالی بود یا وسطش پیام خطا داشت، همان‌جا معلوم می‌شود.

بعد ریستور واقعی در یک دیتابیس موقت. روی سی‌پنل معمولاً کاربر شما اجازه‌ی CREATE DATABASE از خط فرمان ندارد؛ دیتابیس را از بخش MySQL Databases پنل بسازید (مثلاً cpuser_rtest)، همان کاربر را با دسترسی کامل به آن وصل کنید، و بعد:

Bash
gunzip -c ~/backups/daily/cpuser_shop-2026-09-05-0305.sql.gz \
  | mysql --defaults-extra-file="$HOME/.my.cnf" cpuser_rtest

اگر بی‌صدا تمام شد، خبر خوبی است. حالا سه چیز را بررسی کنید:

SQL
SELECT COUNT(*) FROM users;
SELECT MAX(created_at) FROM orders;
SELECT TABLE_NAME, TABLE_COLLATION
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'cpuser_rtest';

اولی می‌گوید داده آمده. دومی می‌گوید داده تا کِی تازه است — اگر جدیدترین رکورد مال دو هفته پیش است، یعنی کرون شما مدتی است کار نمی‌کند. سومی می‌گوید کاراکترست جدول‌ها سالم مانده؛ اگر جایی latin1 دیدید، متن فارسی‌تان در خطر است و باید سوییچ --default-character-set و تنظیمات دیتابیس مقصد را بازبینی کنید.

آخرش هم یک نگاه چشمی به داده‌ی فارسی بیندازید:

SQL
SELECT id, name FROM products ORDER BY id DESC LIMIT 5;

اگر به‌جای فارسی علامت سؤال یا کاراکترهای عجیب دیدید، مشکل در همان زنجیره‌ی کاراکترست است نه در بک‌آپ.

وقتی کارتان تمام شد، دیتابیس موقت را از پنل حذف کنید. این تست را ماهی یک بار انجام دهید و تاریخش را جایی بنویسید. ده دقیقه وقت می‌گیرد و تنها راهی است که می‌فهمید نقشه‌تان واقعی است.

فایل‌های storage و چیزهایی که نباید بک‌آپ گرفت#

دیتابیس نصف ماجراست. تصویر محصول، فایل‌های قابل دانلود و فاکتورها روی دیسک‌اند. برای فروشگاه فایل، از دست رفتن storage/app تقریباً به بدی از دست رفتن دیتابیس است.

بک‌آپ فایل‌ها را جدا و کم‌تکرارتر بگیرید، چون حجمش خیلی بیشتر است:

Bash
tar -czf "$HOME/backups/files/storage-$(date +%F).tar.gz" \
  --exclude='storage/framework/cache/*' \
  --exclude='storage/framework/sessions/*' \
  --exclude='storage/framework/views/*' \
  --exclude='storage/logs/*' \
  -C "$HOME/app" storage

توجه کنید که storage/app/public معمولاً با یک لینک از public/storage سرو می‌شود. خودِ لینک را بک‌آپ نگیرید؛ بعد از ریستور دوباره بسازیدش. روی سی‌پنل این کار خودش تله دارد که در storage:link روی سی‌پنل نوشتیمش.

و حالا فهرست چیزهایی که نباید در بک‌آپ روزانه باشند، چون فقط حجم و زمان می‌خورند:

  • vendor/ — با composer install از روی composer.lock بازساخته می‌شود. فقط مطمئن شوید خود composer.lock در گیت هست. اگر روی سرور اصلاً composer ندارید، یک نسخه از vendor را در زمان هر انتشار جدا نگه دارید، نه هر شب.
  • node_modules/ — هیچ‌وقت.
  • storage/framework/cache، sessions، views — همه بازساختنی. sessions را بک‌آپ نگیرید مگر اینکه واقعاً بخواهید کاربرها بعد از ریستور لاگین بمانند.
  • storage/logs — سنگین و بی‌فایده در بک‌آپ. اگر لاگ لازم دارید، جدا آرشیوش کنید.
  • bootstrap/cache — بازساختنی با php artisan config:cache.
  • خود پوشه‌ی backups — این را جدی بگیرید؛ یک tar بدون exclude که پوشه‌ی بک‌آپ را هم بردارد، هر شب حجم را چند برابر می‌کند.

در مقابل، این‌ها باید باشند: storage/app، فایل .env (در جای امن و جدا)، و کد که در گیت است.

جمع‌بندی#

بک‌آپ یک فایل نیست، یک چرخه است: گرفتن، چرخاندن، بیرون بردن، و برگرداندن. سه‌تای اول را می‌شود در یک اسکریپت صد خطی نوشت و فراموش کرد. چهارمی را باید دستی و دوره‌ای انجام داد و همان است که فرق می‌گذارد بین کسی که بک‌آپ دارد و کسی که فکر می‌کند دارد.

اگر امروز فقط وقت یک کار را دارید، آخرین فایل بک‌آپتان را در یک دیتابیس موقت ریستور کنید و تعداد ردیف‌های جدول اصلی را بشمارید. جوابِ آن COUNT تنها چیزی است که واقعاً می‌دانید.