بلاگ

Laravel FTP Deployer

دپلوی برنامه‌های لاراول روی هاست‌های اشتراکی یا سی‌پنل (cPanel) بدون دسترسی SSH و فقط با FTP.

این پکیج به صورت پیش‌فرض آرشیوهای ZIP را از طریق FTP آپلود می‌کند، با استفاده از مانیفست راه دور (remote manifest) از آپلود مجدد پوشه تغییرنکرده‌ی vendor/ خودداری می‌کند، و سپس دستورات نگهداری لاراول (maintenance commands) را در مقصد از طریق یک رانر HTTP موقت و توکنایز شده اجرا می‌کند. همچنین می‌توانید حالت آرشیو (archive mode) را غیرفعال کنید تا فرایند دپلوی به آپلود بازگشتی (recursive) فایل‌های تغییریافته با FTP تغییر پیدا کند.

از این ابزار زمانی استفاده کنید که هاست اشتراکی شما دسترسی FTP/FTPS به شما می‌دهد اما دسترسی SSH، کامپوزر (Composer) در سرور مقصد، یا روش امنی برای اجرای دستورات نگهداری لاراول بعد از آپلود در اختیارتان نمی‌گذارد.

این ابزار چه مشکلی را حل می‌کند؟

پس از بروز چندین آسیب‌پذیری ارتقای امتیاز (Privilege Escalation) مانند CVE-2021-4034 موسوم به «PwnKit» و CVE-2021-3156 موسوم به «Baron Samedit» که امکان دسترسی روت (root) از یوزر معمولی را نشان دادند، بسیاری از شرکت‌های میزبانی دسترسی SSH را برای دپلوی محدود یا به طور کامل غیرفعال کردند. توصیه‌های امن‌سازی OpenSSH و بنچمارک‌های CIS نیز در همین مسیر گام برداشتند.

در عین حال، بسیاری از هاست‌ها فاقد ابزار دپلوی داخلی بودند؛ به‌ویژه در کنترل‌پنل‌های هاست‌های اشتراکی مانند دایرکت‌ادمین (DirectAdmin) که وب‌سرویس‌ها و APIهای اتوماسیون محدودی دارند.

این خلاء، نیاز به یک مسیر دپلوی مینیمال و امن که وابستگی به دسترسی کامل SSH نداشته باشد را ایجاد کرد.

گیت‌هاب: https://github.com/inja-online/ftp-deployer

پیش‌نیازها

  • PHP 8.1+
  • لاراول ۱۰، ۱۱، ۱۲ یا ۱۳
  • افزونه‌ی ftp در PHP
  • افزونه‌ی zip در PHP روی سیستم محلی و سرور مقصد (هنگام فعال بودن حالت آرشیو)
  • دسترسی FTP/FTPS به هاست
  • آدرس HTTPS عمومی برای برنامه‌ی دپلوی شده
  • بیلد فایل‌های فرانت‌اند از قبل روی سیستم محلی یا CI انجام شده باشد

نصب

از طریق Packagist

composer require inja-online/ftp-deployer
php artisan vendor:publish --tag=ftp-deployer-config

از دایرکتوری محلی (نصب محلی)

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

۱. ریپازیتوری را دانلود یا کلون کرده و فایل‌ها را در یک پوشه محلی درون پروژه لاراول خود قرار دهید، برای مثال: packages/ftp-deployer.

۲. یک ریپازیتوری از نوع path که به پوشه محلی اشاره می‌کند را در فایل composer.json پروژه خود اضافه کنید:

{
  "repositories": [
    {
      "type": "path",
      "url": "packages/ftp-deployer",
      "options": {
        "symlink": true
      }
    }
  ]
}

(نکته: در صورتی که می‌خواهید کامپوزر فایل‌های پکیج را کپی یا Mirror کند و سیم‌لینک نسازد، گزینه "symlink": false را قرار دهید. این کار برای محیط‌هایی مثل داکر یا هاست‌های اشتراکی که ممکن است از سیم‌لینک پشتیبانی نکنند مفید است).

۳. دستور require را اجرا کنید:

composer require inja-online/ftp-deployer:dev-main
php artisan vendor:publish --tag=ftp-deployer-config

از ریپازیتوری سفارشی گیت‌هاب

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

ریپازیتوری را به composer.json پروژه لاراول خود اضافه کنید:

{
  "repositories": [
    {
      "type": "vcs",
      "url": "https://github.com/inja-online/ftp-deployer"
    }
  ]
}

سپس پکیج را require کنید:

composer require inja-online/ftp-deployer:dev-main
php artisan vendor:publish --tag=ftp-deployer-config

برای یک فورک یا ریپازیتوری خصوصی، URL را تغییر دهید:

{
  "repositories": [
    {
      "type": "vcs",
      "url": "https://github.com/YOUR-USER/YOUR-REPO"
    }
  ]
}

سپس از شاخه (branch) مورد نظر نصب کنید:

composer require inja-online/ftp-deployer:dev-main

پیکربندی

فایل پیکربندی منتشر شده:

config/ftp-deployer.php

مقادیر دپلوی را به فایل .env محلی یا محیط CI خود اضافه کنید:

FTP_DEPLOYER_PROFILE=production
FTP_DEPLOYER_HOST=ftp.example.com
FTP_DEPLOYER_USERNAME=ftp-user
FTP_DEPLOYER_PASSWORD=secret
FTP_DEPLOYER_PORT=21
FTP_DEPLOYER_SSL=false
FTP_DEPLOYER_PASSIVE=true
FTP_DEPLOYER_FTP_ROOT=/public_html
FTP_DEPLOYER_APP_URL=https://laravelapp.inja.online

# دپلوی آرشیوی به صورت پیش‌فرض فعال است.
# این مقدار باید مسیر فیزیکی فایل‌سیستم در PHP باشد که با FTP_DEPLOYER_FTP_ROOT همخوانی دارد.
FTP_DEPLOYER_FILESYSTEM_ROOT=/home/your-user/public_html/laravelapp.inja.online
FTP_DEPLOYER_ARCHIVE_ENABLED=true

# لایه‌بندی پیش‌فرض
FTP_DEPLOYER_MODE=simple
FTP_DEPLOYER_APP_ROOT=app
FTP_DEPLOYER_PUBLIC_ROOT=app/public

ساختار پیش‌فرض پوشه‌ها در هاست مقصد:

{ftp_root}/app/          ریشه لاراول: شامل فایل‌ها و پوشه‌های artisan, vendor, .env, bootstrap, storage
{ftp_root}/app/public/   ریشه عمومی (public root) و محل قرارگیری رانر موقت
{ftp_root}/.ftp-deployer فایل‌های مانیفست موقت و فایل‌های آرشیو آپلود شده

اگر ریشه دامنه شما FTP_DEPLOYER_FTP_ROOT=/public_html/laravelapp.inja.online است، مقدار FTP_DEPLOYER_FILESYSTEM_ROOT را برابر با مسیر مطلق فیزیکی هم‌تراز با آن در هاست قرار دهید، برای مثال /home/\<cpanel-user\>/public_html/laravelapp.inja.online.

فایل .env در سرور مقصد باید از قبل وجود داشته باشد و شامل مقدار APP_KEY باشد. این پکیج فایل .env محلی شما را آپلود نمی‌کند.

دپلوی

ابتدا برنامه خود را به صورت محلی یا در محیط CI بیلد کنید:

composer install --no-dev --prefer-dist --optimize-autoloader
npm ci
npm run build

برای پروژه‌های Bun/React/Vite، بیلد را با Bun انجام دهید:

bun install --frozen-lockfile
bun run build

فرانت‌اند ندارید؟ می‌توانید بخش Node/Bun را به طور کامل نادیده بگیرید؛ اگر فایل package.json در پروژه نباشد، تشخیص فرانت‌ند به طور خودکار انجام نمی‌شود.

اجرای دپلوی:

php artisan ftp-deploy production

برای اتوماسیون یا ابزارهای هوش مصنوعی (AI Agents)، از خروجی ساختاریافته‌ی JSON استفاده کنید:

php artisan ftp-deploy production --format=agent

دستورات پیش‌فرض در سرور مقصد:

migrate --force
optimize:clear
optimize
storage:link

می‌توانید آن‌ها را در فایل config/ftp-deployer.php و تحت کلید remote_commands تغییر دهید:

'remote_commands' => [
    'migrate --force',
    'app:setup:cache',
    'optimize:clear',
    'optimize',
    ['command' => 'storage:link', 'ignore_failures' => true],
],

برای مواردی مثل بیلد با Bun/React، برنامه‌های بدون فرانت‌اند، دستورات شخصی‌سازی‌شده، صف‌ها، کش‌ها و CI، به مستندات کتابچه و مثال‌ها مراجعه کنید. برای شخصی‌سازی و بازنویسی متدها، توسعه و شخصی‌سازی را ببینید.

بررسی اتصال

قبل از اجرای دپلوی، می‌توانید تنظیمات اتصال FTP خود (میزبان، نام کاربری، رمز عبور، پورت، SSL و حالت Passive) را با دستور زیر بررسی کنید:

php artisan ftp-deploy:check production

برای اتوماسیون یا ابزارهای هوش مصنوعی (AI Agents)، از خروجی ساختاریافته‌ی JSON استفاده کنید:

php artisan ftp-deploy:check production --format=agent

مهاجرت به حالت نسخه‌بندی‌شده (Versioned Mode)

اگر از قبل پروفایلی با حالت ساده (simple) دارید و می‌خواهید آن را به حالت نسخه‌بندی‌شده (versioned) ارتقا دهید، می‌توانید از دستور مهاجرت تعاملی زیر استفاده کنید:

php artisan ftp-deploy:migrate production

آرگومان‌ها و گزینه‌ها:

  • profile (اختیاری): نام پروفایلی که می‌خواهید مهاجرت دهید (به طور پیش‌فرض production).
  • --write: به‌طور خودکار متغیرهای محیطی مورد نیاز را در فایل .env محلی پروژه‌تان بازنویسی یا اضافه می‌کند (در صورتی که فایل قابل نوشتن باشد).
  • --format=agent: به‌جای اعلان‌های تعاملی، یک خروجی ساختاریافته‌ی JSON برمی‌گرداند که برای اتوماسیون عالی است.

تغییرات در سرور مقصد

توجه داشته باشید که دستور مهاجرت فقط پیکربندی محلی شما را به‌روزرسانی می‌کند و تغییری روی فایل .env سرور مقصد ایجاد نمی‌کند. برای تکمیل این انتقال: ۱. به سرور FTP خود متصل شوید. ۲. فایل .env مقصد را از مسیر {ftp_root}/{app_root}/.env به {ftp_root}/{shared_root}/.env منتقل کنید. ۳. پوشه storage/ مقصد را به مسیر {ftp_root}/{shared_root}/storage/ منتقل کنید. ۴. مطمئن شوید که پوشه storage/ در سرور مقصد همچنان قابل نوشتن باقی بماند (chmod -R 775 یا معادل آن). ۵. دستور php artisan ftp-deploy <profile> را اجرا کنید تا بوت‌لودر دپلوی شده و به ساختار ریلیز جدید لینک داده شود.

حالت دپلوی آرشیوی (Archive deploy mode)

حالت آرشیو به طور پیش‌فرض فعال است. این حالت در هر دپلوی یک فایل ZIP از برنامه را آپلود می‌کند، و پوشه vendor را به صورت ZIP تنها زمانی آپلود می‌کند که فایل‌های composer.json یا composer.lock تغییر کرده باشند. فایل‌های ZIP موقت از نام‌های تصادفی در پوشه .ftp-deployer/archives/ استفاده می‌کنند، با فایل‌های .htaccess محافظت می‌شوند و پس از استخراج حذف می‌شوند. فایل‌های دیتابیس SQLite در پوشه database/ از فایل آرشیو برنامه حذف (Exclude) می‌شوند. رمزگذاری فایل‌های ZIP در نسخه ۱ پشتیبانی نمی‌شود.

پس از استخراج موفقیت‌آمیز، فایل‌های قدیمی و بدون استفاده از مانیفست قبلی حذف می‌شوند و مانیفست جدید قبل از اجرای دستورات نهایی سرور ذخیره می‌گردد. در صورت ناموفق بودن استخراج، فایل‌های مانیفست و فایل‌های قدیمی بدون تغییر باقی می‌مانند. اگر هاست شما در طول استخراج با خطای زمان‌انتظار (Timeout) مواجه شود یا آدرس‌دهی مطلق فایل‌سیستم هاست در دسترس نباشد، مقدار FTP_DEPLOYER_ARCHIVE_ENABLED=false را تنظیم کنید تا از آپلود معمولی و بازگشتی FTP استفاده شود.

حالت ساده (Simple mode) فایل‌ها را مستقیماً روی فایل‌های فعال استخراج می‌کند و تلاش می‌کند تا به بهترین شکل دپلوی را انجام دهد. برای داشتن انتشار امن‌تر و بدون قطعی، از حالت نسخه‌بندی‌شده (Versioned mode) استفاده کنید.

حالت دپلوی نسخه‌بندی‌شده (Versioned deploy mode)

حالت ساده فایل‌ها را در مسیرهای ثابت app/public آپلود می‌کند. در مقابل، حالت نسخه‌بندی‌شده کدهای برنامه را در پوشه‌های مجزای مربوط به هر ریلیز آپلود کرده و پس از موفقیت دپلوی، اشاره‌گر نسخه جاری (current-release pointer) را به‌روزرسانی می‌کند.

FTP_DEPLOYER_MODE=versioned
FTP_DEPLOYER_RELEASE_ROOT=../app/releases
FTP_DEPLOYER_SHARED_ROOT=../app/shared
FTP_DEPLOYER_CURRENT_PATH=../app/current

یکپارچه‌سازی با ابزارهای هوش مصنوعی (AI Agents)

این مخزن شامل یک مهارت پیش‌فرض هوش مصنوعی (AI Agent Skill) به نام ftp-deployer است که به دستیارهای خودمختار برنامه‌نویسی (مانند گوگل Antigravity یا کلاود کد) کمک می‌کند تا دپلوی‌ها را به طور ایمن اجرا، پیکربندی و عیب‌یابی کنند.

برای نصب سراسری این مهارت روی دستیار خود:

npx skills add inja-online/ftp-deployer --skill ftp-deployer

برای اطلاعات بیشتر در مورد روش یکپارچه‌سازی، پیکربندی ریشه‌های سفارشی و پرامپت‌های نمونه برای بات‌ها، مستندات یکپارچه‌سازی هوش مصنوعی را بررسی کنید.

مستندات

امنیت

  • نام فایل و توکن رانر در هر بار دپلوی به صورت تصادفی تولید می‌شوند.
  • رانر فقط ورودی JSON پذیرفته و خروجی JSON برمی‌گرداند.
  • رانر بلافاصله پس از موفقیت یا شکست دپلوی حذف می‌شود.
  • فایل .env به صورت پیش‌فرض از آپلودها مستثنی شده است.
  • از پروتکل امن HTTPS برای FTP_DEPLOYER_APP_URL استفاده کنید.

گردش کار انتشار دستی (Manual release workflow)

مخزن شامل یک ورک‌فلوی دستی گیت‌هاب (GitHub Actions) برای انتشار نسخه‌های نشانه‌گذاری‌شده (tagged package releases) است:

۱. به گیت‌هاب رفته و وارد بخش Actions -> manual-release شوید. ۲. روی Run workflow کلیک کنید. ۳. نسخه را به شکل v1.2.3 وارد کنید. ۴. در صورت تمایل، آن را به عنوان نسخه پیش‌انتشار (prerelease) علامت‌گذاری کرده یا یادداشت‌های انتشار را بنویسید.

این گردش کار متادیتای کامپوزر را اعتبارسنجی می‌کند، تست‌ها، PHPStan و بررسی استایل کد (lint dry-run) را اجرا می‌کند، یک تگ نشانه‌گذاری‌شده‌ی گیت می‌سازد و سپس ریلیز گیت‌هاب را ایجاد می‌کند. پس از آن Packagist می‌تواند این تگ را مستقیماً از گیت‌هاب دریافت کند.

دستورات مفید

composer test
composer stan
composer lint-test

لایسنس

نسخه AGPL-3.0 یا بالاتر. فایل LICENSE.md را بررسی کنید.