- گیت ورود Traefik (Basic Auth) روی همه مسیرها: سایت، API، تصاویر و چت — بدون رمز فقط ۴۰۱ - لایه دوم noindex: هدر X-Robots-Tag روی همه پاسخها + robots.txt مسدودکننده + نقشه سایت خالی - اسکریپت scripts/preview-mode.sh on|off|status — ساخت خودکار نام کاربری/رمز و هماهنگکردن .env - دو دامنه (DOMAIN + DOMAIN_ALIAS) با گواهی TLS و ریدایرکت ۳۰۱ دامنه دوم به اصلی در زمان انتشار - اسکیمای FAQPage در پوسته سئوی سمت سرور (فارسی و انگلیسی) برای خزندههای بدون جاوااسکریپت - مستندسازی جریان پیشنمایش و چکلیست انتشار در README و .env.example
140 lines
8.3 KiB
YAML
140 lines
8.3 KiB
YAML
# لایه تولید — TLS با Let's Encrypt و انتشار روی پورتهای استاندارد ۸۰/۴۳.
|
|
# پیشنیاز: متغیرهای DOMAIN و ACME_EMAIL در .env؛ بهعلاوه رکورد DNS دامنه به این سرور.
|
|
#
|
|
# bash scripts/stack.sh deploy-prod
|
|
# (معادل: docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build)
|
|
#
|
|
# همه درخواستهای HTTP به HTTPS هدایت میشوند و مسیریابیها فقط روی دامنه DOMAIN پاسخ میدهند.
|
|
# برای داشبوردها: analytics.DOMAIN (آنالیتیکس) و grafana.DOMAIN (مانیتورینگ، با فایل monitoring).
|
|
#
|
|
# ---- پیشنمایش خصوصی و انتشار (scripts/preview-mode.sh) ----
|
|
# در حالت پیشنمایش (PREVIEW_MODE=on) کل سایت پشت Basic Auth است و noindex میگیرد:
|
|
# تیم با یک نام کاربری/رمز مشترک وارد میشود، محتوا را ویرایش میکند و هیچکس دیگری —
|
|
# از جمله گوگل — به سایت دسترسی ندارد. برای انتشار عمومی:
|
|
# bash scripts/preview-mode.sh off → deploy مجدد → ثبت sitemap در Search Console.
|
|
#
|
|
# DOMAIN دامنه اصلی است و DOMAIN_ALIAS دامنه دوم (هر دو مسیر و گواهی میگیرند).
|
|
# در زمان انتشار، برای جلوگیری از محتوای تکراری، دامنه دوم را به اصلی ریدایرکت کنید:
|
|
# ALIAS_REDIRECT=on (در .env) — ریدایرکت ۳۰۱ دائمی به دامنه اصلی.
|
|
|
|
services:
|
|
traefik:
|
|
command: !override
|
|
- --providers.docker=true
|
|
- --providers.docker.exposedbydefault=false
|
|
- --providers.docker.constraints=Label(`com.docker.compose.project`, `aramesh-backend`)
|
|
- --entrypoints.web.address=:80
|
|
- --entrypoints.web.http.redirections.entrypoint.to=websecure
|
|
- --entrypoints.web.http.redirections.entrypoint.scheme=https
|
|
- --entrypoints.websecure.address=:443
|
|
- --certificatesresolvers.le.acme.email=${ACME_EMAIL:?ACME_EMAIL را در .env تنظیم کنید}
|
|
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
|
|
- --certificatesresolvers.le.acme.httpchallenge=true
|
|
- --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
|
|
- --ping=true
|
|
- --accesslog=true
|
|
- --metrics.prometheus=true
|
|
ports: !override
|
|
- "80:80"
|
|
- "443:443"
|
|
volumes: !override
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
- letsencrypt_data:/letsencrypt
|
|
|
|
# MinIO کنسول (رمز root) و API مستقیمش فقط از localhost سرور در دسترس باشند —
|
|
# از راه دور با تونل SSH: ssh -L 9001:127.0.0.1:9001 server
|
|
minio:
|
|
ports: !override
|
|
- "127.0.0.1:9001:9001"
|
|
- "127.0.0.1:19000:9000"
|
|
labels:
|
|
- traefik.enable=true
|
|
- traefik.http.routers.media.rule=Host(`${DOMAIN}`) && PathPrefix(`/media`)
|
|
- traefik.http.routers.media.entrypoints=websecure
|
|
- traefik.http.routers.media.tls.certresolver=le
|
|
- traefik.http.routers.media.priority=100
|
|
- traefik.http.routers.media.middlewares=${PREVIEW_MIDDLEWARES:+$PREVIEW_MIDDLEWARES,}media-rewrite,sec-headers
|
|
- traefik.http.middlewares.media-rewrite.replacepathregex.regex=^/media/(.*)
|
|
- traefik.http.middlewares.media-rewrite.replacepathregex.replacement=/aramesh-media/$$1
|
|
- traefik.http.services.media.loadbalancer.server.port=9000
|
|
|
|
backend:
|
|
environment:
|
|
# سایت هممبدأ است — CORS لازم نیست
|
|
CORS_ORIGINS: '[]'
|
|
SITE_URL: https://${DOMAIN:?DOMAIN را در .env تنظیم کنید}
|
|
# پیشنمایش خصوصی — هدر noindex + robots مسدودکننده (لایه دوم؛ لایه اول گیت Traefik)
|
|
PREVIEW_MODE: ${PREVIEW_MODE:-off}
|
|
labels:
|
|
- traefik.enable=true
|
|
- traefik.http.routers.api.rule=Host(`${DOMAIN}`) && PathPrefix(`/api`)
|
|
- traefik.http.routers.api.entrypoints=websecure
|
|
- traefik.http.routers.api.tls.certresolver=le
|
|
- traefik.http.routers.api.middlewares=${PREVIEW_MIDDLEWARES:+$PREVIEW_MIDDLEWARES,}api-flood,sec-headers
|
|
- traefik.http.routers.seo.rule=Host(`${DOMAIN}`) && (PathPrefix(`/blog/`) || Path(`/blog`) || PathPrefix(`/projects/`) || Path(`/projects`) || Path(`/en`) || PathPrefix(`/en/`) || PathPrefix(`/robots.txt`) || PathPrefix(`/sitemap.xml`))
|
|
- traefik.http.routers.seo.entrypoints=websecure
|
|
- traefik.http.routers.seo.tls.certresolver=le
|
|
- traefik.http.routers.seo.middlewares=${PREVIEW_MIDDLEWARES:+$PREVIEW_MIDDLEWARES,}sec-headers
|
|
|
|
frontend:
|
|
labels:
|
|
- traefik.enable=true
|
|
- traefik.http.routers.web.rule=Host(`${DOMAIN}`) && PathPrefix(`/`)
|
|
- traefik.http.routers.web.entrypoints=websecure
|
|
- traefik.http.routers.web.tls.certresolver=le
|
|
- traefik.http.routers.web.middlewares=${PREVIEW_MIDDLEWARES:+$PREVIEW_MIDDLEWARES,}sec-headers
|
|
# تعریف یکبارهٔ میانافزار هدرهای امنیتی — روی همهٔ روترهای websecure استفاده میشود
|
|
- traefik.http.middlewares.sec-headers.headers.stsSeconds=31536000
|
|
- traefik.http.middlewares.sec-headers.headers.stsIncludeSubdomains=true
|
|
- traefik.http.middlewares.sec-headers.headers.stsPreload=true
|
|
- traefik.http.middlewares.sec-headers.headers.contentTypeNosniff=true
|
|
# ---- گیت پیشنمایش خصوصی ----
|
|
# ورود با نام کاربری/رمز مشترک (htpasswd) — تا وقتی PREVIEW_MIDDLEWARES تنظیم است فعال میشود
|
|
- traefik.http.middlewares.preview-auth.basicauth.users=${PREVIEW_AUTH_USERS:-disabled:nope}
|
|
- traefik.http.middlewares.preview-auth.basicauth.realm=Aramesh private preview
|
|
# لایه دوم جلوگیری از ایندکس — حتی اگر گیت حذف شد، گوگل چیزی ذخیره نکند
|
|
- traefik.http.middlewares.preview-noindex.headers.customresponseheaders.X-Robots-Tag=noindex, nofollow, noarchive
|
|
# ---- ریدایرکت دامنه دوم به اصلی (زمان انتشار: ALIAS_REDIRECT=on) ----
|
|
- traefik.http.middlewares.to-primary.redirectregex.regex=^https?://[^/]+(.*)
|
|
- traefik.http.middlewares.to-primary.redirectregex.replacement=https://${DOMAIN}$$1
|
|
- traefik.http.middlewares.to-primary.redirectregex.permanent=true
|
|
- traefik.http.routers.alias-redirect.rule=Host(`${DOMAIN_ALIAS:-invalid.invalid}`)
|
|
- traefik.http.routers.alias-redirect.entrypoints=websecure
|
|
- traefik.http.routers.alias-redirect.tls.certresolver=le
|
|
- traefik.http.routers.alias-redirect.priority=1000
|
|
- traefik.http.routers.alias-redirect.middlewares=${ALIAS_REDIRECT_MIDDLEWARES:-sec-headers}
|
|
- traefik.http.routers.alias-redirect.service=web
|
|
|
|
chatbot:
|
|
labels:
|
|
- traefik.enable=true
|
|
- traefik.http.routers.chat.rule=Host(`${DOMAIN}`) && PathPrefix(`/chat`)
|
|
- traefik.http.routers.chat.entrypoints=websecure
|
|
- traefik.http.routers.chat.tls.certresolver=le
|
|
- traefik.http.routers.chat.priority=100
|
|
- traefik.http.routers.chat.middlewares=${PREVIEW_MIDDLEWARES:+$PREVIEW_MIDDLEWARES,}api-flood,sec-headers
|
|
|
|
umami:
|
|
labels:
|
|
- traefik.enable=true
|
|
- traefik.http.routers.analytics.rule=Host(`analytics.${DOMAIN}`)
|
|
- traefik.http.routers.analytics.entrypoints=websecure
|
|
- traefik.http.routers.analytics.tls.certresolver=le
|
|
- traefik.http.routers.analytics.middlewares=sec-headers
|
|
- traefik.http.services.analytics.loadbalancer.server.port=3000
|
|
|
|
# داشبورد Grafana (docker-compose.monitoring.yml) در تولید با TLS نیاز به روتر
|
|
# Host(`grafana.DOMAIN`) دارد — چون آن سرویس فقط با فایل monitoring بالا میآید،
|
|
# برچسبهایش را در override سرور بگذارید (README، بخش مانیتورینگ):
|
|
# docker-compose.override.yml روی سرور:
|
|
# services:
|
|
# grafana:
|
|
# labels:
|
|
# - traefik.http.routers.grafana.rule=Host(`grafana.YOURDOMAIN`)
|
|
# - traefik.http.routers.grafana.entrypoints=websecure
|
|
# - traefik.http.routers.grafana.tls.certresolver=le
|
|
# - traefik.http.services.grafana.loadbalancer.server.port=3000
|
|
|
|
volumes:
|
|
letsencrypt_data:
|