diff --git a/.claude/prompt/deploy-liara-php.md b/.claude/prompt/deploy-liara-php.md new file mode 100644 index 00000000..05c39c82 --- /dev/null +++ b/.claude/prompt/deploy-liara-php.md @@ -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 + + 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` نگه‌داشتنی نیست. +``` diff --git a/.env.coolify.example b/.env.coolify.example index e0d6bba4..5a5e9e0c 100644 --- a/.env.coolify.example +++ b/.env.coolify.example @@ -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 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 836ae23b..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -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 diff --git a/.liaraignore b/.liaraignore new file mode 100644 index 00000000..397e0703 --- /dev/null +++ b/.liaraignore @@ -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 diff --git a/docs/deploy-liara-php.md b/docs/deploy-liara-php.md new file mode 100644 index 00000000..497d269e --- /dev/null +++ b/docs/deploy-liara-php.md @@ -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:// +``` + +همهٔ پروسه‌ها (وب + هر دو 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://:@:3306/?serverVersion=mariadb-11.8.0&charset=utf8mb4" --app clinicpro-api +liara env set REDIS_URL="redis://:@:6379" --app clinicpro-api +liara env set MESSENGER_TRANSPORT_DSN="redis://:@: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:// DEFAULT_URI=https:// --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="" CORS_ALLOW_ORIGIN="" --app clinicpro-api +liara env set API_IR_BASE_URL=https://s.api.ir API_IR_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:///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` را همزمان نگه ندار. diff --git a/liara.json b/liara.json index 716b3a9f..4f675177 100644 --- a/liara.json +++ b/liara.json @@ -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" + } ] } diff --git a/liara_php.ini b/liara_php.ini new file mode 100644 index 00000000..a0e3cd50 --- /dev/null +++ b/liara_php.ini @@ -0,0 +1,5 @@ +file_uploads = On +memory_limit = 1024M +upload_max_filesize = 16M +post_max_size = 24M +max_execution_time = 120 diff --git a/liara_pre_build.sh b/liara_pre_build.sh new file mode 100755 index 00000000..aee278b1 --- /dev/null +++ b/liara_pre_build.sh @@ -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 diff --git a/liara_pre_start.sh b/liara_pre_start.sh new file mode 100755 index 00000000..6bcd47d0 --- /dev/null +++ b/liara_pre_start.sh @@ -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 diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 00000000..8abdb0f7 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,35 @@ +DirectoryIndex index.php + + + Options -MultiViews + + + + 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] + + + + + # When mod_rewrite is not available, redirect to the front controller. + RedirectMatch 307 ^/$ /index.php/ + + diff --git a/supervisor.conf b/supervisor.conf new file mode 100644 index 00000000..d6752426 --- /dev/null +++ b/supervisor.conf @@ -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