# دیپلوی 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
<IfModule mod_rewrite.c>
    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]
</IfModule>
<IfModule !mod_rewrite.c>
    <IfModule mod_alias.c>
        RedirectMatch 307 ^/$ /index.php/
    </IfModule>
</IfModule>
```

### ۳. ساخت `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://<domain>/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` نگه‌داشتنی نیست.
```
