feat(deploy): configure deployment for ClinicPro on Liara PHP platform
This commit is contained in:
@@ -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` نگهداشتنی نیست.
|
||||
```
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
file_uploads = On
|
||||
memory_limit = 1024M
|
||||
upload_max_filesize = 16M
|
||||
post_max_size = 24M
|
||||
max_execution_time = 120
|
||||
Executable
+12
@@ -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
|
||||
Executable
+25
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user