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,159 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user