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:
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"uid": "aramesh-app",
|
||||
"title": "آرامش — اپلیکیشن و ترافیک",
|
||||
"tags": ["aramesh"],
|
||||
"timezone": "browser",
|
||||
"schemaVersion": 39,
|
||||
"refresh": "30s",
|
||||
"time": { "from": "now-6h", "to": "now" },
|
||||
"panels": [
|
||||
{
|
||||
"type": "timeseries",
|
||||
"title": "درخواستها بر ثانیه (هر روتر Traefik)",
|
||||
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 },
|
||||
"datasource": { "type": "prometheus", "uid": "prom" },
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(traefik_service_requests_total[5m])) by (service)",
|
||||
"legendFormat": "{{ service }}"
|
||||
}
|
||||
],
|
||||
"fieldConfig": { "defaults": { "unit": "reqps" }, "overrides": [] }
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"title": "خطاهای 5xx (بکاند)",
|
||||
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 0 },
|
||||
"datasource": { "type": "prometheus", "uid": "prom" },
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(http_request_duration_seconds_count{status=~\"5..\"}[5m]))",
|
||||
"legendFormat": "5xx/s"
|
||||
}
|
||||
],
|
||||
"fieldConfig": { "defaults": { "unit": "reqps" }, "overrides": [] }
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"title": "تأخیر بکاند — p50 و p95",
|
||||
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 8 },
|
||||
"datasource": { "type": "prometheus", "uid": "prom" },
|
||||
"targets": [
|
||||
{
|
||||
"expr": "histogram_quantile(0.5, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))",
|
||||
"legendFormat": "p50"
|
||||
},
|
||||
{
|
||||
"expr": "histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))",
|
||||
"legendFormat": "p95"
|
||||
}
|
||||
],
|
||||
"fieldConfig": { "defaults": { "unit": "s" }, "overrides": [] }
|
||||
},
|
||||
{
|
||||
"type": "stat",
|
||||
"title": "سلامت /api/health (پروب عمق)",
|
||||
"gridPos": { "h": 8, "w": 4, "x": 12, "y": 8 },
|
||||
"datasource": { "type": "prometheus", "uid": "prom" },
|
||||
"targets": [
|
||||
{ "expr": "probe_success{job=\"blackbox-internal-health\"}", "legendFormat": "health" }
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [
|
||||
{ "type": "value", "options": { "1": { "text": "سالم", "color": "green" }, "0": { "text": "خراب", "color": "red" } } }
|
||||
],
|
||||
"min": 0,
|
||||
"max": 1
|
||||
},
|
||||
"overrides": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "stat",
|
||||
"title": "تازگی آخرین پشتیبان (ثانیه)",
|
||||
"gridPos": { "h": 8, "w": 4, "x": 16, "y": 8 },
|
||||
"datasource": { "type": "prometheus", "uid": "prom" },
|
||||
"targets": [
|
||||
{ "expr": "time() - backup_success_time", "legendFormat": "age" }
|
||||
],
|
||||
"fieldConfig": { "defaults": { "unit": "s", "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "orange", "value": 72000 }, { "color": "red", "value": 93600 } ] } }, "overrides": [] }
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"title": "اتصالهای Postgres",
|
||||
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 16 },
|
||||
"datasource": { "type": "prometheus", "uid": "prom" },
|
||||
"targets": [
|
||||
{ "expr": "sum(pg_stat_activity_count)", "legendFormat": "اتصالها" }
|
||||
],
|
||||
"fieldConfig": { "defaults": { "unit": "short" }, "overrides": [] }
|
||||
},
|
||||
{
|
||||
"type": "logs",
|
||||
"title": "لاگ زندهٔ سرویسها (Loki)",
|
||||
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 16 },
|
||||
"datasource": { "type": "loki", "uid": "loki" },
|
||||
"targets": [
|
||||
{ "expr": "{service=~\"backend|traefik|chatbot|umami\"}" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"uid": "aramesh-infra",
|
||||
"title": "آرامش — زیرساخت (میزبان و کانتینرها)",
|
||||
"tags": ["aramesh"],
|
||||
"timezone": "browser",
|
||||
"schemaVersion": 39,
|
||||
"refresh": "30s",
|
||||
"time": { "from": "now-6h", "to": "now" },
|
||||
"panels": [
|
||||
{
|
||||
"type": "timeseries",
|
||||
"title": "CPU میزبان",
|
||||
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 },
|
||||
"datasource": { "type": "prometheus", "uid": "prom" },
|
||||
"targets": [
|
||||
{
|
||||
"expr": "100 - (avg by (instance) (rate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100)",
|
||||
"legendFormat": "CPU %"
|
||||
}
|
||||
],
|
||||
"fieldConfig": { "defaults": { "unit": "percent", "max": 100 }, "overrides": [] }
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"title": "حافظه میزبان",
|
||||
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 0 },
|
||||
"datasource": { "type": "prometheus", "uid": "prom" },
|
||||
"targets": [
|
||||
{ "expr": "node_memory_MemTotal_bytes", "legendFormat": "کل" },
|
||||
{ "expr": "node_memory_MemAvailable_bytes", "legendFormat": "آزاد" }
|
||||
],
|
||||
"fieldConfig": { "defaults": { "unit": "bytes" }, "overrides": [] }
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"title": "دیسک — درصد پرشدگی",
|
||||
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 8 },
|
||||
"datasource": { "type": "prometheus", "uid": "prom" },
|
||||
"targets": [
|
||||
{
|
||||
"expr": "100 * (1 - (node_filesystem_avail_bytes{fstype!~\"tmpfs|overlay|squashfs\"} / node_filesystem_size_bytes{fstype!~\"tmpfs|overlay|squashfs\"}))",
|
||||
"legendFormat": "{{ mountpoint }}"
|
||||
}
|
||||
],
|
||||
"fieldConfig": { "defaults": { "unit": "percent", "max": 100 }, "overrides": [] }
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"title": "مصرف RAM هر کانتینر",
|
||||
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 8 },
|
||||
"datasource": { "type": "prometheus", "uid": "prom" },
|
||||
"targets": [
|
||||
{
|
||||
"expr": "topk(10, container_memory_usage_bytes{image!=\"\", container!=\"\"})",
|
||||
"legendFormat": "{{ container }}"
|
||||
}
|
||||
],
|
||||
"fieldConfig": { "defaults": { "unit": "bytes" }, "overrides": [] }
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"title": "CPU هر کانتینر",
|
||||
"gridPos": { "h": 8, "w": 24, "x": 0, "y": 16 },
|
||||
"datasource": { "type": "prometheus", "uid": "prom" },
|
||||
"targets": [
|
||||
{
|
||||
"expr": "topk(10, rate(container_cpu_usage_seconds_total{image!=\"\", container!=\"\"}[5m]))",
|
||||
"legendFormat": "{{ container }}"
|
||||
}
|
||||
],
|
||||
"fieldConfig": { "defaults": { "unit": "percentunit" }, "overrides": [] }
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
apiVersion: 1
|
||||
|
||||
providers:
|
||||
- name: aramesh
|
||||
folder: Aramesh
|
||||
type: file
|
||||
disableDeletion: false
|
||||
updateIntervalSeconds: 30
|
||||
options:
|
||||
path: /var/lib/grafana/dashboards
|
||||
foldersFromFilesStructure: false
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: 1
|
||||
|
||||
datasources:
|
||||
- name: Prometheus
|
||||
type: prometheus
|
||||
uid: prom
|
||||
access: proxy
|
||||
url: http://prometheus:9090
|
||||
isDefault: true
|
||||
editable: false
|
||||
|
||||
- name: Loki
|
||||
type: loki
|
||||
uid: loki
|
||||
access: proxy
|
||||
url: http://loki:3100
|
||||
editable: false
|
||||
Reference in New Issue
Block a user