diff --git a/.claude/prompt/coolify-symfony-deploy.md b/.claude/prompt/coolify-symfony-deploy.md new file mode 100644 index 00000000..1cc5e426 --- /dev/null +++ b/.claude/prompt/coolify-symfony-deploy.md @@ -0,0 +1,205 @@ +# آماده‌سازی پروژه ClinicPro برای دیپلوی روی Coolify (Symfony + Nixpacks) + +## زمینه + +پروژه قرار است روی **Coolify** با Build Pack = **Nixpacks** دیپلوی شود (طبق https://coolify.io/docs/applications/symfony). +در حال حاضر پروژه فقط برای محیط لوکال **ddev** پیکربندی شده است (`compose.yaml`، `.ddev/`). +برای Coolify نیاز به این موارد داریم که هیچ‌کدام الان وجود ندارند: + +1. کنترل کامل بر فرایند **build** — چون این پروژه علاوه بر PHP یک فرانت‌اند **React 19 + Webpack Encore** دارد که باید با `yarn build` کامپایل شود (Nixpacks پیش‌فرض Symfony فقط PHP را build می‌کند و دارایی‌های `public/build/` ساخته نمی‌شوند). +2. تولید **کلیدهای JWT** (lexik) هنگام دیپلوی — این کلیدها در `.gitignore` هستند و در ریپو نیستند. +3. اجرای **migrations** بعد از هر دیپلوی. +4. اجرای **worker**های Messenger: + - `messenger:consume async` → ارسال SMS + - `messenger:consume scheduler_default` → انقضای نوبت‌های پرداخت‌نشده (هر ۱ دقیقه) +5. تنظیم **Trusted Proxies** (چون Coolify پشت Traefik/reverse-proxy است؛ بدون آن `https`، IP کلاینت و کوکی‌ها درست کار نمی‌کنند). +6. یک فایل **`.env.coolify.example`** که همهٔ متغیرهای محیطی لازم را برای وارد کردن در داشبورد Coolify فهرست کند. + +نکتهٔ مهم معماری: دیتابیس پروژه **MariaDB/MySQL** است (نه PostgreSQL که داک Coolify مثال می‌زند). Redis برای Messenger و OTP استفاده می‌شود. + +## هدف + +پروژه را طوری «coolify-ready» کن که با Build Pack = Nixpacks و چند متغیر محیطی، بدون تغییر در منطق برنامه، روی Coolify بالا بیاید — شامل build فرانت‌اند، تولید کلید JWT، migration، و راه‌اندازی worker‌ها. + +## فایل‌های مرتبط + +| فایل | نقش | وضعیت | +|------|-----|-------| +| `nixpacks.toml` | کنترل فازهای build/install/start برای Nixpacks | **باید ساخته شود** | +| `config/packages/framework.yaml` | افزودن `trusted_proxies` و `trusted_headers` | باید ویرایش شود | +| `.env.coolify.example` | فهرست کامل env برای داشبورد Coolify | **باید ساخته شود** | +| `docs/deploy/coolify.md` | راهنمای گام‌به‌گام دیپلوی | **باید ساخته شود** | +| `.env.example` | مرجع موجود متغیرها (فقط خواندن) | بدون تغییر | +| `package.json` | اسکریپت `build` = `encore production` | بدون تغییر | +| `config/packages/messenger.yaml` | تعریف transport های `async` و `scheduler_default` | بدون تغییر (مرجع worker) | + +## وضعیت فعلی + +- `package.json` اسکریپت build دارد: `"build": "encore production --progress"`. +- خروجی build در `public/build/` می‌رود (در `.gitignore` است → باید حین دیپلوی ساخته شود). +- کلیدهای JWT در `config/jwt/*.pem` (در `.gitignore`). +- `framework.yaml` فعلی هیچ `trusted_proxies` ندارد: + +```yaml +framework: + secret: '%env(APP_SECRET)%' + session: true +``` + +- متغیرهای کلیدی از `.env.example`: `APP_ENV`, `APP_SECRET`, `DATABASE_URL` (mysql), `JWT_*`, `CORS_ALLOW_ORIGIN`, `MESSENGER_TRANSPORT_DSN` (redis), `REDIS_URL`, `API_IR_*`, `APP_BASE_URL`, `ALLOWED_FRONTEND_HOSTS`, `DEFAULT_URI`, `MAX_FILE_SIZE_BYTES`, `UPLOAD_DIR`. + +## وظایف + +### ۱. ساخت `nixpacks.toml` در ریشهٔ پروژه + +هدف: علاوه بر نصب وابستگی‌های PHP، فرانت‌اند را هم build کن و کلید JWT بساز. Nixpacks باید هم PHP و هم Node/Yarn را در محیط build داشته باشد. + +```toml +# nixpacks.toml — کنترل دیپلوی Coolify +[phases.setup] +nixPkgs = ["php82", "php82Packages.composer", "nodejs_20", "yarn", "openssl"] + +[phases.install] +cmds = [ + "composer install --no-dev --optimize-autoloader --no-interaction --prefer-dist", + "yarn install --frozen-lockfile" +] + +[phases.build] +# build دارایی‌های فرانت‌اند (React/Encore) → public/build +# و تولید کلید JWT اگر وجود نداشته باشد (idempotent) +cmds = [ + "yarn build", + "php bin/console lexik:jwt:generate-keypair --skip-if-exists --no-interaction" +] + +[start] +# Nixpacks خودش php-fpm/nginx را با NIXPACKS_PHP_ROOT_DIR اجرا می‌کند؛ +# این بخش را فقط در صورت نیاز override کن. در حالت عادی خالی بماند. +``` + +**نکات:** +- نسخهٔ `php82` باید با `"php": ">=8.2"` در `composer.json` هم‌خوان باشد. اگر Nixpacks نسخهٔ PHP را خودکار از composer تشخیص می‌دهد، می‌توان `php` را از `nixPkgs` حذف کرد — ولی صریح بودن امن‌تر است. +- `lexik:jwt:generate-keypair --skip-if-exists` باعث می‌شود اگر کلید از قبل (مثلاً از volume) وجود داشته باشد، دوباره ساخته نشود. **هشدار را در docs ذکر کن:** اگر کلید JWT بین دیپلوی‌ها persist نشود، همهٔ توکن‌های صادرشده باطل می‌شوند → بهتر است `JWT_SECRET_KEY`/`JWT_PUBLIC_KEY` به‌صورت محتوای base64 در env داده شوند یا روی volume مانت شوند. هر دو گزینه را در docs توضیح بده. +- اگر `JWT_PASSPHRASE` در env تنظیم نشده باشد، تولید کلید fail می‌شود — این را در docs قید کن. + +### ۲. افزودن Trusted Proxies به `config/packages/framework.yaml` + +Coolify پشت Traefik است. بدون این تنظیم، Symfony پروتکل `https` و IP واقعی را تشخیص نمی‌دهد. + +```yaml +framework: + secret: '%env(APP_SECRET)%' + session: true + + # Coolify/Traefik reverse proxy + trusted_proxies: '%env(TRUSTED_PROXIES)%' + trusted_headers: ['x-forwarded-for', 'x-forwarded-host', 'x-forwarded-proto', 'x-forwarded-port'] +``` + +و در `.env.coolify.example` مقدار پیش‌فرض امن بده (طبق داک Coolify که از REMOTE_ADDR یا رنج شبکهٔ داکر استفاده می‌شود): + +``` +# IP/رنج reverse proxy؛ برای Coolify معمولاً رنج شبکهٔ داخلی داکر یا 'REMOTE_ADDR' +TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1 +``` + +**نکته:** اگر `TRUSTED_PROXIES` خالی بماند، Symfony خطا نمی‌دهد ولی پشت پراکسی درست کار نمی‌کند؛ مقدار پیش‌فرض را در env بگذار تا اگر کاربر فراموش کرد، رفتار منطقی باشد. اگر ترجیح می‌دهی fallback داشته باشی از `'%env(default::TRUSTED_PROXIES)%'` با مقدار پیش‌فرض در `.env` استفاده کن. + +### ۳. ساخت `.env.coolify.example` + +فهرست کامل و دسته‌بندی‌شدهٔ تمام متغیرهایی که باید در داشبورد Coolify (Environment Variables) وارد شوند. از روی `.env.example` بساز ولی این موارد Coolify-specific را اضافه کن: + +```env +# ───── Coolify / Nixpacks (الزامی طبق داک Coolify) ───── +APP_ENV=prod +APP_DEBUG=0 +NIXPACKS_PHP_FALLBACK_PATH=/index.php +NIXPACKS_PHP_ROOT_DIR=/app/public +# Port Exposure را در UI روی 80 بگذار + +# ───── Symfony Core ───── +APP_SECRET= # php -r "echo bin2hex(random_bytes(32));" +DEFAULT_URI=https://your-domain.com +APP_BASE_URL=https://your-domain.com +ALLOWED_FRONTEND_HOSTS=your-domain.com + +# ───── Reverse Proxy (Coolify/Traefik) ───── +TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1 + +# ───── Database (MariaDB/MySQL service در Coolify) ───── +DATABASE_URL="mysql://USER:PASS@HOST:3306/clinic_pro?serverVersion=mariadb-11.8.0&charset=utf8mb4" + +# ───── JWT (lexik) ───── +JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem +JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem +JWT_PASSPHRASE= # الزامی برای تولید کلید در فاز build + +# ───── CORS ───── +CORS_ALLOW_ORIGIN='^https://your-domain\.com$' + +# ───── Messenger / Redis (Redis service در Coolify) ───── +MESSENGER_TRANSPORT_DSN=redis://HOST:6379/messages +REDIS_URL=redis://HOST:6379 + +# ───── Auth / OTP ───── +REFRESH_TOKEN_TTL=2592000 +OTP_TTL=1200 + +# ───── api.ir (استعلام هویت) ───── +API_IR_BASE_URL=https://s.api.ir +API_IR_TOKEN= + +# ───── File Upload ───── +MAX_FILE_SIZE_BYTES=5242880 +UPLOAD_DIR=var/uploads +``` + +**نکات مهم برای این فایل:** +- `serverVersion` در `DATABASE_URL` را با MariaDB هم‌خوان کن (`mariadb-11.8.0`)، نه `8.0`، چون پروژه روی MariaDB 11.8 است. +- توضیح بده که `HOST` در DSNها = نام internal service که Coolify برای DB/Redis می‌دهد. +- کلیدهای SMS و درگاه پرداخت از DB («تنظیمات سایت») خوانده می‌شوند، نه env — این را با کامنت ذکر کن. + +### ۴. ساخت راهنمای `docs/deploy/coolify.md` + +یک سند گام‌به‌گام فارسی شامل: + +1. **تنظیمات Coolify UI:** + - Build Pack = `nixpacks` + - Port Exposure = `80` + - Health Check Path = `/api/doc` یا یک endpoint سبک (بررسی کن چه endpoint عمومی‌ای برای healthcheck مناسب است؛ از `config/packages/security.yaml` لیست public_endpoints را ببین). +2. **Environment Variables:** ارجاع به `.env.coolify.example` + متغیرهای الزامی Nixpacks. +3. **Post-Deployment Command** (در بخش مربوطهٔ Coolify): + ``` + php bin/console doctrine:migrations:migrate --all-or-nothing --no-interaction + ``` +4. **Persistent Storage (Volumes)** — این بخش حیاتی است، حتماً توضیح بده: + - `config/jwt/` → تا کلیدهای JWT بین دیپلوی‌ها باقی بمانند (در غیر این صورت همهٔ لاگین‌ها باطل می‌شوند). + - `public/uploads/` و `var/uploads/` → فایل‌های آپلودی کاربران. + - `var/log/` (اختیاری). +5. **Worker‌ها (سرویس‌های جداگانه در Coolify):** توضیح بده که باید دو سرویس/کانتینر اضافه با همان image ولی start command متفاوت ساخته شوند، یا از Supervisor در همان کانتینر استفاده شود: + - `php bin/console messenger:consume async --time-limit=3600 --memory-limit=128M` + - `php bin/console messenger:consume scheduler_default --time-limit=3600` + گزینهٔ توصیه‌شده را مشخص کن (در Coolify معمولاً سرویس جداگانه تمیزتر است؛ یا یک `supervisord.conf` در همان کانتینر). +6. **اولین راه‌اندازی:** ساخت admin اولیه: + ``` + php bin/console app:create-admin + ``` + +### ۵. (اختیاری ولی توصیه‌شده) فایل `supervisord.conf` برای worker‌ها + +اگر تصمیم گرفتی worker‌ها در همان کانتینر اجرا شوند، یک `supervisord.conf` بساز که هر دو consumer را مدیریت کند و در `nixpacks.toml` به phase setup اضافه‌اش کن. در غیر این صورت، در docs گزینهٔ «سرویس جداگانه در Coolify» را به‌عنوان روش پیش‌فرض توضیح بده و این فایل را نساز. + +تصمیم پیشنهادی: **سرویس جداگانه در Coolify** (ساده‌تر، بدون نیاز به supervisor و بدون پیچیده کردن کانتینر اصلی). فقط در docs مستند کن. + +## نکات مهم (محدودیت‌ها و edge caseها) + +- **MariaDB نه PostgreSQL:** داک رسمی Coolify مثال PostgreSQL می‌زند؛ این پروژه MySQL/MariaDB است. همه‌جا DSN را `mysql://...` با `serverVersion=mariadb-11.8.0` نگه دار. +- **build فرانت‌اند الزامی است:** بدون `yarn build`، صفحات admin (و دارایی‌های React) لود نمی‌شوند چون `public/build/` خالی می‌ماند. این تفاوت اصلی با setup پیش‌فرض Symfony در داک Coolify است. +- **JWT keypair persistence:** اگر volume برای `config/jwt/` تعریف نشود، هر دیپلوی کلید جدید می‌سازد و کاربران logout می‌شوند. این مهم‌ترین نکتهٔ عملیاتی است — حتماً در docs برجسته شود. +- **`JWT_PASSPHRASE` باید قبل از build در env باشد** وگرنه `lexik:jwt:generate-keypair` در فاز build شکست می‌خورد. +- **Scheduler:** transport `scheduler_default` نوبت‌های پرداخت‌نشده را هر دقیقه منقضی می‌کند؛ اگر worker آن اجرا نشود، نوبت‌های رزرو ولی پرداخت‌نشده آزاد نمی‌شوند. این رفتار را در docs ذکر کن. +- **هیچ منطق برنامه‌ای را تغییر نده** — فقط فایل‌های infra/config اضافه یا ویرایش شوند. +- **بدون تغییر در ddev:** فایل‌های `compose.yaml` و `.ddev/` دست‌نخورده بمانند تا محیط لوکال نشکند. +- بعد از تغییر `framework.yaml`، `ddev exec php bin/console cache:clear` بزن تا مطمئن شوی config معتبر است. +- این تغییرات API را عوض نمی‌کنند، پس نیازی به به‌روزرسانی `docs/api/*` نیست؛ ولی فایل جدید `docs/deploy/coolify.md` اضافه می‌شود. diff --git a/.gitignore b/.gitignore index 7299dd92..33260e49 100644 --- a/.gitignore +++ b/.gitignore @@ -54,5 +54,8 @@ npm-debug.log yarn-error.log ###< symfony/webpack-encore-bundle ### clinicpro/* -clinicpromain/* .env + +# Database backups (never commit dumps to the repo) +*.sql +*.sql.gz