feat(deploy): configure deployment for ClinicPro on Liara PHP platform

This commit is contained in:
hamed
2026-06-29 16:11:54 +03:30
parent efee966efb
commit b4273cfa8b
11 changed files with 683 additions and 83 deletions
+280
View File
@@ -0,0 +1,280 @@
# دیپلوی 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` نگه‌داشتنی نیست.
```
+17
View File
@@ -60,3 +60,20 @@ API_IR_TOKEN=
# from the DB ("Site Settings"), NOT from env. No need to set them here.
# • REFRESH_TOKEN_TTL / OTP_TTL / MAX_FILE_SIZE_BYTES are fixed in the compose file.
# • MariaDB & Redis are separate Coolify resources — see DATABASE_URL/REDIS_URL above.
APP_ENV=prod
APP_DEBUG=0
APP_SECRET=...
JWT_PASSPHRASE=...
JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1
ALLOWED_FRONTEND_HOSTS=... # همان خروجی gen-cors-env.php
CORS_ALLOW_ORIGIN=... # همان
API_IR_BASE_URL=https://s.api.ir
API_IR_TOKEN=...
REFRESH_TOKEN_TTL / OTP_TTL / MAX_FILE_SIZE_BYTES / UPLOAD_DIR
-73
View File
@@ -1,73 +0,0 @@
name: CI
on:
push:
branches: [main, backend-audit]
pull_request:
jobs:
backend:
name: PHP — phpstan + migrate-on-empty-db + phpunit
runs-on: ubuntu-latest
services:
mariadb:
image: mariadb:11.8
env:
MARIADB_ROOT_PASSWORD: root
MARIADB_DATABASE: db
MARIADB_USER: db
MARIADB_PASSWORD: db
ports:
- 3306:3306
options: >-
--health-cmd="healthcheck.sh --connect --innodb_initialized"
--health-interval=10s --health-timeout=5s --health-retries=20
redis:
image: redis:7
ports:
- 6379:6379
options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=10
env:
# Real env vars take precedence over .env/.env.test. doctrine's when@test
# config appends `_test`, so this `db` becomes `db_test` for the test run.
DATABASE_URL: "mysql://db:db@127.0.0.1:3306/db?serverVersion=11.8.0-MariaDB&charset=utf8mb4"
REDIS_URL: "redis://127.0.0.1:6379"
APP_ENV: test
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: pdo_mysql, intl, redis, gd, zip, mbstring, bcmath
coverage: none
- name: Install dependencies
run: composer install --no-interaction --prefer-dist --no-progress
- name: Create test database
run: |
mysql -h127.0.0.1 -uroot -proot -e "CREATE DATABASE IF NOT EXISTS db_test; GRANT ALL ON db_test.* TO 'db'@'%'; FLUSH PRIVILEGES;"
- name: Generate JWT keypair
run: php bin/console lexik:jwt:generate-keypair --skip-if-exists --env=test
# phpstan's symfony extension reads the dev container XML (see phpstan.neon),
# so warm the dev cache first.
- name: Warm dev cache (for phpstan container)
run: php bin/console cache:warmup --env=dev
env:
APP_ENV: dev
- name: PHPStan (baselined — fails only on NEW errors)
run: php vendor/bin/phpstan analyse --no-progress
- name: Migrate on empty DB (smoke)
run: php bin/console doctrine:migrations:migrate --no-interaction --env=test
- name: PHPUnit
run: php bin/phpunit
+49
View File
@@ -0,0 +1,49 @@
# Liara upload excludes. When this file exists Liara uses it instead of .gitignore,
# so public/build (gitignored) IS uploaded — required, the platform never runs yarn.
# Build assets locally first: `ddev exec yarn build`.
.git
.github
.ddev
.claude
.agents
.vscode
.idea
node_modules
# Composer rebuilds vendor on the platform.
vendor
# Local env & dev secrets — a minimal prod .env is written by liara_pre_build.sh.
.env
.env.local
.env.*.local
.env.dev
.env.test
.env.coolify.example
.env.liara.example
config/jwt/*.pem
# Docker/Coolify deployment artifacts (irrelevant on the PHP platform).
Dockerfile
.dockerignore
docker
docker-compose.yml
liara-compose.yaml
# Tests, static analysis, runtime state, docs.
tests
phpunit.dist.xml
.phpunit.cache
phpstan.neon
phpstan-baseline.neon
var/cache
var/log
docs
graphify-out
# DB dumps & OS noise.
*.sql
*.sql.gz
.DS_Store
*.log
+208
View File
@@ -0,0 +1,208 @@
# راهنمای دیپلوی ClinicPro روی لیارا (پلتفرم PHP)
دیپلوی بک‌اند Symfony روی **پلتفرم PHP لیارا** (نوع برنامه `php` هنگام «ساخت برنامه‌ی جدید»). اینجا داکری در کار نیست؛ لیارا کد را می‌گیرد، خودش `composer install` می‌زند و روی Apache + PHP-FPM سرو می‌کند.
> مسیر جایگزین (داکر) در [docs/deploy-liara.md](deploy-liara.md) است. **همزمان فقط یک مسیر** را نگه دار — هر دو از یک `liara.json` استفاده می‌کنند.
---
## نمای کلی
```
┌──────────────── Liara private network ─────────────────┐
│ │
│ ┌──────────────────────────────┐ ┌──────────────┐ │
│ │ clinicpro-api (PHP app) │ │ MariaDB 11.8 │ │
│ │ • Apache + PHP-FPM (docroot │◄─►│ (managed) │ │
│ │ = public/) :80 │ └──────────────┘ │
│ │ • supervisor.conf: │ ┌──────────────┐ │
│ │ worker-async │◄─►│ Redis │ │
│ │ worker-scheduler │ │ (managed) │ │
│ └──────────────────────────────┘ └──────────────┘ │
│ disks: jwt / uploads / var-uploads │
└────────────────────────────────────────────────────────┘
│ :80
▼ https://<domain>
```
همهٔ پروسه‌ها (وب + هر دو worker) در **یک** کانتینر اجرا می‌شوند؛ workerها کانتینر جدا ندارند (برخلاف نسخهٔ docker-compose).
---
## فایل‌های دخیل (همه ساخته شده‌اند)
| فایل | نقش |
|---|---|
| [`liara.json`](../liara.json) | `platform: php`، `php.version: 8.2`، `documentRoot: public`، دیسک‌ها |
| [`public/.htaccess`](../public/.htaccess) | rewrite front-controller سیمفونی روی Apache |
| [`liara_pre_build.sh`](../liara_pre_build.sh) | قبل `composer install`: ساخت `.env` مینیمال prod |
| [`liara_pre_start.sh`](../liara_pre_start.sh) | قبل start: انتظار DB، تولید JWT، cache، migration |
| [`supervisor.conf`](../supervisor.conf) | دو worker مسنجر (`async` + `scheduler_default`) |
| [`liara_php.ini`](../liara_php.ini) | memory_limit، حجم آپلود، زمان اجرا |
| [`.liaraignore`](../.liaraignore) | کنترل آپلود (تا `public/build` آپلود شود، سکرت‌ها مستثنی) |
| [`.env.liara.example`](../.env.liara.example) | مرجع متغیرهای محیطی |
---
## پیش‌نیاز
```bash
npm i -g @liara/cli
liara login
```
مقادیر آماده:
```bash
php -r "echo bin2hex(random_bytes(32)).\"\n\";" # APP_SECRET
openssl rand -hex 32 # JWT_PASSPHRASE
ddev exec php docker/gen-cors-env.php # ALLOWED_FRONTEND_HOSTS + CORS_ALLOW_ORIGIN
```
---
## گام ۱ — build محلی asset‌ها (حیاتی)
پلتفرم PHP لیارا **`yarn` اجرا نمی‌کند** و asset‌های React را نمی‌سازد. قبل هر دیپلوی محلی build کن (خروجی در `public/build`؛ `.liaraignore` آن را آپلود می‌کند):
```bash
ddev exec yarn install --frozen-lockfile
ddev exec yarn build
```
> اگر این مرحله را رد کنی، پنل ادمین `/admin` لود نمی‌شود.
---
## گام ۲ — ساخت دیتابیس و Redis
کنسول لیارا:
1. **MariaDB 11.8** بساز (هماهنگ با `serverVersion=mariadb-11.8.0`).
2. **Redis** بساز.
3. روی هر دو **شبکهٔ خصوصی** فعال؛ هاست خصوصی/کاربر/رمز را یادداشت کن.
---
## گام ۳ — ساخت برنامهٔ PHP
1. New App → نوع **PHP** → نسخهٔ **8.2** (پروژه `php: >=8.2` می‌خواهد) → شناسه `clinicpro-api`.
2. آن را به **همان شبکهٔ خصوصیِ** DB و Redis وصل کن.
---
## گام ۴ — ساخت دیسک‌های ماندگار
در صفحهٔ برنامه، بخش Disks، این سه دیسک را با همین نام‌ها بساز (mount از `liara.json`):
| نام | mountTo | محتوا |
|---|---|---|
| `jwt` | `/var/www/config/jwt` | کلید JWT (نباید هر دیپلوی نو شود) |
| `uploads` | `/var/www/public/uploads` | فایل‌های عمومی |
| `var-uploads` | `/var/www/var/uploads` | فایل‌های خصوصی |
> ⚠️ ریشهٔ اپ فرض شده `/var/www`. اگر در لاگ مسیر دیگری دیدی، `mountTo` در `liara.json` را اصلاح کن.
---
## گام ۵ — متغیرهای محیطی
از روی [`.env.liara.example`](../.env.liara.example) ست کن (تب Environment یا CLI). یک‌بار روی app؛ workerها همان را می‌گیرند.
```bash
liara env set APP_ENV=prod APP_DEBUG=0 --app clinicpro-api
liara env set APP_SECRET=<...> JWT_PASSPHRASE=<...> --app clinicpro-api
liara env set DATABASE_URL="mysql://<user>:<pass>@<db-private-host>:3306/<db>?serverVersion=mariadb-11.8.0&charset=utf8mb4" --app clinicpro-api
liara env set REDIS_URL="redis://:<pass>@<redis-private-host>:6379" --app clinicpro-api
liara env set MESSENGER_TRANSPORT_DSN="redis://:<pass>@<redis-private-host>:6379/messages" --app clinicpro-api
liara env set JWT_SECRET_KEY='%kernel.project_dir%/config/jwt/private.pem' JWT_PUBLIC_KEY='%kernel.project_dir%/config/jwt/public.pem' --app clinicpro-api
liara env set APP_BASE_URL=https://<domain> DEFAULT_URI=https://<domain> --app clinicpro-api
liara env set TRUSTED_PROXIES="10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1" --app clinicpro-api
liara env set ALLOWED_FRONTEND_HOSTS="<output>" CORS_ALLOW_ORIGIN="<output>" --app clinicpro-api
liara env set API_IR_BASE_URL=https://s.api.ir API_IR_TOKEN=<token> --app clinicpro-api
liara env set REFRESH_TOKEN_TTL=2592000 OTP_TTL=1200 MAX_FILE_SIZE_BYTES=5242880 UPLOAD_DIR=var/uploads --app clinicpro-api
```
نکته‌ها:
- `RUN_INIT` را ست **نکن** — init یک‌بار در `liara_pre_start.sh` اجرا می‌شود.
- `APP_ENV=prod` موقع build هم لازم است (composer اسکریپت `cache:clear` را اجرا می‌کند).
- کلیدهای SMS و درگاه پرداخت از **دیتابیس** خوانده می‌شوند، نه env.
---
## گام ۶ — دیپلوی
از ریشهٔ `clinicpro/`:
```bash
liara deploy
```
روند خودکار:
1. `liara_pre_build.sh``.env` مینیمال prod (اگر نبود).
2. `composer install` (لیارا).
3. `liara_pre_start.sh` → انتظار DB → تولید JWT (روی دیسک) → warmup → migration.
4. Apache (docroot=`public`) + `supervisor.conf` (هر دو worker) بالا می‌آیند.
---
## گام ۷ — اتصال دامنه و تأیید
1. بخش Domains: دامنهٔ API را وصل کن، TLS بگیر.
2. سلامت:
```bash
curl -i https://<domain>/health # باید 200
```
3. workerها:
```bash
# در کنسول/CLI برنامه:
supervisorctl status # worker-async و worker-scheduler = RUNNING
```
4. ورود `/admin` و تست یک endpoint (تأیید DB/Redis).
---
## دیپلوی‌های بعدی
```bash
ddev exec yarn build # اگر assets عوض شد
liara deploy
```
migrationها خودکار در `liara_pre_start.sh` اجرا می‌شوند. دیسک‌ها و env حفظ می‌شوند.
---
## نگاشت compose → لیارا (PHP platform)
در `docker-compose.yml` هر worker یک کانتینر جدا بود؛ اینجا هر دو در همان کانتینر با `supervisor.conf`:
| compose | لیارا |
|---|---|
| سرویس `app` (php-fpm+nginx) | خود پلتفرم PHP (Apache+PHP-FPM)، docroot=`public` |
| `worker-async` (کانتینر جدا) | `[program:worker-async]` در `supervisor.conf` |
| `worker-scheduler` (کانتینر جدا) | `[program:worker-scheduler]` در `supervisor.conf` |
| `environment: *app-env` + `RUN_INIT` | env روی app؛ init در `liara_pre_start.sh` |
| `volumes` | دیسک‌های `liara.json` |
| `networks: [coolify]` | شبکهٔ خصوصی لیارا |
| `restart`/`healthcheck` | `autorestart=true` |
| `stop_grace_period: 30s` | `stopsignal=TERM` + `stopwaitsecs=30` |
---
## رفع اشکال
| نشانه | علت | راه‌حل |
|---|---|---|
| پنل `/admin` سفید/۴۰۴ asset | `public/build` آپلود نشده | `ddev exec yarn build` قبل deploy؛ `.liaraignore` build را مستثنی نکند |
| کل سایت ۴۰۳/۴۰۴ | documentRoot روی `public` نیست یا `.htaccess` نیست | `documentRoot: public` در liara.json + `public/.htaccess` |
| `Database not reachable after 60s` | شبکهٔ خصوصی/`DATABASE_URL` غلط | DB و app یک شبکه؛ هاست خصوصی را چک کن |
| توکن‌ها بعد هر دیپلوی باطل | دیسک `jwt` mount نشده | دیسک `jwt` → `/var/www/config/jwt` |
| worker بالا نمی‌آید / messenger خطای redis | افزونهٔ `redis` روی پلتفرم نیست | `php -m \| grep redis`؛ نبود → `MESSENGER_TRANSPORT_DSN=doctrine://default` + کش فایل‌سیستم |
| build به‌خاطر composer scripts شکست | `cache:clear` موقع نصب | مطمئن شو `APP_ENV=prod` ست است؛ در صورت لزوم نصب را `--no-scripts` کن و همه‌چیز را به `liara_pre_start.sh` بسپار |
| خطای CORS از فرانت | `CORS_ALLOW_ORIGIN` قدیمی | `docker/gen-cors-env.php` دوباره؛ env به‌روز |
---
## ریسک‌های شناخته‌شده
- **ext-redis:** اگر پلتفرم PHP لیارا phpredis نداشته باشد، Redis (cache + messenger) کار نمی‌کند → fallback به Doctrine transport و کش فایل‌سیستم. قبل اتکا verify کن.
- **مسیر دیسک:** ریشهٔ `/var/www` فرض است؛ با لاگ واقعی تطبیق بده.
- **انتخاب مسیر:** یا Docker یا PHP platform — هر دو `liara.json` را همزمان نگه ندار.
+17 -10
View File
@@ -1,16 +1,23 @@
{
"app": "clinicpro-api",
"platform": "docker",
"port": 8080,
"healthCheck": {
"command": "/usr/local/bin/healthcheck.sh",
"interval": 30,
"timeout": 5,
"initialDelaySeconds": 60
"platform": "php",
"port": 80,
"php": {
"version": "8.2"
},
"documentRoot": "public",
"disks": [
{ "name": "jwt", "mountTo": "/app/config/jwt" },
{ "name": "uploads", "mountTo": "/app/public/uploads" },
{ "name": "var-uploads", "mountTo": "/app/var/uploads" }
{
"name": "jwt",
"mountTo": "/var/www/config/jwt"
},
{
"name": "uploads",
"mountTo": "/var/www/public/uploads"
},
{
"name": "var-uploads",
"mountTo": "/var/www/var/uploads"
}
]
}
+5
View File
@@ -0,0 +1,5 @@
file_uploads = On
memory_limit = 1024M
upload_max_filesize = 16M
post_max_size = 24M
max_execution_time = 120
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh
# Liara PHP platform pre-build hook — runs BEFORE composer install (no env access).
# Symfony's Dotenv::bootEnv() hard-requires a .env file to exist, and composer's
# auto-scripts (cache:clear) run during install. The real .env is excluded from the
# upload (.liaraignore) to avoid leaking dev secrets, so write a minimal prod one if
# absent. Real values come from Liara's env vars; Dotenv never overwrites a set var.
set -e
if [ ! -f .env ]; then
printf 'APP_ENV=prod\nAPP_DEBUG=0\n' > .env
echo "pre-build: wrote minimal prod .env"
fi
+25
View File
@@ -0,0 +1,25 @@
#!/bin/sh
# Liara PHP platform pre-start hook — runs before the app starts, with env vars
# available. One-time init: wait for DB, generate JWT keypair, warm cache, migrate.
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))
if [ "$i" -ge 30 ]; then
echo "Database not reachable after 60s — aborting." >&2
exit 1
fi
echo "waiting for database... ($i)"
sleep 2
done
# JWT keypair persists on the mounted 'jwt' disk; --skip-if-exists is idempotent.
php bin/console lexik:jwt:generate-keypair --skip-if-exists --no-interaction
php bin/console cache:clear --no-warmup
php bin/console cache:warmup
# Apply pending migrations inside a transaction.
php bin/console doctrine:migrations:migrate --all-or-nothing --no-interaction
+35
View File
@@ -0,0 +1,35 @@
DirectoryIndex index.php
<IfModule mod_negotiation.c>
Options -MultiViews
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
# Determine the RewriteBase automatically and set it as environment variable.
RewriteCond %{REQUEST_URI}::$0 ^(/.+)/(.*)::\2$
RewriteRule .* - [E=BASE:%1]
# Sets the HTTP_AUTHORIZATION header removed by Apache
RewriteCond %{HTTP:Authorization} .+
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0]
# Redirect to URI without front controller to prevent duplicate content
RewriteCond %{ENV:REDIRECT_STATUS} =""
RewriteRule ^index\.php(?:/(.*)|$) %{ENV:BASE}/$1 [R=301,L]
# If the requested filename exists, simply serve it.
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^ - [L]
# Rewrite all other queries to the front controller.
RewriteRule ^ %{ENV:BASE}/index.php [L]
</IfModule>
<IfModule !mod_rewrite.c>
<IfModule mod_alias.c>
# When mod_rewrite is not available, redirect to the front controller.
RedirectMatch 307 ^/$ /index.php/
</IfModule>
</IfModule>
+35
View File
@@ -0,0 +1,35 @@
; Liara PHP platform — background workers.
; The platform itself serves Apache + PHP-FPM; this file ONLY adds the two
; Symfony Messenger consumers (mirrors the docker-compose worker services).
; $ROOT is the app root on the platform. --time-limit makes each consumer exit
; cleanly every hour; autorestart brings it back (guards memory/connection drift).
[program:worker-async]
process_name=%(program_name)s_%(process_num)02d
command=sh -c "cd $ROOT && php bin/console messenger:consume async --time-limit=3600 --memory-limit=128M -v"
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
; Graceful shutdown: SIGTERM lets messenger finish the in-flight message, then exit
; (mirrors compose stop_grace_period: 30s).
stopsignal=TERM
stopwaitsecs=30
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=sh -c "cd $ROOT && php bin/console messenger:consume scheduler_default --time-limit=3600 -v"
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
stopsignal=TERM
stopwaitsecs=30
numprocs=1
user=www-data
redirect_stderr=true
stdout_logfile=/tmp/worker-scheduler.log