diff --git a/.env.example b/.env.example index c8adc17..e1bb8c4 100644 --- a/.env.example +++ b/.env.example @@ -3,7 +3,6 @@ DJANGO_SECRET_KEY=replace-with-a-unique-random-secret-of-at-least-50-characters DJANGO_ALLOWED_HOSTS=support.example.com CSRF_TRUSTED_ORIGINS=https://support.example.com PUBLIC_BASE_URL=https://support.example.com -ASSET_HOST=connect.loolegostar.com WEBOTP_HOST=support.example.com TIME_ZONE=Asia/Tehran diff --git a/docker-compose.yml b/docker-compose.yml index b18292f..afeb158 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,6 @@ x-django-service: &django-service DJANGO_ALLOWED_HOSTS: ${DJANGO_ALLOWED_HOSTS:?Set DJANGO_ALLOWED_HOSTS in Coolify} CSRF_TRUSTED_ORIGINS: ${CSRF_TRUSTED_ORIGINS:?Set CSRF_TRUSTED_ORIGINS in Coolify} PUBLIC_BASE_URL: ${PUBLIC_BASE_URL:?Set PUBLIC_BASE_URL in Coolify} - ASSET_HOST: ${ASSET_HOST:-connect.loolegostar.com} WEBOTP_HOST: ${WEBOTP_HOST:-} TIME_ZONE: ${TIME_ZONE:-Asia/Tehran} DATABASE_URL: ${DATABASE_URL:-} @@ -87,22 +86,6 @@ services: restart: unless-stopped networks: - coolify - labels: - - "traefik.enable=true" - - "traefik.docker.network=coolify" - - "traefik.http.middlewares.customer-ticket-assets-redirect-${COOLIFY_RESOURCE_UUID:-local}.redirectscheme.scheme=https" - - "traefik.http.routers.customer-ticket-assets-http-${COOLIFY_RESOURCE_UUID:-local}.entrypoints=http" - - "traefik.http.routers.customer-ticket-assets-http-${COOLIFY_RESOURCE_UUID:-local}.rule=Host(`${ASSET_HOST:-connect.loolegostar.com}`) && (PathPrefix(`/static/`) || PathPrefix(`/media/branding/`) || PathPrefix(`/protected-media/`))" - - "traefik.http.routers.customer-ticket-assets-http-${COOLIFY_RESOURCE_UUID:-local}.priority=1000" - - "traefik.http.routers.customer-ticket-assets-http-${COOLIFY_RESOURCE_UUID:-local}.middlewares=customer-ticket-assets-redirect-${COOLIFY_RESOURCE_UUID:-local}" - - "traefik.http.routers.customer-ticket-assets-http-${COOLIFY_RESOURCE_UUID:-local}.service=customer-ticket-assets-${COOLIFY_RESOURCE_UUID:-local}" - - "traefik.http.routers.customer-ticket-assets-https-${COOLIFY_RESOURCE_UUID:-local}.entrypoints=https" - - "traefik.http.routers.customer-ticket-assets-https-${COOLIFY_RESOURCE_UUID:-local}.rule=Host(`${ASSET_HOST:-connect.loolegostar.com}`) && (PathPrefix(`/static/`) || PathPrefix(`/media/branding/`) || PathPrefix(`/protected-media/`))" - - "traefik.http.routers.customer-ticket-assets-https-${COOLIFY_RESOURCE_UUID:-local}.priority=1000" - - "traefik.http.routers.customer-ticket-assets-https-${COOLIFY_RESOURCE_UUID:-local}.service=customer-ticket-assets-${COOLIFY_RESOURCE_UUID:-local}" - - "traefik.http.routers.customer-ticket-assets-https-${COOLIFY_RESOURCE_UUID:-local}.tls=true" - - "traefik.http.routers.customer-ticket-assets-https-${COOLIFY_RESOURCE_UUID:-local}.tls.certresolver=letsencrypt" - - "traefik.http.services.customer-ticket-assets-${COOLIFY_RESOURCE_UUID:-local}.loadbalancer.server.port=80" expose: - "80" depends_on: diff --git a/docs/DEPLOYMENT_COOLIFY.md b/docs/DEPLOYMENT_COOLIFY.md index 93a51ad..d8d0a7d 100644 --- a/docs/DEPLOYMENT_COOLIFY.md +++ b/docs/DEPLOYMENT_COOLIFY.md @@ -24,9 +24,17 @@ customer_ticket_seed ## 2. دامنه‌ها و Traefik خود Coolify -دامنه اصلی HTTPS را در Coolify مستقیماً به سرویس `web` و پورت `8000` متصل کنید. مقدار Domain سرویس web باید دقیقاً یک URL و بدون فاصله یا کامای انتهایی باشد. Traefik خود Coolify TLS، reverse proxy و هدر `X-Forwarded-Proto` را مدیریت می‌کند؛ Nginx جلوی Django قرار نمی‌گیرد. +این راهنما برای حالت عادی **Git-based Docker Compose Application** است، نه گزینه `Raw Compose Deployment`. در این حالت Coolify باید routeهای Traefik را از فیلدهای Domains بسازد؛ routerهای کامل Traefik را به‌صورت دستی داخل Compose تعریف نکنید. -فیلد Domain سرویس‌های `nginx` و `notification-worker` را در UI کاملاً خالی بگذارید. Compose برای Nginx routerهای صریح HTTP/HTTPS با priority برابر 1000 می‌سازد و `/static/`، `/media/branding/` و `/protected-media/` را بدون StripPrefix به پورت 80 می‌فرستد. hostname این ruleها از `ASSET_HOST` خوانده می‌شود. معماری containerها همچنان بدون `proxy_pass` بین Nginx و Django است. +در `Configuration > General` مقدارها را دقیقاً به شکل زیر وارد کنید: + +- `Domains for web`: `https://connect.loolegostar.com:8000` +- `Domains for notification-worker`: خالی +- `Domains for nginx`: `cc` + +در `Configuration > Advanced` گزینه `Strip Prefixes` را **خاموش** کنید. در غیر این صورت Coolify پیشوند `/static` یا `/media/branding` را قبل از تحویل درخواست حذف می‌کند، در حالی که `docker/nginx.conf` عمداً همان مسیرهای کامل را match می‌کند. بعد از Save، گزینه `Reload Compose File` و سپس Redeploy را اجرا کنید. + +Traefik خود Coolify TLS، reverse proxy و اولویت routeهای path-based را مدیریت می‌کند؛ Nginx جلوی Django قرار نمی‌گیرد و فقط فایل‌های static و media را سرو می‌کند. فایل‌های آپلودی تیکت عمومی نیستند: Django ابتدا مجوز را بررسی می‌کند و سپس کاربر را با یک URL امضاشده پنج‌دقیقه‌ای به `/protected-media/` روی Nginx می‌فرستد. branding عمومی زیر `/media/branding/` مستقیماً از Nginx سرو می‌شود. @@ -122,7 +130,7 @@ SECURE_HSTS_PRELOAD=false 1. volumeها و database خالی را ایجاد کنید. 2. طبق `docs/PRODUCTION_SEED.md` سه فایل seed را داخل volume `customer_ticket_seed` قرار دهید. 3. Environment Variables را در Coolify وارد کنید. -4. `ASSET_HOST` را روی hostname عمومی بگذارید، Domain سرویس web را بدون کامای انتهایی تنظیم کنید و Domainهای nginx/worker را خالی نگه دارید؛ سپس Compose را deploy کنید. +4. Domain سرویس web و سه Domain مسیرمحور nginx را مطابق بخش ۲ وارد، `Strip Prefixes` را خاموش و Domain سرویس worker را خالی نگه دارید؛ سپس Compose را deploy کنید. 5. log سرویس `web` را برای موفقیت migrate، import production seed، collectstatic و Gunicorn بررسی کنید. وقتی seed موجود است، bootstrap عمداً اجرا نمی‌شود تا snapshot تغییر نکند. 6. log سرویس `notification-worker` را بررسی کنید؛ container باید Running بماند. 7. با superuser منتقل‌شده از SQLite وارد شوید و با گیرنده آزمایشی مجاز یک SMS و یک پیام Bale واقعی بفرستید. diff --git a/tests/test_deployment.py b/tests/test_deployment.py index 6affae6..0ca7a48 100644 --- a/tests/test_deployment.py +++ b/tests/test_deployment.py @@ -45,11 +45,11 @@ def test_nginx_serves_assets_without_proxying_django(): assert "media_data:/app/media:ro" in compose assert "notification-worker:" in compose assert " db:" not in compose - assert "PathPrefix(`/static/`)" in compose - assert "PathPrefix(`/media/branding/`)" in compose - assert "PathPrefix(`/protected-media/`)" in compose - assert "priority=1000" in compose - assert "stripprefix" not in compose.lower() + # Normal Git-based Coolify applications generate proxy routes from the + # service Domains fields. Hand-maintained Traefik routers belong to Raw + # Compose deployments and must not compete with Coolify's generated routes. + assert "traefik.http.routers" not in compose + assert "traefik.docker.network" not in compose assert "Set MEDIA_SIGNING_SECRET in Coolify" not in compose