#!/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