#!/usr/bin/env bash set -euo pipefail # Import all Grafana dashboards via API (Postgres-backed, no ConfigMaps). # # Dashboards use ${DS_PROMETHEUS}, ${DS_LOKI}, ${DS_TEMPO} variables. # This script auto-detects datasource UIDs, resolves variables, and # imports via the Grafana API. Dashboards are stored in Postgres. # # Required environment variables: # GRAFANA_PASS - Password for admin user # # Optional overrides: # GRAFANA_URL - Grafana base URL (default: https://grafana.fxhnt.ai) # GRAFANA_USER - Grafana user (default: admin) # GRAFANA_PROM_UID - Override Prometheus datasource UID # GRAFANA_LOKI_UID - Override Loki datasource UID # GRAFANA_TEMPO_UID - Override Tempo datasource UID # # Usage: # GRAFANA_PASS="xxx" ./import.sh SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" GRAFANA_USER="${GRAFANA_USER:-admin}" GRAFANA_URL="${GRAFANA_URL:-https://grafana.fxhnt.ai}" GRAFANA_URL="${GRAFANA_URL%/}" if [[ -z "${GRAFANA_PASS:-}" ]]; then echo "ERROR: GRAFANA_PASS is not set" >&2 exit 1 fi # --- Datasource auto-detection --- detect_datasource_uid() { local ds_type="$1" curl -sf -u "${GRAFANA_USER}:${GRAFANA_PASS}" "${GRAFANA_URL}/api/datasources" 2>/dev/null | \ python3 -c " import sys, json for ds in json.load(sys.stdin): if ds.get('type') == '${ds_type}': print(ds['uid']) break " 2>/dev/null || true } echo "Detecting datasource UIDs..." PROM_UID="${GRAFANA_PROM_UID:-$(detect_datasource_uid prometheus)}" LOKI_UID="${GRAFANA_LOKI_UID:-$(detect_datasource_uid loki)}" TEMPO_UID="${GRAFANA_TEMPO_UID:-$(detect_datasource_uid tempo)}" echo " Prometheus: ${PROM_UID:-NOT FOUND}" echo " Loki: ${LOKI_UID:-NOT FOUND}" echo " Tempo: ${TEMPO_UID:-NOT FOUND}" if [[ -z "$PROM_UID" ]]; then echo "ERROR: Could not detect Prometheus datasource. Set GRAFANA_PROM_UID." >&2 exit 1 fi # --- Resolve datasource variables --- resolve_dashboard() { local file="$1" python3 -c " import sys, json with open('${file}') as f: d = json.load(f) d.pop('__inputs', None) d.pop('__requires', None) raw = json.dumps(d) raw = raw.replace('\${DS_PROMETHEUS}', '${PROM_UID}') raw = raw.replace('\${DS_LOKI}', '${LOKI_UID}') raw = raw.replace('\${DS_TEMPO}', '${TEMPO_UID}') print(raw) " } # --- Ensure folders exist --- ensure_folder() { local title="$1" local uid uid=$(curl -sf -u "${GRAFANA_USER}:${GRAFANA_PASS}" "${GRAFANA_URL}/api/folders" 2>/dev/null | \ python3 -c " import sys, json for f in json.load(sys.stdin): if f['title'] == '${title}': print(f['uid']) break " 2>/dev/null || true) if [[ -z "$uid" ]]; then uid=$(curl -sf -u "${GRAFANA_USER}:${GRAFANA_PASS}" \ -H "Content-Type: application/json" \ -d "{\"title\": \"${title}\"}" \ "${GRAFANA_URL}/api/folders" 2>/dev/null | \ python3 -c "import sys,json; print(json.load(sys.stdin)['uid'])" 2>/dev/null) echo " Created folder: ${title} (${uid})" fi echo "$uid" } echo "" echo "Ensuring folders..." FOLDER_CI=$(ensure_folder "CI") FOLDER_INFRA=$(ensure_folder "Infrastructure") FOLDER_OPS=$(ensure_folder "Operations") FOLDER_TRADING=$(ensure_folder "Trading") FOLDER_TRAINING=$(ensure_folder "Training") FOLDER_SERVICES=$(ensure_folder "Services") FOLDER_PLATFORM=$(ensure_folder "Platform") # --- Import a single dashboard to a folder --- import_dashboard() { local file="$1" local folder_uid="$2" local name name="$(basename "$file" .json)" local resolved resolved=$(resolve_dashboard "$file") local response response=$(curl -s -w "\n%{http_code}" \ -u "${GRAFANA_USER}:${GRAFANA_PASS}" \ -H "Content-Type: application/json" \ -d "{\"dashboard\": ${resolved}, \"folderUid\": \"${folder_uid}\", \"overwrite\": true}" \ "${GRAFANA_URL}/api/dashboards/db") local http_code http_code=$(echo "$response" | tail -1) if [[ "$http_code" -ge 200 && "$http_code" -lt 300 ]]; then echo " OK ${name}" else local body body=$(echo "$response" | sed '$d') echo " FAIL ${name} (${http_code}): ${body}" >&2 return 1 fi } # --- Dashboard → Folder mapping --- declare -A DASHBOARD_FOLDERS=( # Original dashboards [foxhunt-cockpit]="${FOLDER_OPS}" [foxhunt-trading]="${FOLDER_TRADING}" [foxhunt-training]="${FOLDER_TRAINING}" [foxhunt-infrastructure]="${FOLDER_INFRA}" [foxhunt-observability]="${FOLDER_INFRA}" [foxhunt-ci-logs]="${FOLDER_CI}" [foxhunt-training-logs]="${FOLDER_TRAINING}" [foxhunt-training-deep-dive]="${FOLDER_TRAINING}" # Application services [svc-api-gateway]="${FOLDER_SERVICES}" [svc-trading-service]="${FOLDER_SERVICES}" [svc-backtesting]="${FOLDER_SERVICES}" [svc-data-acquisition]="${FOLDER_SERVICES}" [svc-ml-training]="${FOLDER_SERVICES}" [svc-trading-agent]="${FOLDER_SERVICES}" [svc-web-gateway]="${FOLDER_SERVICES}" # Platform services [platform-prometheus]="${FOLDER_PLATFORM}" [platform-grafana]="${FOLDER_PLATFORM}" [platform-loki]="${FOLDER_PLATFORM}" [platform-tempo]="${FOLDER_PLATFORM}" [platform-redis]="${FOLDER_PLATFORM}" [platform-postgres]="${FOLDER_PLATFORM}" [platform-argo]="${FOLDER_PLATFORM}" [platform-questdb]="${FOLDER_PLATFORM}" [platform-costs]="${FOLDER_PLATFORM}" [platform-scaleway]="${FOLDER_PLATFORM}" ) # --- Deploy all dashboards --- echo "" echo "Importing dashboards..." failed=0 total=0 for dashboard in "${SCRIPT_DIR}"/*.json; do [[ -f "$dashboard" ]] || continue name="$(basename "$dashboard" .json)" folder_uid="${DASHBOARD_FOLDERS[$name]:-}" if [[ -z "$folder_uid" ]]; then echo " SKIP ${name} (no folder mapping)" continue fi total=$((total + 1)) if ! import_dashboard "$dashboard" "$folder_uid"; then failed=$((failed + 1)) fi done echo "" if [[ $failed -eq 0 ]]; then echo "All ${total} dashboards deployed successfully." else echo "ERROR: ${failed}/${total} dashboard(s) failed." >&2 exit 1 fi