# راهنمای دیپلوی ClinicPro (Coolify + Docker Compose)

این راهنما برای دیپلوی بک‌اند ClinicPro روی **Coolify** با استفاده از `docker-compose.yml` نوشته شده.

> توجه: این `docker-compose.yml` فقط برای **production** است. محیط لوکال از `compose.yaml` خودِ ddev استفاده می‌کند — این دو را با هم اشتباه نگیر.

---

## معماری استک

`docker-compose.yml` فقط سرویس‌های **اپلیکیشن** را بالا می‌آورد. **MariaDB و Redis جداگانه** به‌صورت Database Resource مستقل Coolify اجرا می‌شوند (نه داخل این compose):

| سرویس | نقش | نکته |
|---|---|---|
| `app` | PHP-FPM + Nginx (وب، non-root، پورت ۸۰۸۰) | تنها سرویسی که `RUN_INIT=1` دارد؛ مهاجرت DB و تولید کلید JWT را اجرا می‌کند. دامنه‌ها را به این سرویس (پورت ۸۰۸۰) وصل کن. |
| `worker-async` | مصرف‌کننده صف async (SMS و کارهای async) | `messenger:consume async` |
| `worker-scheduler` | زمان‌بند | هر ۱ دقیقه نوبت‌های پرداخت‌نشده را منقضی می‌کند |

| Resource مستقل Coolify | نقش |
|---|---|
| **MariaDB 11.8** (Database Resource جدا) | پایگاه‌داده — مدیریت/بکاپ/ری‌استارت مستقل |
| **Redis** (Database Resource جدا) | صف Messenger + کش |

**ولوم‌های ماندگار استک اپ** (در ری‌دیپلوی حفظ می‌شوند):

- `jwt_keys` → کلیدهای JWT
- `uploads_public` و `uploads_var` → فایل‌های آپلودی

> داده‌ی MariaDB و Redis توسط خودِ Resourceهای مستقل نگه داشته می‌شود (volume در آن‌ها، نه در این استک).

---

## پیش‌نیازها

- نمونه‌ی Coolify در حال اجرا با Traefik (پیش‌فرض Coolify).
- ریپوی Git متصل به Coolify.
- رکوردهای DNS برای دامنه‌ی API و همه‌ی دامنه‌های فرانت‌اند که به سرور اشاره کنند.

---

## مرحله ۱ — ساخت دیتابیس‌های مستقل (MariaDB + Redis)

اول این دو Resource را جدا بساز (قبل از استک اپ):

1. **New Resource → Database → MariaDB**، نسخه **11.8** (باید با `serverVersion` در `DATABASE_URL` و migrationها هم‌خوان باشد). نام دیتابیس `clinic_pro`، یوزر `clinic`، یک رمز قوی ست کن.
2. **New Resource → Database → Redis**.
3. از صفحه‌ی هر Resource، **Internal URL / hostname** (به شکل `mariadb-<uuid>` و `redis-<uuid>`) و credentials را یادداشت کن — در مرحله ۴ لازم می‌شود.

---

## مرحله ۲ — ساخت منبع اپ (Resource) در Coolify

1. **New Resource → Docker Compose** (Build Pack: `Docker Compose`).
2. ریپو و برنچ را انتخاب کن.
3. فیلد **Compose file** را روی `docker-compose.yml` بگذار.
4. **"Connect to Predefined Network"** را روی این استک **فعال کن** — تا اپ بتواند به Resourceهای مستقل MariaDB/Redis (که در شبکه‌ی دیگری هستند) وصل شود.
5. `networks:` سفارشی تعریف **نکن** — شبکه را Coolify مدیریت می‌کند؛ شبکه‌ی سفارشی روتینگ Traefik را می‌شکند.

---

## مرحله ۳ — دامنه‌ها

همه‌ی دامنه‌های سرو شونده را به سرویس **`app`** اختصاص بده — هم دامنه‌ی API و هم همه‌ی دامنه‌های فرانت‌اند. Coolify لیست دامنه‌ی جدا‌شده با کاما را روی یک سرویس قبول می‌کند و TLS را خودش صادر می‌کند.

> ⚠️ **پورت سرویس = `8080`** (نه ۸۰). کانتینر non-root اجرا می‌شود و nginx روی پورت غیرممتاز ۸۰۸۰ گوش می‌دهد. در Coolify port سرویس `app` را روی **۸۰۸۰** بگذار (Traefik به هر پورتی روت می‌کند — طبق داک، هر پورتی مجاز است).

> اجازه‌دادن CORS و host فرانت‌اندها از طریق متغیرهای `CORS_ALLOW_ORIGIN` / `ALLOWED_FRONTEND_HOSTS` کنترل می‌شود، نه دامنه‌ی Coolify.

---

## مرحله ۴ — متغیرهای محیطی

از `.env.coolify.example` کپی کن و در تب **Environment Variables** منبع اپ Coolify بگذار.

### اتصال به دیتابیس‌های مستقل (الزامی)

چون MariaDB/Redis جدا هستند، رشته‌های اتصال **اینجا** ست می‌شوند و به hostname داخلی Resource اشاره می‌کنند (`mariadb-<uuid>` / `redis-<uuid>` از مرحله ۱):

```bash
DATABASE_URL="mysql://clinic:DB_PASSWORD@mariadb-XXXXXXXX:3306/clinic_pro?serverVersion=mariadb-11.8.0&charset=utf8mb4"
REDIS_URL="redis://redis-XXXXXXXX:6379"
MESSENGER_TRANSPORT_DSN="redis://redis-XXXXXXXX:6379/messages"
# اگر Redis رمز دارد: redis://:PASSWORD@redis-XXXXXXXX:6379
```

> `serverVersion=mariadb-11.8.0` باید با نسخه‌ی Resource مستقل MariaDB یکی باشد.

### اسرار (الزامی — قبل از اولین دیپلوی)

```bash
APP_SECRET=        # php -r "echo bin2hex(random_bytes(32));"
JWT_PASSPHRASE=    # openssl rand -hex 32   (باید قبل از اولین استارت موجود باشد؛ کلید JWT با همین ساخته می‌شود)
```

> رمز DB دیگر اینجا (`DB_PASSWORD`/`DB_ROOT_PASSWORD`) ست نمی‌شود — هنگام ساخت Resource مستقل MariaDB تعیین می‌شود و داخل `DATABASE_URL` بالا قرار می‌گیرد.

> ⚠️ `JWT_PASSPHRASE` را بعد از اولین دیپلوی عوض نکن — کلید JWT یک‌بار با همین passphrase تولید و روی ولوم `jwt_keys` ماندگار می‌شود. تغییرش همه‌ی توکن‌ها را می‌شکند.

در Coolify می‌توانی به‌جای هاردکد از magic var استفاده کنی:

```bash
APP_SECRET=${SERVICE_HEX_APPSECRET}
```

### دامنه‌ها و CORS

```bash
APP_BASE_URL=https://api.nobat724.com   # دامنه‌ی خودِ بک‌اند (برای callback پرداخت و URLهای مطلق)
```

`ALLOWED_FRONTEND_HOSTS` و `CORS_ALLOW_ORIGIN` از `docker/frontend-domains.json` **تولید** می‌شوند. برای اضافه/حذف دامنه‌ی شهر:

```bash
# آن فایل را ویرایش کن، سپس:
php docker/gen-cors-env.php      # روی سرور
# یا لوکال:
ddev exec php docker/gen-cors-env.php
```

خروجی را در Coolify جایگزین کن.

### ریورس‌پراکسی

```bash
TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1
```

تا Symfony به هدرهای `X-Forwarded-*` ترافیک اعتماد کند.

### api.ir (استعلام هویت — Shahkar / IbanMatch)

```bash
API_IR_BASE_URL=https://s.api.ir
API_IR_TOKEN=    # خالی => fail-closed (تأیید نماینده رد می‌شود)
```

### چیزهایی که env لازم ندارند

- کلیدهای SMS (kavenegar/rangineh) و درگاه پرداخت (mellat/sep) از **DB ("تنظیمات سایت")** خوانده می‌شوند، نه env.
- `REFRESH_TOKEN_TTL` / `OTP_TTL` / `MAX_FILE_SIZE_BYTES` در خود compose ثابت‌اند.

---

## مرحله ۵ — دیپلوی

روی **Deploy** بزن. در اولین استارت به‌صورت خودکار این‌ها اتفاق می‌افتد (`entrypoint.sh` + `RUN_INIT=1` روی سرویس `app`):

1. مالکیت `var`, `public/uploads`, `config/jwt` به `www-data` داده می‌شود.
2. منتظر آماده‌شدن DB مستقل می‌ماند (تا ~۶۰ ثانیه؛ چون دیگر `depends_on: service_healthy` نیست).
3. کلید JWT اگر روی ولوم نباشد ساخته می‌شود (`--skip-if-exists`).
4. کش prod پاک و warmup می‌شود.
5. مهاجرت‌های DB با `--all-or-nothing` اعمال می‌شوند (ترنزکشن).

> ورکرها `RUN_INIT=0` دارند تا مهاجرت/تولید کلید با هم تداخل نکنند.

---

## مرحله ۶ — پس از اولین دیپلوی

### ساخت اولین ادمین

```bash
# داخل کانتینر سرویس app
php bin/console app:create-admin
```

### بررسی سلامت

- healthcheck سرویس `app`: `docker/healthcheck.sh` route واقعی `/health` را روی پورت ۸۰۸۰ می‌زند (نه فقط چک پورت).
- workerها: healthcheck زنده‌بودن پروسه‌ی `messenger:consume` با `ps`.
- Swagger: `https://<APP_BASE_URL>/api/doc`
- پنل ادمین: `https://<APP_BASE_URL>/admin`

---

## امنیت و منابع

- **non-root:** کل استک (supervisord + php-fpm + nginx) با کاربر `www-data` اجرا می‌شود؛ nginx روی پورت غیرممتاز ۸۰۸۰. هیچ پروسه‌ای root نیست.
- **بدون secret در image/repo:** همه‌ی مقادیر حساس از تب Environment Variables کولیفای (`${...}`)؛ `.env` مخزن gitignore است و در image یک `.env` حداقلی فقط `APP_ENV=prod` ساخته می‌شود.
- **Resource Limits:** داک Coolify limits را از UI منبع می‌گیرد (نه لزوماً از compose). پیشنهاد شروع:
  - `app`: حافظه ~۵۱۲MB–۱GB، CPU ~۱
  - هر worker: حافظه ~۲۵۶MB، CPU ~۰٫۵
  در صفحه‌ی هر سرویس Coolify تنظیم کن و با مصرف واقعی تنظیم نهایی کن.

---

## دیپلوی‌های بعدی

push روی برنچ متصل (یا Deploy دستی). در هر ری‌دیپلوی:

- ایمیج دوباره build می‌شود (vendor + اسمبل فرانت‌اند multi-stage).
- مهاجرت‌های جدید روی استارت `app` اعمال می‌شوند.
- ولوم‌های استک اپ (آپلودها، کلیدهای JWT) حفظ می‌شوند. داده‌ی MariaDB/Redis در Resourceهای مستقل مستقل از این ری‌دیپلوی سالم می‌ماند.

---

## عیب‌یابی

| نشانه | علت محتمل |
|---|---|
| ارورهای CORS در فرانت | `CORS_ALLOW_ORIGIN` با دامنه نمی‌خواند؛ از `gen-cors-env.php` بازتولید کن |
| `app` با «waiting for database...» می‌ماند و بعد ۶۰ ثانیه می‌میرد | اپ به Resource مستقل MariaDB نمی‌رسد؛ چک کن: «Connect to Predefined Network» فعال است، hostname در `DATABASE_URL` درست (`mariadb-<uuid>`) و رمز/نسخه‌ی Resource درست است |
| `redis` در دسترس نیست / صف کار نمی‌کند | `REDIS_URL`/`MESSENGER_TRANSPORT_DSN` به `redis-<uuid>` درست اشاره نمی‌کند یا رمز جا افتاده |
| ۴۰۱/توکن نامعتبر بعد از ری‌دیپلوی | `JWT_PASSPHRASE` تغییر کرده یا ولوم `jwt_keys` پاک شده |
| IPها/HTTPS اشتباه پشت پراکسی | `TRUSTED_PROXIES` ست نشده |
| مهاجرت اجرا نشد | فقط `app` با `RUN_INIT=1` اجرا می‌کند؛ مطمئن شو override نشده |
| تأیید نماینده رد می‌شود | `API_IR_TOKEN` خالی است (fail-closed) |
