# دیپلوی ClinicPro (Symfony) روی لیارا با پلتفرم PHP (بدون داکر) ## پروژه `clinicpro` (Backend Symfony 7.4 + پنل ادمین React 19). راهنما + تسک پیاده‌سازی برای دیپلوی روی **پلتفرم PHP لیارا** (buildpack بومی؛ لیارا خودش از سورس با `composer install` build می‌کند — بدون Dockerfile). > منابع: مستندات لیارا — paas/php/how-tos (set-envs، use-disk، set-logs، customize-php-ini، customize-htaccess، use-queues، set-http-security-headers، use-hooks، set-cron-job) و paas/laravel (quick-start، set-envs، set-cron-job، choose-version، manage-logs). --- ## زمینه تا الان دو مسیر دیپلوی برای این پروژه آماده شده: - **داکر/Coolify:** `Dockerfile` + `docker-compose.yml` - **داکر/لیارا:** `liara.json` (platform docker) + `liara-compose.yaml` این پرامپت مسیر **سومی** را می‌سازد: **پلتفرم PHP لیارا** (نوع برنامه = `php` هنگام «ساخت برنامه‌ی جدید»). اینجا داکری در کار نیست؛ لیارا کد را می‌گیرد، `composer install` می‌زند و روی Apache + PHP-FPM سرو می‌کند. تفاوت‌های کلیدی پلتفرم PHP که معماری را تعیین می‌کنند (از مستندات): - **Hookها فایل‌های شل‌اند، نه JSON:** `liara_pre_build.sh` (قبل build، **بدون** دسترسی به env، مناسب apt) و `liara_pre_start.sh` (قبل start، **با** env، مناسب migration). - **Worker با `supervisor.conf`:** فایل `supervisor.conf` در ریشه؛ هر `[program]` یک دستور دلخواه (`php bin/console messenger:consume ...`) را زنده نگه می‌دارد. `$ROOT` = ریشهٔ اپ، `user=www-data`. - **php.ini با `liara_php.ini`** در ریشه. - **سرو با Apache + `.htaccess`؛ documentRoot باید `public/` شود.** - **دیسک‌ها** در `liara.json` با مسیر **absolute**. - **نسخهٔ PHP:** 7.2 تا 8.4 (پیش‌فرض 7.4) — پروژه `php: >=8.2` می‌خواهد، پس باید 8.2+ انتخاب شود. --- ## مشکل / هدف اجرای کامل Symfony روی پلتفرم PHP لیارا، شامل: 1. سرو front-controller سیمفونی از `public/` روی Apache. 2. اجرای migration و تولید کلید JWT در زمان استقرار. 3. اجرای دو مصرف‌کنندهٔ Messenger (`async` و `scheduler_default`) به‌صورت پایدار. 4. آپلودِ asset‌های build‌شدهٔ React (که `composer install` آن‌ها را نمی‌سازد). 5. اتصال به MariaDB و Redis مدیریت‌شدهٔ لیارا. --- ## فایل‌های مرتبط | فایل | نقش | وضعیت | |------|-----|------| | `liara.json` | پیکربندی دیپلوی | **بازنویسی برای platform php** (فعلاً platform docker است) | | `public/.htaccess` | rewrite سیمفونی برای Apache | **وجود ندارد — ساخته شود** (apache-pack نصب نیست) | | `liara_pre_start.sh` | hook قبل start (migration/JWT) | **جدید** | | `supervisor.conf` | اجرای دو worker | **جدید** | | `liara_php.ini` | تنظیم php.ini | **جدید** | | `.liaraignore` | کنترل فایل‌های آپلودی | **جدید** (تا `public/build` آپلود شود) | | `composer.json` | اسکریپت‌های نصب | بدون تغییر (با احتیاط؛ پایین) | > ⚠️ **تداخل liara.json:** الان `liara.json` برای platform docker است. پلتفرم PHP به محتوای متفاوتی نیاز دارد. فقط **یک** مسیر را همزمان نگه دار. اگر PHP را انتخاب کردی، محتوای docker را با نسخهٔ PHP زیر جایگزین کن (یا برنچ جدا). --- ## وضعیت فعلی (کد واقعی) `composer.json` هنگام نصب این اسکریپت‌ها را اجرا می‌کند (روی build لیارا هم اجرا می‌شوند): ```json "auto-scripts": { "cache:clear": "symfony-cmd", "assets:install %PUBLIC_DIR%": "symfony-cmd" }, "post-install-cmd": ["@auto-scripts"] ``` `.gitignore` این‌ها را نادیده می‌گیرد (پس به‌صورت پیش‌فرض آپلود **نمی‌شوند**): ``` /public/build/ # ← خروجی yarn build؛ باید آپلود شود /config/jwt/*.pem # ← روی دیسک ساخته می‌شود /var/ .env ``` `public/.htaccess` وجود ندارد؛ extensionهای موردنیاز Symfony: `pdo_mysql`, `intl`, `opcache`, و برای Messenger روی Redis: `redis` (phpredis). --- ## وظایف ### ۱. ساخت `liara.json` برای پلتفرم PHP محتوای فعلی (platform docker) را با این جایگزین کن: ```json { "app": "clinicpro-api", "platform": "php", "port": 80, "php": { "version": "8.2" }, "documentRoot": "public", "disks": [ { "name": "jwt", "mountTo": "/var/www/config/jwt" }, { "name": "uploads", "mountTo": "/var/www/public/uploads" }, { "name": "var-uploads", "mountTo": "/var/www/var/uploads" } ] } ``` نکات: - `documentRoot: "public"` تا Apache front-controller سیمفونی را سرو کند. - مسیر mount دیسک‌ها **absolute** است؛ ریشهٔ اپ روی پلتفرم PHP معمولاً `/var/www` است — اگر در کنسول/لاگ مسیر دیگری دیدی، اصلاح کن. - کلید `php.version` و `documentRoot` را اگر کنسول نپذیرفت، از تنظیمات کنسول (Platform Settings) ست کن — مستند نسخه‌گزینی را از طریق کنسول هم ممکن می‌داند. - سه دیسک `jwt` / `uploads` / `var-uploads` را پیش از دیپلوی در کنسول بساز. ### ۲. ساخت `public/.htaccess` (rewrite سیمفونی) چون `symfony/apache-pack` نصب نیست، یا نصبش کن: ```bash ddev exec composer require symfony/apache-pack ``` (این به‌صورت تعاملی `public/.htaccess` استاندارد سیمفونی را می‌سازد) — یا فایل را دستی بساز: ```apache RewriteEngine On RewriteCond %{REQUEST_URI}::$0 ^(/.+)/(.*)::\2$ RewriteRule .* - [E=BASE:%1] RewriteCond %{HTTP:Authorization} .+ RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0] RewriteCond %{ENV:REDIRECT_STATUS} ="" RewriteRule ^index\.php(?:/(.*)|$) %{ENV:BASE}/$1 [R=301,L] RewriteCond %{REQUEST_FILENAME} -f RewriteRule ^ - [L] RewriteRule ^ %{ENV:BASE}/index.php [L] RedirectMatch 307 ^/$ /index.php/ ``` ### ۳. ساخت `supervisor.conf` (دو worker مسنجر) روی پلتفرم PHP، خود لیارا Apache/PHP-FPM را اجرا می‌کند؛ `supervisor.conf` **فقط** workerها را اضافه می‌کند (نه وب). در ریشه بساز: ```ini [program:worker-async] process_name=%(program_name)s_%(process_num)02d command=cd $ROOT && php bin/console messenger:consume async --time-limit=3600 --memory-limit=128M -v autostart=true autorestart=true stopasgroup=true killasgroup=true numprocs=1 user=www-data redirect_stderr=true stdout_logfile=/tmp/worker-async.log [program:worker-scheduler] process_name=%(program_name)s_%(process_num)02d command=cd $ROOT && php bin/console messenger:consume scheduler_default --time-limit=3600 -v autostart=true autorestart=true stopasgroup=true killasgroup=true numprocs=1 user=www-data redirect_stderr=true stdout_logfile=/tmp/worker-scheduler.log ``` > جایگزینِ cron: اگر نخواهی scheduler به‌صورت worker بماند، می‌توانی همان کار را با Cron Job کنسول (`* * * * *` → دستوری که یک پیام scheduler را مصرف کند) انجام دهی. ولی worker async (SMS) حتماً باید در `supervisor.conf` بماند. ### ۴. ساخت `liara_pre_start.sh` (migration + JWT + cache) در ریشه، با env در دسترس: ```bash #!/bin/sh set -e echo "pre-start: waiting for database..." i=0 until php bin/console doctrine:query:sql "SELECT 1" >/dev/null 2>&1; do i=$((i + 1)) [ "$i" -ge 30 ] && { echo "DB not reachable after 60s" >&2; exit 1; } sleep 2 done # کلید JWT روی دیسک ماندگار jwt php bin/console lexik:jwt:generate-keypair --skip-if-exists --no-interaction php bin/console cache:clear --no-warmup php bin/console cache:warmup php bin/console doctrine:migrations:migrate --all-or-nothing --no-interaction ``` فایل را اجرایی کن: `chmod +x liara_pre_start.sh`. ### ۵. ساخت `liara_php.ini` برای پشتیبانی از آپلود (سقف اپ ۵MB ولی هدر/چندبخشی بزرگ‌تر) و زمان اجرا: ```ini memory_limit = 256M upload_max_filesize = 16M post_max_size = 24M max_execution_time = 120 file_uploads = On ``` ### ۶. ساخت `.liaraignore` تا asset‌های build آپلود شوند `.gitignore` پوشهٔ `/public/build/` را نادیده می‌گیرد و لیارا به‌صورت پیش‌فرض از gitignore پیروی می‌کند → asset‌ها آپلود نمی‌شوند. با ساخت `.liaraignore` کنترل را به‌دست بگیر (فقط واقعاً غیرلازم‌ها را کنار بگذار، `public/build` را **نگه دار**): ``` .git .ddev .claude .github .vscode node_modules tests var/cache var/log docs graphify-out *.sql *.sql.gz .DS_Store ``` پیش از هر دیپلوی، asset‌ها را **محلی** build کن (لیارا `yarn build` نمی‌زند): ```bash ddev exec yarn install --frozen-lockfile ddev exec yarn build # خروجی در public/build ``` ### ۷. دیتابیس و Redis مدیریت‌شده + متغیرهای محیطی مثل مسیر داکر: 1. در کنسول، **MariaDB 11.8** و **Redis** بساز، شبکهٔ خصوصی فعال، برنامه را به همان شبکه وصل کن. 2. envها را با `liara env set ... --app clinicpro-api` یا تب Environment ست کن (مرجع کامل: `.env.liara.example`). حداقل: - `APP_ENV=prod`, `APP_DEBUG=0` - `APP_SECRET`, `JWT_PASSPHRASE` (ثابت) - `DATABASE_URL` (هاست خصوصی MariaDB، `serverVersion=mariadb-11.8.0`) - `REDIS_URL`, `MESSENGER_TRANSPORT_DSN` (هاست خصوصی Redis) - `JWT_SECRET_KEY`/`JWT_PUBLIC_KEY` (مسیر `config/jwt/*.pem`) - `APP_BASE_URL`, `DEFAULT_URI`, `TRUSTED_PROXIES` - `ALLOWED_FRONTEND_HOSTS`, `CORS_ALLOW_ORIGIN` (خروجی `php docker/gen-cors-env.php`) - `API_IR_BASE_URL`, `API_IR_TOKEN` 3. **مهم:** `APP_ENV=prod` باید موقع **build** هم موجود باشد، چون `composer install` اسکریپت `cache:clear` را اجرا می‌کند. ### ۸. استقرار و تأیید ```bash liara deploy # از ریشهٔ clinicpro، با liara.json platform php ``` سپس: - `GET https:///health` → 200 - لاگ: در کنسول/`liara logs` هر دو worker (`worker-async`, `worker-scheduler`) بالا باشند - ورود `/admin` و تست یک endpoint --- ## نکات مهم - **ext-redis ریسک اصلی است:** اگر پلتفرم PHP لیارا افزونهٔ `redis` (phpredis) را نداشته باشد، `REDIS_URL` و `MESSENGER_TRANSPORT_DSN: redis://...` کار نمی‌کنند. اول در محیط لیارا چک کن (`php -m | grep redis`). اگر نبود: - برای Messenger به transport مبتنی بر **Doctrine** سوییچ کن (`MESSENGER_TRANSPORT_DSN=doctrine://default`) و جدول مسنجر را با migration بساز، - برای cache هم به adapter فایل‌سیستم/Doctrine برگرد. - **اسکریپت‌های composer در build:** `cache:clear` در prod بدون DB معمولاً سالم است؛ اگر build به خاطر اسکریپت‌ها شکست، می‌توان نصب را بدون اسکریپت کرد و همه‌چیز را به `liara_pre_start.sh` سپرد (هماهنگ با کاری که `Dockerfile` با `--no-scripts` می‌کند). - **ماندگاری کلید JWT:** دیسک `jwt` حتماً mount شود؛ وگرنه هر دیپلوی کلید نو می‌سازد و توکن‌ها باطل می‌شوند. - **asset‌ها فراموش نشوند:** اگر `public/build` آپلود نشود، پنل ادمین لود نمی‌شود — قبل دیپلوی `yarn build` و `.liaraignore` درست. - **documentRoot:** اگر روی `public` ست نشود، Apache فایل‌های ریشه را سرو می‌کند و اپ ۴۰۴/۴۰۳ می‌دهد. - **هماهنگی نسخهٔ DB:** `serverVersion` در `DATABASE_URL` با نسخهٔ MariaDB مدیریت‌شده یکی باشد (11.8). - این تغییر فقط زیرساخت دیپلوی است و هیچ route/endpoint را عوض نمی‌کند → نیازی به‌روزرسانی `docs/api/*` نیست. - **یک مسیر را انتخاب کن:** Docker (`liara.json` فعلی + `liara-compose.yaml`) یا PHP platform (این پرامپت). همزمان هر دو `liara.json` نگه‌داشتنی نیست. ```