Files
MMD ce0735d775 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.
2026-09-19 00:24:57 +03:30

160 lines
5.6 KiB
Bash

#!/usr/bin/env bash
# ابزار orchestration استک — یک نقطه ورود برای همه عملیات رایج.
#
# bash scripts/stack.sh deploy # بیلد فرانت‌اند (روی هاست) + بیلد و بالا آوردن استک
# bash scripts/stack.sh deploy-prod # همین، با لایه TLS/دامنه (docker-compose.prod.yml)
# bash scripts/stack.sh logs [svc] # دنبال‌کردن لاگ (همه یا یک سرویس)
# bash scripts/stack.sh ps # وضعیت سرویس‌ها
# bash scripts/stack.sh health # وضعیت healthcheck همه سرویس‌ها
# bash scripts/stack.sh backup # پشتیبان‌گیری کامل (scripts/backup.sh)
# bash scripts/stack.sh restore FILE # بازگردانی دیتابیس (scripts/restore.sh)
# bash scripts/stack.sh update # کشیدن ایمیج‌های پین‌شده و بالا آوردن مجدد
# bash scripts/stack.sh monitoring # لایه مانیتورینگ (Prometheus/Grafana/Loki/بله)
# bash scripts/stack.sh seed-force # seed مجدد محتوا (--force — داده‌های جدول‌ها ریست می‌شود)
#
# متغیرهای بیلد فرانت‌اند (VITE_API_URL، VITE_UMAMI_SRC، VITE_UMAMI_WEBSITE_ID)
# از .env ریشه خوانده و به npm run build پاس می‌شوند — متغیرهای Vite در زمان بیلد
# روی هاست معنا دارند، نه در ایمیج Caddy.
set -euo pipefail
cd "$(dirname "$0")/.."
DEV_FILES=(-f docker-compose.yml)
[ -f docker-compose.override.yml ] && DEV_FILES+=(-f docker-compose.override.yml)
# compose dev ... | compose prod ... — انتخاب فایل‌های لایه‌ای
compose() {
local mode="$1"
shift
if [ "$mode" = "prod" ]; then
docker compose -f docker-compose.yml -f docker-compose.prod.yml "$@"
else
docker compose "${DEV_FILES[@]}" "$@"
fi
}
load_env_vars() {
# فقط متغیرهای بیلد فرانت‌اند — بدون خروجی
if [ -f .env ]; then
set -a
# shellcheck disable=SC1091
. ./.env
set +a
fi
}
render_alertmanager() {
# قالب alertmanager.yml با مقادیر .env رندر می‌شود — Alertmanager متغیر محیطی
# را در کانفیگ جایگزین نمی‌کند؛ خروجی (rendered) در گیت نمی‌آید.
load_env_vars
local tpl="monitoring/alertmanager/alertmanager.yml"
local out="monitoring/alertmanager/alertmanager.rendered.yml"
local bot="${ALERT_BOT_TOKEN:-0000000000:PLACEHOLDER-TOKEN}"
local chat="${ALERT_CHAT_ID:-0}"
sed -e "s|\${ALERT_BOT_TOKEN}|${bot}|g" \
-e "s|\${ALERT_CHAT_ID}|${chat}|g" \
"$tpl" > "$out"
if [ -n "${ALERT_BOT_TOKEN:-}" ]; then
echo "[stack] alertmanager config rendered (bale token set)"
else
echo "[stack] alertmanager config rendered (no bale token — alerts will not deliver)"
fi
}
build_frontend() {
load_env_vars
echo "[stack] بیلد فرانت‌اند روی هاست…"
(cd frontend \
&& VITE_API_URL="${VITE_API_URL:-}" \
VITE_UMAMI_SRC="${VITE_UMAMI_SRC:-}" \
VITE_UMAMI_WEBSITE_ID="${VITE_UMAMI_WEBSITE_ID:-}" \
npm run build)
if [ ! -f frontend/dist/index.html ]; then
echo "[stack] ✗ بیلد فرانت‌اند خروجی نداد (frontend/dist)" >&2
exit 1
fi
}
wait_healthy() {
local mode="$1"
echo "[stack] منتظر سلامت سرویس‌ها (حداکثر ۱۸۰ ثانیه)…"
local deadline=$((SECONDS + 180))
local unhealthy=""
while [ $SECONDS -lt $deadline ]; do
unhealthy=$(compose "$mode" ps --format '{{.Service}} {{.Health}}' 2>/dev/null \
| awk '$2=="starting" || $2=="unhealthy" {print $1":"$2}')
if [ -z "$unhealthy" ]; then
echo "[stack] ✔ همه سرویس‌ها سالم‌اند"
return 0
fi
sleep 5
done
echo "[stack] ✗ برخی سرویس‌ها سالم نشدند: ${unhealthy:-نامشخص}" >&2
compose "$mode" ps
return 1
}
do_deploy() {
local mode="$1"
build_frontend
echo "[stack] بیلد و بالا آوردن استک…"
compose "$mode" up -d --build
wait_healthy "$mode"
docker system prune -f >/dev/null 2>&1 || true
echo "[stack] ✔ استقرار تمام شد"
if [ -z "${VITE_UMAMI_WEBSITE_ID:-}" ]; then
echo "[stack] یادآوری: VITE_UMAMI_WEBSITE_ID در .env خالی است — تحلیل رفتار غیرفعال می‌ماند."
fi
}
case "${1:-}" in
deploy)
do_deploy dev
;;
deploy-prod)
load_env_vars
if [ -z "${DOMAIN:-}" ] || [ -z "${ACME_EMAIL:-}" ]; then
echo "[stack] ✗ برای deploy-prod، DOMAIN و ACME_EMAIL باید در .env تنظیم باشند" >&2
exit 1
fi
do_deploy prod
;;
logs)
shift || true
compose dev logs -f --tail=100 "$@"
;;
ps)
compose dev ps
;;
health)
compose dev ps --format '{{.Service}}: {{.Status}}'
;;
backup)
bash scripts/backup.sh
;;
restore)
shift
bash scripts/restore.sh "$@"
;;
update)
compose dev pull
compose dev up -d
wait_healthy dev
;;
monitoring)
# لایه مانیتورینگ — Prometheus/Grafana/Loki/Alertmanager (+ رندر کانفیگ بله)
render_alertmanager
echo "[stack] بالا آوردن لایه مانیتورینگ…"
docker compose "${DEV_FILES[@]}" -f docker-compose.monitoring.yml up -d --build
wait_healthy dev
echo "[stack] ✔ مانیتورینگ بالا آمد — داشبورد: http://grafana.localhost:${TRAEFIK_HTTP_PORT:-5173}"
;;
seed-force)
compose dev run --rm backend python -m app.seed --force
;;
*)
grep '^# ' "$0" | sed 's/^# //'
exit 1
;;
esac