aramesh-site deploy 1b571387df perf(home): متن هیرو بلافاصله دیده شود — تاخیرهای ۲.۱-۲.۶ ثانیه حذف و درخواست‌های داده موازی
- انیمیشن ورود هیرو تاخیر ۲.۱ تا ۲.۶ ثانیه داشت (بعد از بارگذاری JS+API هم
  متن ۳ ثانیه نامرئی بود)؛ حالا ورود نرم با تاخیر ۰.۰۵-۰.۳۶ ثانیه
- SiteProvider: /api/site و /api/admin/articles پشت سر هم fetch می‌شدند و
  setData منتظر هر دو می‌ماند؛ حالا همزمان شروع می‌شوند
2026-09-28 11:27:59 +00:00

گروه ساختمانی آرامش — پروژه کامل (فرانت‌اند + بک‌اند)

مونوریپوی سایت گروه ساختمانی آرامش شامل:

  • frontend/ — سایت React + Vite + Tailwind (تمام متن‌ها و تصاویر از API خوانده می‌شود)
  • backend/ — بک‌اند FastAPI + SQLAlchemy 2 + PostgreSQL (pgvector) + JWT + MinIO
  • chatbot/ — دستیار هوشمند (FastAPI + LangGraph + RAG با pgvector، پاسخ استریمی)
  • مانیتورینگ و آنالیتیکس — Prometheus/Grafana/Loki و Umami به‌صورت لایه‌های compose

معماری

Traefik (تنها نقطه ورود — 80/443 در تولید)
├── /            →  Caddy (فایل‌های استاتیک SPA)
├── /api         →  FastAPI (بک‌اند)          + متریک /metrics فقط داخلی
├── /media       →  MinIO (باکت aramesh-media)
├── /chat        →  چت‌بات (SSE استریم پاسخ)
├── grafana.*    →  Grafana (مانیتورینگ — لایه monitoring)
└── analytics.*  →  Umami (آنالیتیکس محصول)
  • همه محتوای متنی سایت در PostgreSQL است — هیچ متنی در کد فرانت‌اند هاردکد نشده؛ با یک درخواست GET /api/site بارگذاری و از پنل مدیریت ویرایش می‌شود
  • تصاویر آپلودی در MinIO (Object Storage سازگار با S3) ذخیره می‌شوند و با مسیر نسبی /media/... برگردانده می‌شوند — بک‌اند ذخیره‌سازی بدون تغییر محتوای دیتابیس قابل تعویض است
  • مسیریابی همه‌چیز روی Traefik است (با Docker labels) — nginx در پروژه وجود ندارد
  • شبکه‌های compose تفکیک شده‌اند: edge (سرویس‌های پشت Traefik) و internal (دیتابیس/ذخیره‌سازی) — فرانت‌اند هیچ راه مستقیمی به دیتابیس ندارد
  • لاگ‌ها: json-file با سقف ۱۰MB×۳ برای هر سرویس؛ علاوه بر آن Promtail همه را به Loki می‌فرستد تا در Grafana جست‌وجو شوند

ساختار

├── frontend/                  # سایت React (بیلد روی هاست — ایمیج فقط سرو می‌کند)
│   ├── src/                   # کد منبع (api.tsx = لایه اتصال به بک‌اند)
│   ├── Caddyfile              # سرو استاتیک + کش immutable برای /assets/*
│   └── Dockerfile             # Caddy — بدون root
├── backend/                   # بک‌اند FastAPI
│   ├── app/                   # کد منبع (آپلود: app/api/admin.py — سئو: app/seo_pages.py)
│   ├── migrations/            # مهاجرت‌های نسخه‌دار Alembic
│   ├── tests/                 # تست‌های API (pytest)
│   ├── alembic.ini
│   └── Dockerfile             # بدون root — خاموشی تمیز با exec
├── chatbot/                   # دستیار هوشمند (RAG + pgvector)
├── monitoring/                # پیکربندی Prometheus/Alertmanager/Loki/Promtail/Grafana
├── scripts/
│   ├── stack.sh               # ابزار اصلی عملیات — deploy/logs/health/backup/restore/update
│   ├── backup.sh              # پشتیبان کامل (DB + تصاویر) با چک‌سام و گزینه انتشار بیرونی
│   ├── restore.sh             # بازگردانی پشتیبان دیتابیس
│   ├── gen-env.sh             # ساخت .env با رمزهای قوی تصادفی
│   ├── audit.sh               # ممیزی آسیب‌پذیری وابستگی‌ها و ایمیج‌ها
│   ├── migrate-media.py       # انتقال یک‌باره تصاویر CDN خارجی به MinIO
│   └── ci.sh                  # تست + بیلد ایمیج (محلی یا هر CI)
├── docker-compose.yml         # استک اصلی (شامل چت‌بات و Umami)
├── docker-compose.monitoring.yml  # لایه مانیتورینگ
├── docker-compose.prod.yml    # لایه تولید: TLS، دامنه، سخت‌سازی پورت‌ها
└── docker-compose.override.yml    # تنظیمات محلی این ماشین (در گیت نیست)

اجرا با Docker

پیش‌نیاز: Docker Desktop. بیلد فرانت‌اند روی هاست انجام می‌شود (کانتینر فقط فایل‌های dist/ را سرو می‌کند) — scripts/stack.sh همین را به یک دستور تبدیل می‌کند:

cp .env.example .env            # یا: bash scripts/gen-env.sh (رمزهای قوی تصادفی)
cd frontend && npm install && cd ..
bash scripts/stack.sh deploy    # بیلد فرانت + بیلد و بالا آوردن استک + انتظار برای سلامت
  • سایت: http://localhost:18081 (پورت از TRAEFIK_HTTP_PORT در .env) — ورود مدیریت: /admin/login
  • دستورهای عملیات: bash scripts/stack.sh — logs، ps، health، backup، restore، update، seed-force
  • مستندات API: http://localhost:19001/docs (با docker-compose.override.yml محلی)
  • کنسول MinIO: http://localhost:9001 — کاربر/رمز از متغیرهای MINIO_ROOT_* در .env
  • دیتابیس PostgreSQL (تصویر pgvector)، آبجکت‌استور MinIO و کش مدل‌های چت‌بات هر یک در کانتینر جدا با volume دائمی
  • بک‌اند هنگام شروع، جداول پایه را می‌سازد، مهاجرت‌های Alembic را اعمال می‌کند و فقط جدول‌های خالی را seed می‌کند — ارتقای نسخه داده‌های فعلی را از بین نمی‌برد
  • minio-setup باکت aramesh-media را می‌سازد و فقط وقتی باکت خالی است تصاویر seed-media/ را می‌ریزد — db-init هم دیتابیس‌های umami و aramesh_chatbot را روی همان نمونهٔ Postgres می‌سازد

نکته برای تازه‌واردها: هیچ فایل رمزدار در گیت نیست. فقط .env.example را کپی کنید؛ فایل‌های .env/env در .gitignore هستند و هرگز commit نشوند.

پایهٔ API فرانت‌اند در زمان بیلد از VITE_API_URL در .env خوانده می‌شود — در توسعه ویندوزیِ این ماشین VITE_API_URL=http://localhost:19001 بگذارید.

انتشار واقعی (TLS)

پیش‌نیاز: رکوردهای DNS (example.com، analytics.example.com و اگر مانیتورینگ فعال است grafana.example.com) به این سرور، سپس در .env: DOMAIN، ACME_EMAIL و SITE_URL=https://example.com:

bash scripts/stack.sh deploy-prod
  • Traefik گواهی Let's Encrypt می‌گیرد و تمدید می‌کند؛ همه درخواست‌های HTTP به HTTPS هدایت می‌شوند
  • پورت‌های مدیریتی MinIO (۹۰۰۱ کنسول و ۱۹۰۰۰ API) در تولید فقط روی 127.0.0.1 بسته می‌شوند — دسترسی از راه دور فقط با تونل SSH: ssh -L 9001:127.0.0.1:9001 server
  • همه روترهای websecure هدرهای امنیتی می‌گیرند (HSTS + nosniff)

پیش‌نمایش خصوصی (کار روی سایت، قبل از انتشار عمومی)

سایت روی اینترنت بالا می‌آید و تیم با همان پنل مدیریت محتوا را ویرایش می‌کند (ویرایش درجای ایمیل/تلفن/متن‌ها و نوشتن مقاله)، اما نه عموم به آن دسترسی دارند و نه گوگل چیزی ایندکس می‌کند. دو لایه محافظت:

  1. گیت ورود Traefik (Basic Auth) — همه مسیرها (سایت، API، تصاویر و چت) پشت یک نام کاربری/رمز مشترک‌اند؛ بدون آن فقط ۴۰۱ برمی‌گردد
  2. noindex — هدر X-Robots-Tag روی همه پاسخ‌ها + robots.txt مسدودکننده + نقشه سایت خالی؛ حتی اگر گیت برداشته شود، گوگل چیزی ذخیره نمی‌کند
bash scripts/preview-mode.sh on     # فعال‌سازی (بار اول نام کاربری/رمز می‌سازد و چاپ می‌کند)
bash scripts/stack.sh deploy-prod   # اعمال
bash scripts/preview-mode.sh status # وضعیت فعلی
  • دامنه اصلی (DOMAIN) و دامنه دوم (DOMAIN_ALIAS) هر دو سرو می‌شوند و هر دو گیت دارند
  • برای دادن دسترسی به کارفرما، همان نام کاربری/رمز مشترک را بدهید
  • رمز مشترک در .env (PREVIEW_AUTH_USERS) می‌ماند — فقط یک‌بار چاپ می‌شود

انتشار عمومی (برداشتن پیش‌نمایش)

bash scripts/preview-mode.sh off    # حذف گیت ورود + اجازه ایندکس
bash scripts/stack.sh deploy-prod

بعد از این مرحله:

  1. دامنه دوم به‌صورت ۳۰۱ دائمی به دامنه اصلی ریدایرکت می‌شود (جلوگیری از محتوای تکراری)
  2. در Search Console دامنه را تأیید و https://DOMAIN/sitemap.xml را ثبت کنید
  3. اگر پیش‌تر چیزی ایندکس شده بود، حذفش را از همان کنسول درخواست کنید

چک‌لیست رفتن به تولید

  1. bash scripts/gen-env.sh — رمزهای قوی جدید (رمزهای .env توسعه را کپی نکنید)
  2. DNS: رکوردهای دامنه و زیردامنه‌ها + باز بودن پورت‌های ۸۰/۴۴۳ در فایروال
  3. DOMAIN، DOMAIN_ALIAS (دامنه دوم)، ACME_EMAIL، SITE_URL را در .env تنظیم کنید
  4. bash scripts/preview-mode.sh on — اولین انتشار به‌صورت پیش‌نمایش خصوصی می‌رود؛ محتوا را روی سایت آنلاین نهایی کنید و بعد preview-mode.sh off + deploy مجدد
  5. python scripts/migrate-media.py — انتقال یک‌باره تصاویر CDN خارجی به MinIO
  6. پشتیبان‌گیری خودکار: docker compose --profile ops up -d db-backup (پشتیبان ساعتی)
    • scripts/backup.sh شبانه با cron/Task Scheduler
  7. یک تمرین بازگردانی: bash scripts/restore.sh backups/db-<زمان>.sql.gz روی محیط آزمایشی
  8. مانیتورینگ (بخش پایین) و سپس چت‌بات/آنالیتیکس را با متغیرهای LLM و Umami فعال کنید

مانیتورینگ (Prometheus + Grafana + Loki)

bash scripts/stack.sh monitoring
# در تولید (سه فایل با هم):
docker compose -f docker-compose.yml -f docker-compose.monitoring.yml -f docker-compose.prod.yml up -d
  • داشبورد: grafana.localhost (توسعه) یا grafana.example.com (تولید — برچسب‌های روتر آن را در docker-compose.override.yml سرور بگذارید؛ نمونه در انتهای docker-compose.prod.yml) — کاربر/رمز از GRAFANA_ADMIN_* در .env
  • دو داشبورد آمادهٔ «زیرساخت» و «اپلیکیشن» به‌صورت code-provisioned سوار می‌شوند
  • هشدارها به بله: در .env مقدار ALERT_BOT_TOKEN (بات بله — همان شکل API تلگرام، از BotFather بله) و ALERT_CHAT_ID را بگذارید (شناسه چت: به بات یک پیام بدهید سپس curl "https://tapi.bale.ai/bot<توکن>/getUpdates") — قواعد آماده در monitoring/prometheus/alerts.yml: پایین‌بودن هر سرویس، شکست بررسی عمق سلامت، پرشدن دیسک، نرخ خطای 5xx، انقضای گواهی TLS، کهنه‌شدن پشتیبان (سنجهٔ backup_success_time از scripts/backup.sh) و...
  • لاگ‌ها در Grafana: Promtail لاگ همهٔ کانتینرهای استک را به Loki می‌فرستد — برای خطاهای بک‌اند از داشبورد «اپلیکیشن» پنل لاگ را ببینید
  • سلامت واقعی: /api/health اتصال DB و MinIO را واقعاً می‌آزماید (503 در خرابی)؛ پروب blackbox هم همین را از بیرون و نشانی SITE_URL را عمومی چک می‌کند
  • مصرف منابع مجموع لایه حدود ۱ تا ۱.۵ گیگابایت RAM است؛ حذف هر سرویس بقیه را نمی‌شکند

آنالیتیکس محصول (Umami)

استک شامل سرویس Umami است؛ داشبورد: analytics.localhost (توسعه) یا analytics.example.com (تولید). بدون کوکی — بدون نیاز به بنر رضایت.

راه‌اندازی (یک‌بار):

  1. استک را بالا بیاورید و به analytics.localhost وارد شوید (پیش‌فرض admin/umami — بلافاصله رمز را عوض کنید)
  2. یک وب‌سایت با دامنهٔ سایت بسازید؛ «شناسه وب‌سایت» را در .env بگذارید: VITE_UMAMI_WEBSITE_ID=... و آدرس اسکریپت: VITE_UMAMI_SRC=https://analytics.example.com/script.js
  3. bash scripts/stack.sh deploy — اسکریپت ردیاب در index.html تزریق می‌شود

رخدادهای سفارشی که خودکار ارسال می‌شوند: ارسال فرم تماس (contact-submit)، عضویت خبرنامه (newsletter-signup)، کلیک تلفن (phone-click)، باز کردن چت‌بات (chat-open)، پرسش از چت‌بات (chat-question)، تغییر زبان (lang-switch) — سرچشمه: frontend/src/analytics.ts.

چت‌بات (دستیار هوشمند)

  • RAG روی محتوای همان دیتابیس سایت: امبدینگ MiniLM چندزبانه + BM25 + رنکر — بردارها در دیتابیس جدا aramesh_chatbot (pgvector) روی همان نمونهٔ Postgres
  • مسیرها: GET /chat/healthz، GET /chat/suggestions، GET /chat/stream (SSE) — رابط کاربری: ویجت شناور پایین صفحه (ChatWidget.tsx)
  • راه‌اندازی: LLM_BASE_URL/LLM_API_KEY/LLM_MODEL در .env (هر LLM سازگار با OpenAI) — بدون آن سرویس بالا می‌آید اما پاسخ نمی‌دهد
  • اولین اجرا مدل‌ها را دانلود می‌کند (~۵۰۰MB در حجم chatbot_hf) — کند است، طبیعی است
  • ایندکس هر ۱۰ دقیقه خودش با تغییرهای محتوا همگام می‌شود؛ همگام‌سازی فوری با POST /chat/reindex و هدر x-reindex-token (از CHAT_REINDEX_TOKEN)

امکانات بک‌اند

  • API عمومی: برند، منو، هیرو، خدمات، پروژه‌ها، مقالات، نظرات، سوالات متداول، تیم، تایم‌لاین، آمار، دفاتر، مدل‌های سرمایه‌گذاری و... — همه در دیتابیس
  • فرم تماس POST /api/contact و خبرنامه POST /api/newsletter — هر دو با محدودسازی نرخ (تماس: ۵ در ۱۵ دقیقه، خبرنامه: ۳ در ساعت برای هر IP) + دامنهٔ پنهان (honeypot) ضدربات + میان‌افزار محدودسازی سیلاب روی Traefik
  • پنل مدیریت با JWT: ورود POST /api/auth/login (مقادیر از ADMIN_* در .env — هیچ رمز پیش‌فرضی در کد وجود ندارد) و سپس CRUD کامل همه محتواها در /api/admin/...
  • تغییر رمز POST /api/auth/change-password — از نوار مدیریت پایین سایت (دکمه «رمز») هم در دسترس است
  • محدودسازی ورود: بعد از ۵ ورود ناموفق در ۱۵ دقیقه از یک IP، درخواست با 429 رد می‌شود (IP واقعی کلاینت با --proxy-headers از پشت Traefik درست خوانده می‌شود)
  • آپلود تصویر POST /api/admin/upload (فقط توکن ادمین، حداکثر ۱۰MB) — فایل در MinIO ذخیره و URL نسبی /media/... برمی‌گردد؛ در همان لحظه تا ۲۰۰۰ پیکسل کوچک و دوباره فشرده می‌شود (Pillow) تا سرعت صفحه حفظ شود
  • خروجی CSV از نوار مدیریت: پیام‌های تماس و مشترکان خبرنامه (با BOM برای Excel)
  • ویرایش جزئی PATCH /api/admin/{resource}/{id} — فقط فیلدهای ارسال‌شده عوض می‌شوند
  • ترتیب‌بندی POST /api/admin/{resource}/reorder — با {"ids": [...]}
  • همه محتوا در یک درخواست: GET /api/site (مقاله‌های پیش‌نویس در آن نمی‌آیند)
  • مهاجرت‌های اسکیما: Alembic (backend/migrations/) — هر تغییر اسکیمای جدید باید revision شود: cd backend && alembic revision --autogenerate -m "..." و پس از بازبینی commit؛ در استارت کانتینر خودکار upgrade head اجرا می‌شود
  • تست‌ها: cd backend && .venv/Scripts/activate && pytest tests (۲۴ تست — شامل honeypot، محدودسازی نرخ و پاک‌سازی)

در محیط واقعی حتماً SECRET_KEY، ADMIN_PASSWORD و MINIO_ROOT_PASSWORD را قوی بگیرید (bash scripts/gen-env.sh) و بعد از اولین ورود، رمز را از نوار مدیریت عوض کنید.

ویرایش زنده سایت (دوقلوی دیجیتال)

ادمین‌ها نیازی به داشبورد جدا ندارند — خود سایت ویرایشگر است:

  1. به /admin/login بروید و وارد شوید
  2. نوار شناور پایین صفحه ظاهر می‌شود؛ حالت ویرایش را روشن کنید
  3. روی هر متن کلیک کنید و ویرایش کنید — خروج از کادر، بلافاصله ذخیره می‌شود (Enter ذخیره، Esc لغو)؛ وضعیت ذخیره در نوار پایین دیده می‌شود
  4. آیتم‌های مجموعه‌ها (خدمات، پروژه‌ها، مقالات، نظرات، تیم و...) با هاور، دکمه‌های ویرایش / حذف / جابه‌جایی ترتیب نشان می‌دهند؛ دکمه «افزودن» هم انتهای هر لیست هست
  5. تصویرها از همان کشوها به MinIO آپلود می‌شوند
  • در حالت ویرایش، لینک‌ها کلیک نمی‌شوند تا ویرایش متن ممکن باشد
  • بازدیدکننده‌های عادی هیچ اثری از این لایه نمی‌بینند (بدون توکن، همه درخواست‌های مدیریتی 401 برمی‌گردند)
  • تمام تغییرها بلافاصله در PostgreSQL ثبت می‌شوند — با Ctrl+F5 هم می‌مانند

وبلاگ — نوشتن با ویرایشگر WYSIWYG

مقالات با ویرایشگر غنی TipTap نوشته می‌شوند (همان تجربه Medium):

  • در حالت ویرایش، روی هر مقاله در صفحه وبلاگ دکمه ویرایش را بزنید — کشوی نوشتن باز می‌شود
  • تیتر (H2/H3)، بولد/ایتالیک/زیرخط، لیست نقطه‌ای و شماره‌ای، نقل‌قول، لینک و واگرد
  • درج تصویر داخل متن: دکمه تصویر، یا چسباندن/کشیدن تصویر داخل نوشته — فایل مستقیم در MinIO ذخیره و در متن قرار می‌گیرد
  • راست‌به‌چپ کامل با فونت و تم سایت
  • ذخیره خودکار: هر مکث کوتاه در تایپ، تغییرها را ذخیره می‌کند؛ بستن کشو هم ذخیره می‌کند

پیش‌نویس و انتشار: مقاله تازه همیشه پیش‌نویس شروع می‌شود — در سایت عمومی دیده نمی‌شود و تا انتشار، تغییرهای خودکار آن چیزی را خراب نمی‌کند. با دکمه «انتشار در سایت» عمومی می‌شود؛ دوباره می‌توان آن را به پیش‌نویس برگرداند. پیش‌نویس‌ها فقط برای ادمین (در صفحه وبلاگ، با برچسب «پیش‌نویس») دیده می‌شوند و در GET /api/site هرگز نمی‌آیند.

فرمت ذخیره: بدنه مقاله یک سند ساختاریافته TipTap (JSON) در PostgreSQL است — نه HTML خام. صفحه مقاله آن را با رندر استاتیک TipTap به HTML تبدیل می‌کند، پس فقط تگ‌های شناخته‌شده (پاراگراف، تیتر، لیست، نقل‌قول، تصویر، لینک) تولید می‌شوند. مقالات قدیمی (لیست پاراگراف) در اولین اجرا به‌صورت خودکار به این فرمت تبدیل می‌شوند. در زمان ذخیره، آدرس‌های href/src سند در سمت سرور پاک‌سازی می‌شوند (app/sanitize.py) تا اسکیم‌های خطرناک مثل javascript: هرگز ذخیره نشوند.

سئو

  • بررسی سئو در صفحه نوشتن: فهرست سبز/نارنجی/قرمز به سبک یوست ولی سازگار با فارسی — طول عنوان، کلمه کلیدی هدف (در عنوان/پاراگراف اول/تیترها)، طول چکیده، تعداد واژه، تیتربندی H2، متن جایگزین تصاویر، پیوندها و نشانی (slug) — کلمه کلیدی هدف در دیتابیس ذخیره می‌شود (فیلد seo_keyphrase مقاله)
  • سئوی هر صفحه: در حالت ویرایش، دکمه سئو روی هیرو هر صفحه (و کنار هیرو صفحه اصلی) — عنوان تب و توضیح متای هر صفحه از دیتابیس خوانده و اعمال می‌شود؛ اگر ردیف سئوی صفحه‌ای هنوز ساخته نشده باشد، دکمه + همان‌جا آن را می‌سازد
  • صفحه مقاله و پروژه به‌طور خودکار عنوان و توضیح متا را از محتوای خودشان می‌گیرند
  • تگ‌های OG و توییتر و canonical برای همه صفحات از src/seo.ts ست می‌شود و مقاله‌ها اسکیمای JSON-LD نوع Article می‌گیرند
  • robots.txt و sitemap.xml از بک‌اند سرو می‌شوند و مقاله/پروژه‌های منتشرشده را شامل می‌شوند (پیش‌نویس‌ها نمی‌آیند) — برای آدرس مطلق، SITE_URL را در .env بگذارید
  • متا برای خزنده‌های بدون جاوااسکریپت (تلگرام، واتس‌اپ و...): بک‌اند صفحات /blog، /blog/{slug}، /projects و /projects/{slug} را با همان index.html فرانت‌اند سرو می‌کند و پیش از ارسال، title / description / OG / JSON-LD را از دیتابیس داخل آن تزریق می‌کند (app/seo_pages.py) — بازدیدکننده‌های عادی همان SPA همیشگی را می‌بینند

پشتیبان‌گیری و بازگردانی

همه محتوای سایت در PostgreSQL و تصاویر در MinIO است:

bash scripts/stack.sh backup                 # پشتیبان کامل: DB + تصاویر MinIO
docker compose --profile ops up -d db-backup # پشتیبان ساعتی خودکار DB (بدون cron میزبان)
  • خروجی: backups/db-<زمان>.sql.gz (فشرده + فایل .sha256) و backups/images-<زمان>/
  • اسکریپت پشتیبان، خروجی خالی/شکسته را خطا می‌گیرد، سنجهٔ تازگی برای هشدار Grafana می‌نویسد و اگر OFFSITE_RCLONE_REMOTE در .env تنظیم باشد، پشتیبان را به حافظهٔ دیگری هم کپی می‌کند (rclone)
  • بازگردانی: bash scripts/stack.sh restore backups/db-<زمان>.sql.gz (احتیاط: محتوای فعلی جایگزین می‌شود — قبل از انتشار واقعی یک‌بار تمرین کنید!)
  • زمان‌بندی پشتیبان کامل با cron یا Task Scheduler: نمونه دستور داخل خود اسکریپت هست

تست و CI

  • bash scripts/ci.sh — تست‌های pytest + بیلد ایمیج‌ها + ممیزی وابستگی‌ها
  • bash scripts/audit.sh — pip-audit / npm audit / trivy (هر کدام نصب بود)
  • .github/workflows/ci.yml — همین مراحل؛ سینتکس GitHub Actions روی Gitea Actions/ Forgejo هم اجرا می‌شود

نکات امنیتی

  • مقادیر محرمانه در .env ریشه هستند (در .gitignore) و در compose با سینتکس ${VAR:?} اجباری شده‌اند — بدون .env استک بالا نمی‌آید؛ هیچ رمز پیش‌فرضی در کد نیست
  • رمز عبور ادمین با bcrypt هش می‌شود و از نوار مدیریت پایین سایت (دکمه «رمز») قابل تعویض است
  • توکن‌های JWT ۸ ساعت اعتبار دارند (stateless — تغییر SECRET_KEY همه را خارج می‌کند)
  • همهٔ کانتینرها بدون root اجرا می‌شوند؛ کانتکست بیلد هر ایمیج با .dockerignore باریک است و لاگ‌ها سقف دارند
  • فرم‌های عمومی ضدربات‌اند (honeypot + محدودسازی نرخ در بک‌اند + سقف سیلاب در Traefik)
  • کنسول MinIO و API مستقیمش در تولید فقط از 127.0.0.1 در دسترس‌اند (تونل SSH)
  • تصاویر باکت MinIO عمومی (فقط خواندن) هستند؛ آپلود فقط با توکن ادمین ممکن است
  • مقاله‌های پیش‌نویس در هیچ انتهای عمومی نمی‌آیند و در HTML تزریق‌شده سئو هم افشا نمی‌شوند
S
Description
No description provided
Readme
61 MiB
Languages
TypeScript 53%
Python 37.6%
JavaScript 5.2%
Shell 2%
CSS 1.5%
Other 0.6%