- گیت ورود 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
331 lines
26 KiB
Markdown
331 lines
26 KiB
Markdown
# گروه ساختمانی آرامش — پروژه کامل (فرانتاند + بکاند)
|
|
|
|
مونوریپوی سایت گروه ساختمانی آرامش شامل:
|
|
|
|
- **`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` همین را به یک دستور تبدیل میکند:
|
|
|
|
```bash
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 تزریقشده سئو هم افشا نمیشوند
|