feat(stack): full overhaul — hardening, ops tooling, monitoring, analytics, chatbot

Security: deep /api/health (DB+MinIO probes, 503 on failure) + /api/live;
rate-limited public forms with honeypots; real client IP behind Traefik
(--proxy-headers); TipTap URL sanitization on write (nh3); non-root
containers + slim .dockerignore; prod MinIO console bound to 127.0.0.1;
HSTS/nosniff on TLS routers; JWT 8h; no default credentials in code.

Reliability: Alembic migrations (create_all baseline); graceful shutdown
(exec uvicorn); hardened backups (checksums, sanity check, freshness gauge,
optional rclone off-site) + restore.sh + hourly db-backup sidecar (ops
profile); stack.sh ops tooling (deploy/logs/health/backup/restore/monitoring);
.gitattributes LF for shell scripts.

Orchestration: json-file log rotation, pinned images, per-service memory
limits, edge/internal networks, Traefik ping/accesslog/metrics + flood
limiter, conditional minio-setup, pgvector db image, one-shot db-init
(umami + aramesh_chatbot databases).

Chatbot: unified from site-chatbot branch (RAG + pgvector, SSE, ChatWidget).

Monitoring: Prometheus/Alertmanager/Loki/Promtail/Grafana + cAdvisor,
node/postgres/blackbox exporters, provisioned dashboards and 10 alert
rules; alerts delivered to Bale (tapi.bale.ai — Telegram-compatible).

Analytics: Umami on shared Postgres, build-time tracker injection, custom
events (contact/newsletter/phone/chat/lang) via frontend/src/analytics.ts.

Hygiene: CSV export for contact messages and subscribers; upload image
optimization (Pillow); CI script + GitHub-compatible workflow; audit.sh;
gen-env.sh for strong credentials.
This commit is contained in:
MMD
2026-09-19 00:24:57 +03:30
parent 805f4594d4
commit ce0735d775
75 changed files with 4287 additions and 153 deletions
+42 -2
View File
@@ -1,8 +1,13 @@
"""انتهای عمومی — خواندن محتوای سایت بدون نیاز به احراز هویت."""
import boto3
from botocore.config import Config as BotoConfig
from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import JSONResponse
from sqlalchemy import text
from sqlalchemy.orm import Session
from ..config import settings
from ..database import get_db
from .. import models
from ..schemas import (
@@ -22,9 +27,44 @@ from ..schemas import (
router = APIRouter()
@router.get("/live")
def live():
"""زنده‌بودن پروسه — بدون وابستگی؛ برای restart gate استفاده نمی‌شود."""
return {"status": "ok"}
@router.get("/health")
def health():
return {"status": "ok", "app": "Aramesh Building Group API"}
def health(db: Session = Depends(get_db)):
"""بررسی عمق سلامت — اتصال DB و MinIO واقعاً آزموده می‌شود؛
در صورت خرابی هر وابستگی، 503 با جزئیات برمی‌گردد."""
checks: dict[str, str] = {}
try:
db.execute(text("SELECT 1"))
checks["database"] = "ok"
except Exception:
checks["database"] = "error"
try:
s3 = boto3.client(
"s3",
endpoint_url=settings.s3_endpoint,
aws_access_key_id=settings.s3_access_key,
aws_secret_access_key=settings.s3_secret_key,
region_name="us-east-1",
config=BotoConfig(connect_timeout=2, read_timeout=2, retries={"max_attempts": 1}),
)
s3.head_bucket(Bucket=settings.s3_bucket)
checks["storage"] = "ok"
except Exception:
checks["storage"] = "error"
if any(state != "ok" for state in checks.values()):
return JSONResponse(
status_code=503,
content={"status": "degraded", "checks": checks},
)
return {"status": "ok", "app": "Aramesh Building Group API", "checks": checks}
@router.get("/brand", response_model=BrandOut)