281 lines
13 KiB
Markdown
281 lines
13 KiB
Markdown
# دیپلوی 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` نگهداشتنی نیست.
|
||
```
|