Files
clinicpro/docs/DEPLOY.md
T

168 lines
7.6 KiB
Markdown

# راهنمای دیپلوی ClinicPro (Coolify + Docker Compose)
این راهنما برای دیپلوی بک‌اند ClinicPro روی **Coolify** با استفاده از `docker-compose.yml` نوشته شده.
> توجه: این `docker-compose.yml` فقط برای **production** است. محیط لوکال از `compose.yaml` خودِ ddev استفاده می‌کند — این دو را با هم اشتباه نگیر.
---
## معماری استک
`docker-compose.yml` پنج سرویس بالا می‌آورد:
| سرویس | نقش | نکته |
|---|---|---|
| `app` | PHP-FPM + Nginx (وب) | تنها سرویسی که `RUN_INIT=1` دارد؛ مهاجرت DB و تولید کلید JWT را اجرا می‌کند. دامنه‌ها را به این سرویس (پورت 80) وصل کن. |
| `worker-async` | مصرف‌کننده صف async (SMS و کارهای async) | `messenger:consume async` |
| `worker-scheduler` | زمان‌بند | هر ۱ دقیقه نوبت‌های پرداخت‌نشده را منقضی می‌کند |
| `mariadb` | پایگاه‌داده MariaDB 11.8 | healthcheck دارد؛ بقیه منتظرش می‌مانند |
| `redis` | صف Messenger + کش | `appendonly yes` (ماندگار) |
**ولوم‌های ماندگار** (داده‌ها در ری‌دیپلوی حفظ می‌شوند):
- `jwt_keys` → کلیدهای JWT
- `uploads_public` و `uploads_var` → فایل‌های آپلودی
- `mariadb_data` → داده DB
- `redis_data` → داده Redis
---
## پیش‌نیازها
- نمونه‌ی Coolify در حال اجرا با Traefik (پیش‌فرض Coolify).
- ریپوی Git متصل به Coolify.
- رکوردهای DNS برای دامنه‌ی API و همه‌ی دامنه‌های فرانت‌اند که به سرور اشاره کنند.
---
## مرحله ۱ — ساخت منبع (Resource) در Coolify
1. **New Resource → Docker Compose** (Build Pack: `Docker Compose`).
2. ریپو و برنچ را انتخاب کن.
3. فیلد **Compose file** را روی `docker-compose.yml` بگذار.
4. `networks:` سفارشی تعریف **نکن** — شبکه را Coolify مدیریت می‌کند؛ شبکه‌ی سفارشی روتینگ Traefik را می‌شکند.
---
## مرحله ۲ — دامنه‌ها
همه‌ی دامنه‌های سرو شونده را به سرویس **`app`** (پورت 80) اختصاص بده — هم دامنه‌ی API و هم همه‌ی دامنه‌های فرانت‌اند. Coolify لیست دامنه‌ی جدا‌شده با کاما را روی یک سرویس قبول می‌کند و TLS را خودش صادر می‌کند.
> اجازه‌دادن CORS و host فرانت‌اندها از طریق متغیرهای `CORS_ALLOW_ORIGIN` / `ALLOWED_FRONTEND_HOSTS` کنترل می‌شود، نه دامنه‌ی Coolify.
---
## مرحله ۳ — متغیرهای محیطی
از `.env.coolify.example` کپی کن و در تب **Environment Variables** منبع Coolify بگذار.
فقط متغیرهایی که در `docker-compose.yml` به‌صورت `${...}` ارجاع شده‌اند لازم‌اند. `DATABASE_URL` / `MESSENGER_TRANSPORT_DSN` / `REDIS_URL` داخل خود compose از روی نام سرویس‌ها ساخته می‌شوند.
### اسرار (الزامی — قبل از اولین دیپلوی)
```bash
APP_SECRET= # php -r "echo bin2hex(random_bytes(32));"
JWT_PASSPHRASE= # openssl rand -hex 32 (باید قبل از اولین استارت موجود باشد؛ کلید JWT با همین ساخته می‌شود)
DB_PASSWORD= # رمز کاربر DB اپلیکیشن
DB_ROOT_PASSWORD= # رمز root مریادی‌بی
```
> ⚠️ `JWT_PASSPHRASE` را بعد از اولین دیپلوی عوض نکن — کلید JWT یک‌بار با همین passphrase تولید و روی ولوم `jwt_keys` ماندگار می‌شود. تغییرش همه‌ی توکن‌ها را می‌شکند.
در Coolify می‌توانی به‌جای هاردکد از magic var استفاده کنی:
```bash
DB_PASSWORD=${SERVICE_PASSWORD_DB}
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. کلید JWT اگر روی ولوم نباشد ساخته می‌شود (`--skip-if-exists`).
3. کش prod پاک و warmup می‌شود.
4. مهاجرت‌های DB با `--all-or-nothing` اعمال می‌شوند (ترنزکشن).
> ورکرها `RUN_INIT=0` دارند تا مهاجرت/تولید کلید با هم تداخل نکنند.
---
## مرحله ۵ — پس از اولین دیپلوی
### ساخت اولین ادمین
```bash
# داخل کانتینر سرویس app
php bin/console app:create-admin
```
### بررسی سلامت
- healthcheck سرویس `app`: `php fsockopen 127.0.0.1:80`.
- Swagger: `https://<APP_BASE_URL>/api/doc`
- پنل ادمین: `https://<APP_BASE_URL>/admin`
---
## دیپلوی‌های بعدی
push روی برنچ متصل (یا Deploy دستی). در هر ری‌دیپلوی:
- ایمیج دوباره build می‌شود (vendor + اسمبل فرانت‌اند multi-stage).
- مهاجرت‌های جدید روی استارت `app` اعمال می‌شوند.
- ولوم‌ها حفظ می‌شوند (DB، آپلودها، کلیدهای JWT، Redis سالم می‌مانند).
---
## عیب‌یابی
| نشانه | علت محتمل |
|---|---|
| ارورهای CORS در فرانت | `CORS_ALLOW_ORIGIN` با دامنه نمی‌خواند؛ از `gen-cors-env.php` بازتولید کن |
| `app` بالا نمی‌آید، منتظر DB می‌ماند | healthcheck `mariadb` رد نشده؛ لاگ mariadb را ببین |
| ۴۰۱/توکن نامعتبر بعد از ری‌دیپلوی | `JWT_PASSPHRASE` تغییر کرده یا ولوم `jwt_keys` پاک شده |
| IPها/HTTPS اشتباه پشت پراکسی | `TRUSTED_PROXIES` ست نشده |
| مهاجرت اجرا نشد | فقط `app` با `RUN_INIT=1` اجرا می‌کند؛ مطمئن شو override نشده |
| تأیید نماینده رد می‌شود | `API_IR_TOKEN` خالی است (fail-closed) |