Looking to hire Laravel developers? Try LaraJobs

laravel-backup-kit maintained by utab

Description
Encrypted, monitored database backups for Laravel on S3-compatible storage, built on spatie/laravel-backup.
Author
Last update
2026/10/17 16:50 (dev-main)
License
Downloads
3

Comments
comments powered by Disqus

Laravel Backup Kit

بکاپ رمزنگاری‌شده و پایش‌شونده‌ی دیتابیس برای Laravel، روی هر ذخیره‌ساز سازگار با S3 (پیش‌فرض: ArvanCloud). ساخته‌شده روی spatie/laravel-backup.

پکیج را نصب می‌کنید، چند متغیر .env می‌گذارید و بقیه خودکار است: بکاپ شبانه، آرشیو AES-256، چیدمان تاریخ‌دار، تاریخچه‌ی اجراها، heartbeat به مانیتور مرکزی، هشدار و صفحه‌ی عیب‌یابی storage.

فهرست

امکانات

  • بکاپ شبانه‌ی فقط دیتابیس (فایل‌های پروژه هرگز بکاپ نمی‌شوند) در قالب zip با رمزنگاری AES-256.
  • چیدمان تاریخ‌دار روی باکت: <project>/YYYY/MM/DD/<timestamp>.zip؛ چند پروژه می‌توانند یک باکت مشترک داشته باشند.
  • تاریخچه‌ی اجراها در جدول backup_runs برای هر backup، cleanup و hot.
  • وضعیت سلامت پنج‌حالته (SUCCESS، FAILED، STORAGE_ERROR، NEVER_RUN، STALE) و دستور backup:status با exit code مناسب cron و alerting.
  • Heartbeat اختیاری به مانیتور مرکزی؛ بعد از هر شکست هم بلافاصله ارسال می‌شود.
  • Hot backup ساعتی فقط برای جدول‌های حیاتی (اختیاری).
  • ایمیل هشدار اختیاری، لاگ اختصاصی backup و پیام‌های خطای sanitize‌شده (بدون secret).
  • صفحه‌ی تست storage برای عیب‌یابی اتصال به باکت.
  • زمان‌بندی و پیکربندی خودکار؛ هر دو قابل خاموش‌شدن‌اند.

نیازمندی‌ها

  • PHP ^8.3 با اکستنشن zip
  • Laravel ^12.40 یا ^13.0
  • spatie/laravel-backup ^10.3 و league/flysystem-aws-s3-v3 (همراه پکیج نصب می‌شوند)
  • باینری dump دیتابیس روی سرور (مثلاً mysqldump یا mariadb-dump) روی PATH یا از طریق DB_DUMP_BINARY_PATH
  • Scheduler لاراول فعال: * * * * * php /path/to/artisan schedule:run >> /dev/null 2>&1

نصب سریع

composer require utab/laravel-backup-kit
php artisan backup-kit:install
php artisan migrate

backup-kit:install فایل‌های config/backup-kit.php و config/backup.php (تنظیمات آماده‌ی Spatie) را publish می‌کند. فایل‌های موجود دست‌نخورده می‌مانند، مگر با --force. معادل دستی:

php artisan vendor:publish --tag=backup-kit-config
php artisan vendor:publish --tag=backup-kit-spatie-config
php artisan vendor:publish --tag=backup-kit-migrations   # فقط اگر migration را خودتان نگه می‌دارید

حداقل متغیرهای .env:

BACKUP_PROJECT=my-project              # پوشه‌ی ریشه‌ی این پروژه روی باکت (پیش‌فرض: APP_NAME)، در باکت مشترک یکتا باشد
BACKUP_ARCHIVE_PASSWORD=               # رمز AES-256 آرشیو، الزامی
ARVAN_ACCESS_KEY=
ARVAN_SECRET_KEY=
ARVAN_BUCKET=
ARVAN_ENDPOINT=https://s3.ir-central1.arvanstorage.ir   # بدون نام باکت در host

بعد از تنظیم، نصب را بررسی کنید:

php artisan backup:status          # دیسک reachable است؟
php artisan backup:run --only-db   # اولین بکاپ دستی
php artisan backup:status          # باید SUCCESS شود

اگر از قبل config/backup.php دارید و می‌خواهید نگهش دارید: source.files.include باید خالی باشد، password و encryption => 'aes256' تنظیم شود، destination.disks شامل BACKUP_DISK باشد و backup.name همان BACKUP_PROJECT باشد. ساده‌ترین راه شروع از stub پکیج است.

پیکربندی

همه‌ی تنظیمات پکیج در config/backup-kit.php است و تقریباً همه از .env خوانده می‌شوند. تنظیمات Spatie (retention، health checkها، notifications) در config/backup.php می‌مانند.

متغیرهای محیطی

ذخیره‌سازی و هویت پروژه

متغیر پیش‌فرض توضیح
BACKUP_DISK arvan-backup نام دیسک مقصد
BACKUP_PROJECT APP_NAME پوشه‌ی ریشه‌ی پروژه روی باکت و برچسب آن در مانیتور
BACKUP_ARCHIVE_PASSWORD - رمز آرشیو (AES-256)؛ بدون آن health check ArchiveIsEncrypted شکست می‌خورد
ARVAN_ACCESS_KEY / ARVAN_SECRET_KEY - credential سرویس Object Storage
ARVAN_BUCKET - نام باکت
ARVAN_ENDPOINT - endpoint S3؛ بدون نام باکت
ARVAN_REGION default region
ARVAN_USE_PATH_STYLE_ENDPOINT true آدرس‌دهی path-style
BACKUP_RUNS_TABLE backup_runs نام جدول تاریخچه (قبل از migrate تنظیم شود)
BACKUP_KIT_LOAD_MIGRATIONS true بارگذاری خودکار migration پکیج؛ بعد از publish دستی false کنید

Dump دیتابیس

متغیر پیش‌فرض توضیح
DB_DUMP_BINARY_PATH خالی پوشه‌ی حاوی mysqldump (مثل /usr/bin)، نه خود باینری
DB_DUMP_SINGLE_TRANSACTION true dump سازگار بدون قفل جدول (InnoDB)
DB_DUMP_TIMEOUT 300 timeout به ثانیه
DB_DUMP_EXCLUDE_TABLES خالی جدول‌های حذف‌شونده، جداشده با کاما. جدول به‌کلی (schema و data) حذف می‌شود و هنگام restore ساخته نمی‌شود

زمان‌بندی، مانیتور و hot backup

متغیر پیش‌فرض توضیح
BACKUP_SCHEDULE_ENABLED true ثبت خودکار زمان‌بندی
BACKUP_DAILY_AT 02:30 ساعت بکاپ شبانه
BACKUP_CLEANUP_AT 04:00 ساعت cleanup (retention)
BACKUP_REPORT_EVERY_MINUTES 15 فاصله‌ی heartbeat (۱ تا ۵۹)
BACKUP_MONITOR_ENABLED true کلید کلی مانیتور
BACKUP_MONITOR_URL - endpoint heartbeat؛ بدون مقدار، هیچ heartbeatی ارسال نمی‌شود
BACKUP_MONITOR_TOKEN - Bearer token
BACKUP_MONITOR_TIMEOUT 10 timeout درخواست به ثانیه
BACKUP_STALE_AFTER_HOURS 30 بعد از این مدت بدون موفقیت، وضعیت STALE می‌شود
BACKUP_HOT_TABLES خالی جدول‌های حیاتی، جداشده با کاما؛ خالی یعنی hot backup غیرفعال
BACKUP_HOT_ENABLED true کلید کلی hot backup
BACKUP_HOT_FROM_HOUR / BACKUP_HOT_TO_HOUR 9 / 23 بازه‌ی کاری اجرای ساعتی
BACKUP_HOT_KEEP_DAYS 7 نگه‌داری آرشیوهای hot (0 یعنی حذف نکن)

صفحه‌ی تست، لاگ، retention و اعلان

متغیر پیش‌فرض توضیح
BACKUP_STORAGE_TEST_ENABLED true ثبت route صفحه‌ی تست storage
BACKUP_STORAGE_TEST_PATH backup-storage-test مسیر route
BACKUP_DEBUG_TOKEN - توکن صفحه‌ی تست خارج از local (fallback: BACKUP_MONITOR_TOKEN)
BACKUP_NOTIFICATION_MAIL_TO - گیرنده‌ی ایمیل هشدار؛ خالی یعنی ایمیل خاموش
BACKUP_LOG_LEVEL / BACKUP_LOG_DAYS info / 30 سطح و مدت نگه‌داری لاگ storage/logs/backup-*.log
BACKUP_KEEP_ALL_DAYS 7 نگه‌داری همه‌ی بکاپ‌ها
BACKUP_KEEP_DAILY_DAYS 16 یک بکاپ در روز
BACKUP_KEEP_WEEKLY_WEEKS 8 یک بکاپ در هفته
BACKUP_KEEP_MONTHLY_MONTHS 4 یک بکاپ در ماه
BACKUP_KEEP_YEARLY_YEARS 2 یک بکاپ در سال
BACKUP_MAX_STORAGE_MB 20000 سقف فضا؛ قدیمی‌ترین بکاپ‌ها بعد از آن حذف می‌شوند و باید زیر سهمیه‌ی باکت بماند
BACKUP_MAX_AGE_DAYS 2 health check: بیشینه‌ی سن جدیدترین بکاپ
BACKUP_VERIFY_ARCHIVE true بررسی آرشیو بعد از ساخت
BACKUP_TRIES / BACKUP_RETRY_DELAY_SECONDS 2 / 60 تعداد تلاش و فاصله‌ی بین تلاش‌ها

تنظیم خودکار (auto-configure)

پکیج در boot این سه مورد را تعریف می‌کند، هرکدام فقط اگر اپ خودش تعریفش نکرده باشد:

  • دیسک S3 با نام BACKUP_DISK که از متغیرهای ARVAN_* تغذیه می‌شود.
  • کانال لاگ backup (روزانه، فایل storage/logs/backup-YYYY-MM-DD.log).
  • تنظیمات dump روی اتصال‌های دیتابیس لیست‌شده در backup.source.databases (single-transaction، timeout، exclude tables، مسیر باینری). رمز دیتابیس از طریق فایل defaults موقت به mysqldump می‌رسد و روی خط فرمان نمی‌آید.

هرکدام با backup-kit.auto_configure.{disk,log_channel,dump}.enabled خاموش می‌شود. تعریف‌ها داخل فایل config هستند، پس php artisan config:cache آن‌ها را هم cache می‌کند. برای سفارشی‌سازی (مثلاً دیسک غیر Arvan) کافی است دیسک را خودتان در config/filesystems.php تعریف کنید.

نحوه‌ی کار

php artisan backup:run --only-db   (زمان‌بندی‌شده، ۰۲:۳۰)
        │
        ▼
dump.sql ──► ZIP (AES-256 + BACKUP_ARCHIVE_PASSWORD) ──► verify
        │
        ▼
<ARVAN_BUCKET>/<BACKUP_PROJECT>/YYYY/MM/DD/<timestamp>.zip
        │
        ├── backup_runs (DB)  ──► SUCCESS / FAILED / STORAGE_ERROR / NEVER_RUN / STALE
        └── heartbeat ──► BACKUP_MONITOR_URL (اختیاری)
  • مسیر تاریخ‌دار هنگام زمان‌بندی با --destination-path به backup:run داده می‌شود. Listener MoveBackupToDatedFolder روی رویداد BackupWasSuccessful هر آرشیو flat را (از جمله اجراهای دستی) به Y/m/d منتقل می‌کند؛ idempotent است و فایل موجود را overwrite نمی‌کند.
  • Listener RecordBackupRunEvents بعد از جابه‌جایی، اجرا را در backup_runs ثبت و heartbeat را ارسال می‌کند. خطای ثبت یا مانیتور هرگز pipeline بکاپ را نمی‌شکند.
  • پیام خطا قبل از ذخیره یا لاگ از BackupErrorSanitizer عبور می‌کند.

دستورها

دستور توضیح
backup-kit:install [--force] [--migrations] publish تنظیمات (و اختیاری migration) و چاپ مراحل باقی‌مانده
backup:status [--json] [--skip-storage] وضعیت سلامت؛ exit code صفر فقط برای SUCCESS. --skip-storage پروب دیسک را رد می‌کند
backup:report [--dry-run] محاسبه و ارسال heartbeat؛ --dry-run فقط payload را چاپ می‌کند
backup:hot [--tables=a,b] [--from-file=dump.sql] [--list] بکاپ جدول‌های حیاتی؛ --list جدول‌های تنظیم‌شده را نشان می‌دهد

دستورهای خود Spatie هم کار می‌کنند: backup:run، backup:clean، backup:list و backup:monitor.

php artisan backup:run --only-db
php artisan backup:list
php artisan backup:clean

# destination-path کاملاً جای نام پروژه می‌نشیند: پیشوند پروژه را خودتان بدهید
php artisan backup:run --only-db --destination-path="my-project/manual/$(date +%Y/%m/%d)"

زمان‌بندی

مگر با BACKUP_SCHEDULE_ENABLED=false، چهار ورودی روی scheduler اپ ثبت می‌شود. همه با withoutOverlapping() و بر اساس timezone اپ اجرا می‌شوند:

نام زمان شرط
backup-run-daily BACKUP_DAILY_AT backup:run --only-db --destination-path=<project>/Y/m/d
backup-hot-hourly هر ساعت بین BACKUP_HOT_FROM_HOUR و BACKUP_HOT_TO_HOUR فقط وقتی BACKUP_HOT_TABLES پر و BACKUP_HOT_ENABLED روشن باشد
backup-clean-daily BACKUP_CLEANUP_AT backup:clean
backup-monitor-heartbeat هر BACKUP_REPORT_EVERY_MINUTES دقیقه فقط وقتی BACKUP_MONITOR_URL تنظیم و مانیتور روشن باشد

مقدار نامعتبر هرگز scheduler را خراب نمی‌کند: ساعت بدفرمت به پیش‌فرض برمی‌گردد، ساعت‌های hot به بازه‌ی ۰ تا ۲۳ محدود می‌شوند و فاصله‌ی heartbeat به ۱ تا ۵۹ دقیقه. اگر BACKUP_HOT_FROM_HOUR بزرگ‌تر از BACKUP_HOT_TO_HOUR باشد، ورودی hot کلاً اجرا نمی‌شود (بازه‌ی شبانه‌روزی پشتیبانی نمی‌شود).

اگر زمان‌بندی را خودتان می‌خواهید (BACKUP_SCHEDULE_ENABLED=false):

// routes/console.php
use Illuminate\Support\Facades\Schedule;

Schedule::command('backup:run', ['--only-db'])->dailyAt('02:30')->withoutOverlapping();
Schedule::command('backup:clean')->dailyAt('04:00')->withoutOverlapping();
Schedule::command('backup:report')->everyFifteenMinutes();

وضعیت سلامت و heartbeat

وضعیت معنا
SUCCESS آخرین بکاپ موفق و تازه است
FAILED آخرین اجرا ناموفق بود (dump، zip یا pipeline)
STORAGE_ERROR دیسک در دسترس نیست یا آخرین اجرا در لایه‌ی storage شکست خورد
NEVER_RUN هیچ اجرایی ثبت نشده (یا جدول تاریخچه migrate نشده)
STALE آخرین موفقیت قدیمی‌تر از BACKUP_STALE_AFTER_HOURS است

اولویت: STORAGE_ERROR ← FAILED ← STALE ← NEVER_RUN ← SUCCESS. فقط اجراهای نوع backup در وضعیت اصلی حساب می‌شوند؛ اجراهای cleanup و hot آن را خراب نمی‌کنند.

اگر BACKUP_MONITOR_URL تنظیم باشد، پکیج heartbeat را هر چند دقیقه، بعد از هر رویداد پایانی بکاپ و بلافاصله بعد از هر شکست hot ارسال می‌کند:

POST {BACKUP_MONITOR_URL} با هدر Authorization: Bearer {BACKUP_MONITOR_TOKEN} و بدنه‌ی JSON:

{
  "project": "my-project",
  "environment": "production",
  "status": "SUCCESS",
  "detail": "Last backup completed successfully.",
  "checked_at": "2026-10-06T15:00:00+03:30",
  "last_run_at": "2026-10-06T02:30:04+03:30",
  "last_success_at": "2026-10-06T02:30:58+03:30",
  "last_backup_path": "my-project/2026/10/06/2026-10-06-02-30-00.zip",
  "last_backup_size_bytes": 12345678,
  "disk": "arvan-backup",
  "consecutive_failures": 0,
  "hot": {
    "status": "success",
    "last_run_at": "2026-10-06T15:00:03+03:30",
    "last_success_at": "2026-10-06T15:00:03+03:30",
    "last_path": "hot-my-project/2026/10/06/2026-10-06-15-00-03-hot.zip",
    "last_size_bytes": 54210,
    "consecutive_failures": 0
  },
  "meta": { "php": "8.4.0", "laravel": "13.0.0" }
}

برای دیدن payload بدون ارسال: php artisan backup:report --dry-run. کلاینت مانیتور هرگز exception پرتاب نمی‌کند؛ خرابی مانیتور فقط یک warning در لاگ backup است.

بکاپ ساعتی جدول‌های حیاتی (hot backup)

برای داده‌ی جایگزین‌ناپذیر (سفارش‌ها، کیف پول، کاربران، نقش‌ها و دسترسی‌ها و...)، فول‌دامپ شبانه RPO یک روزه دارد. Hot backup هر ساعت در بازه‌ی کاری فقط همین جدول‌ها را dump می‌کند؛ آرشیو بسیار کوچک است و RPO داده‌ی حیاتی به یک ساعت می‌رسد. این قابلیت تا وقتی جدول‌ها را تنظیم نکنید خاموش است:

BACKUP_HOT_TABLES=orders,wallet_transactions,users
hot-<BACKUP_PROJECT>/YYYY/MM/DD/<timestamp>-hot.zip     ← AES-256، روی همان دیسک
  • چرا hot-<project>/ در ریشه؟ Retention پکیج Spatie لیست allFiles(<project>) را prefix-match می‌کند. اگر آرشیو hot داخل <project>/ یا <project>-hot/ بود، keep-daily ممکن بود یک zip کوچک hot را به‌جای فول‌دامپ نگه دارد. پیشوند hot- این تداخل را می‌شکند.
  • Retention جدا: بعد از هر اجرای موفق، آرشیوهای قدیمی‌تر از BACKUP_HOT_KEEP_DAYS حذف می‌شوند.
  • ثبت: در backup_runs با type = 'hot'؛ وضعیت اصلی بکاپ را تغییر نمی‌دهد. وضعیت مستقل آن در بلوک hot heartbeat است: disabled | never_run | success | failed | storage_error | stale. وضعیت stale وابسته به بازه‌ی کاری است: داخل بازه، آخرین موفقیت باید کمتر از ۲ ساعت قبل باشد؛ بیرون بازه کافی است آخرین اسلات دیروز اجرا شده باشد.
  • هشدار: شکست در لاگ ثبت می‌شود، heartbeat فوری می‌رود و (اگر ایمیل فعال باشد) فقط در اولین شکستِ هر دنباله ایمیل می‌رسد؛ اولین موفقیت بعدی دنباله را ریست می‌کند.
  • اتصال: از اولین اتصال backup.source.databases (یا database.default) استفاده می‌شود و فایل داخل zip با نام db-dumps/<db>-hot.sql ذخیره می‌شود.
  • اگر BACKUP_ARCHIVE_PASSWORD تنظیم نباشد، آرشیو hot بدون رمز نوشته می‌شود و یک warning در لاگ می‌آید.

هشدار ایمیلی

ایمیل اختیاری است و با تنظیم BACKUP_NOTIFICATION_MAIL_TO روشن می‌شود. آن‌وقت اعلان‌های شکست Spatie (BackupHasFailed، CleanupHasFailed، UnhealthyBackupWasFound) و اعلان HotBackupFailed پکیج به آن آدرس می‌روند. بدون این متغیر، heartbeat تنها منبع حقیقت است. فرستنده از MAIL_FROM_ADDRESS و MAIL_FROM_NAME خوانده می‌شود.

صفحه‌ی تست storage

GET /backup-storage-test سه پروب روی دیسک بکاپ اجرا می‌کند و خطای دقیقِ sanitize‌شده را نشان می‌دهد:

  1. کامل‌بودن تنظیمات دیسک (برای دیسک s3: key، secret، endpoint و bucket)
  2. files() روی پوشه‌ی پروژه (احراز هویت و خواندن)
  3. round-trip put / get / delete (دسترسی نوشتن)

خروجی HTML است؛ با Accept: application/json پاسخ JSON می‌دهد. در موفقیت 200 و در شکست 503 برمی‌گرداند.

  • Local: بدون توکن باز است.
  • غیر Local: فقط با ?token= برابر BACKUP_DEBUG_TOKEN (در نبود آن BACKUP_MONITOR_TOKEN)؛ در غیر این صورت 404. اگر هیچ توکنی تنظیم نشده باشد، همیشه 404.
  • access key و secret هرگز چاپ نمی‌شوند؛ فقط set (N chars) یا MISSING دیده می‌شود. برای دیسک‌های غیر s3 فقط وجود تعریف دیسک بررسی می‌شود.
curl -H "Accept: application/json" "https://example.com/backup-storage-test?token=$BACKUP_DEBUG_TOKEN"

مسیر، middleware و روشن/خاموش‌بودن route در config/backup-kit.php است:

'routes' => [
    'storage_test' => [
        'enabled' => env('BACKUP_STORAGE_TEST_ENABLED', true),
        'path' => env('BACKUP_STORAGE_TEST_PATH', 'backup-storage-test'),
        'middleware' => ['web'],
    ],
],

نام route برابر backup-kit.storage-test است.

Retention

Cleanup با DefaultStrategy پکیج Spatie انجام می‌شود و جدیدترین بکاپ همیشه نگه داشته می‌شود. بازه‌ها با متغیرهای BACKUP_KEEP_* و BACKUP_MAX_STORAGE_MB (جدول بالا) تنظیم می‌شوند. Cleanup روی پوشه‌ی پروژه recursive کار می‌کند، پس فایل‌های داخل Y/m/d و pre-deploy/ هم پوشش داده می‌شوند. آرشیوهای hot retention جدا دارند (BACKUP_HOT_KEEP_DAYS).

Restore

جدول‌های DB_DUMP_EXCLUDE_TABLES در dump نیستند و بعد از restore ساخته نمی‌شوند؛ باید با migration یا دستی دوباره ساخته شوند.

۱. آرشیو را پیدا و دانلود کنید:

php artisan backup:list
aws --endpoint-url "$ARVAN_ENDPOINT" s3 ls "s3://$ARVAN_BUCKET/my-project/" --recursive
aws --endpoint-url "$ARVAN_ENDPOINT" s3 cp \
  "s3://$ARVAN_BUCKET/my-project/2026/10/06/2026-10-06-02-30-00.zip" backup.zip

۲. رمزگشایی: unzip معمولی AES-256 را نمی‌فهمد؛ از 7-Zip (لینوکس: 7zz یا p7zip) یا PHP استفاده کنید:

7z x -p"$BACKUP_ARCHIVE_PASSWORD" backup.zip -o./restore-tmp
$zip = new ZipArchive;
$zip->open('backup.zip', ZipArchive::RDONLY);
$zip->setPassword(getenv('BACKUP_ARCHIVE_PASSWORD'));
$zip->extractTo('./restore-tmp');
$zip->close();

۳. دیتابیس را روی یک دیتابیس جدید یا staging بازیابی کنید، نه مستقیم روی production:

mysql -h <host> -u <user> -p <database> < ./restore-tmp/db-dumps/mysql-<database>.sql

۴. بعد از restore، تعداد ردیف‌های جدول‌های کلیدی را بررسی کنید و مطمئن شوید اپ بالا می‌آید (php artisan about).

Restore از hot backup: ابتدا فول‌دامپ شب را restore کنید، سپس آخرین آرشیو hot را با همان روش باز کنید و db-dumps/<db>-hot.sql را import کنید. این کار روی جدول‌های موجود overwrite می‌کند (mysqldump به‌صورت پیش‌فرض DROP TABLE IF EXISTS دارد، پس schema هم درست می‌شود).

بکاپ پیش از دیپلوی (Deployer)

پکیج هیچ کدی برای Deployer ندارد و deployer/deployer وابستگی آن نیست. اگر دیپلوی شما با Deployer انجام می‌شود، این تسک قبل از migration یک dump تازه از release فعلی (current، یعنی کد و .env که می‌دانید کار می‌کند) روی باکت می‌گذارد تا یک migration خراب هرگز بدون snapshot اجرا نشود:

<?php

namespace Deployer;

desc('Pre-deploy database backup (deploy aborts on failure)');
task('backup:pre-deploy', function () {
    if (getenv('DEPLOY_SKIP_BACKUP')) {
        writeln('<comment>DEPLOY_SKIP_BACKUP set, skipping pre-deploy backup</comment>');

        return;
    }

    // --destination-path replaces the backup name entirely, so the project
    // prefix must be part of it: {{application}} has to equal BACKUP_PROJECT.
    run('cd {{current_path}} && {{bin/php}} artisan backup:run --only-db --destination-path="{{application}}/pre-deploy/$(date +%Y/%m/%d)"');
});

before('artisan:migrate', 'backup:pre-deploy');
  • آرشیو در <project>/pre-deploy/YYYY/MM/DD/ می‌نشیند و زیر retention عادی است.
  • اگر بکاپ شکست بخورد، دستور run خطا می‌دهد و دیپلوی متوقف می‌شود.
  • دور زدن فقط برای اورژانس: DEPLOY_SKIP_BACKUP=1. در اولین دیپلوی هم release current وجود ندارد و باید از همین متغیر استفاده کنید.

امنیت

  • credentialها فقط از .env خوانده می‌شوند و هیچ‌جا چاپ نمی‌شوند.
  • پیام خطا قبل از ذخیره در backup_runs.error_summary یا لاگ: فقط خط اول، کوتاه‌شده، با حذف password= / token= / secret= / key= و mask کردن secretهای جاری config (دیتابیس، S3، رمز آرشیو، توکن مانیتور؛ حداقل ۸ کاراکتر).
  • رمز دیتابیس از طریق فایل defaults موقت به mysqldump می‌رسد و در argv یا لیست پردازه‌ها دیده نمی‌شود.
  • رمز آرشیو را در یک secret store نگه دارید. هر آرشیو با رمزِ زمان ساختش رمز می‌شود؛ هنگام تغییر رمز، رمز قبلی را تا حذف شدن آرشیوهای قدیمی نگه دارید، وگرنه آن‌ها قابل بازیابی نیستند.
  • توکن صفحه‌ی تست storage در URL می‌آید و ممکن است در access logها بماند؛ آن را دوره‌ای عوض کنید یا اگر لازم نیست BACKUP_STORAGE_TEST_ENABLED=false بگذارید.
  • کلید S3 را فقط به باکت (و در صورت امکان پیشوند) بکاپ محدود کنید؛ دسترسی list، put، get و delete کافی است (delete برای cleanup لازم است).
  • آدرس مانیتور را فقط HTTPS بدهید؛ توکن به‌صورت Bearer ارسال می‌شود.

عیب‌یابی

علامت بررسی
STORAGE_ERROR صفحه‌ی /backup-storage-test را باز کنید (در production با ?token=)؛ خطای دقیق S3 را نشان می‌دهد
NoSuchKey ARVAN_ENDPOINT نباید نام باکت را داخل host داشته باشد (https://s3.ir-central1.arvanstorage.ir درست است). با path-style، نام باکت در host یعنی «double bucket»
Disk [...] does not have a configured driver دیسک BACKUP_DISK تعریف نشده: ARVAN_* را تنظیم کنید یا دیسک را در config/filesystems.php بسازید
خطای mysqldump DB_DUMP_BINARY_PATH باید پوشه‌ی حاوی باینری باشد (مثل /usr/bin)، نه خود باینری
NEVER_RUN Scheduler (schedule:run) فعال است؟ migration جدول backup_runs اجرا شده؟
STALE آخرین خطا را ببینید: SELECT * FROM backup_runs ORDER BY id DESC LIMIT 5 و storage/logs/backup-*.log
health check ArchiveIsEncrypted BACKUP_ARCHIVE_PASSWORD تنظیم نشده یا encryption در config/backup.php دیگر aes256 نیست
heartbeat نمی‌رسد backup:report --dry-run payload را نشان می‌دهد؛ BACKUP_MONITOR_URL و BACKUP_MONITOR_ENABLED را بررسی و لاگ backup را نگاه کنید

مهاجرت از پیاده‌سازی داخلی پروژه

اگر پروژه‌ای همین pipeline را به‌صورت کد داخلی دارد، با نصب پکیج این جایگزینی‌ها لازم است:

قبلاً (در اپ) با پکیج
بلوک‌های monitoring، schedule و hot در config/backup.php همان کلیدها زیر config/backup-kit.php
backup.monitoring.disk backup-kit.disk (BACKUP_DISK)
دیسک arvan-backup، کانال backup و dump در filesystems، logging و database خودکار، اگر خودتان تعریف نکرده باشید
ورودی‌های Schedule در routes/console.php خودکار؛ با BACKUP_SCHEDULE_ENABLED=false خاموش می‌شود
route و controller صفحه‌ی تست storage backup-kit.routes.storage_test
فهرست پیش‌فرض جدول‌های hot در config باید BACKUP_HOT_TABLES را صریح بدهید (پیش‌فرض خالی یعنی خاموش)

Migration پکیج idempotent است: اگر جدول backup_runs از قبل وجود داشته باشد، دست‌نخورده می‌ماند. کد داخلی (provider، commandها، listenerها، controller و route) را بعد از اطمینان از کارکرد پکیج حذف کنید.

توسعه و تست

composer install
composer test    # Pest روی Orchestra Testbench؛ نیازی به S3 یا شبکه ندارد
composer lint    # Laravel Pint در حالت --test

تست‌ها دیسک و HTTP را با Storage::fake و Http::fake شبیه‌سازی می‌کنند.

مجوز

MIT. متن کامل در فایل LICENSE.