fleetanalytics_mcp/scripts/bootstrap_analytics_ro.sh
david kiania 1eda59fe06 feat: read-only Fleet Analytics MCP server
Standalone, hosted MCP server that lets the decision & analytics team query
the fleet database (reporting.* / tracksolid.*) from Claude — read-only, for
reporting and decisions, never edit/delete.

- analytics_mcp.py: FastMCP streamable-HTTP server. Tools: query (guarded
  single SELECT/WITH, auto-LIMIT, write/DDL blocked), list_schemas,
  list_tables, describe_table, list_functions, sample_table. Per-analyst
  Bearer auth; /healthz exempt. No ts_shared_rev import (carries no ingestion
  secrets).
- Read-only enforced at four layers: analytics_ro GRANTs,
  default_transaction_read_only=on, rolled-back txn, SQL keyword guard.
- scripts/: analytics_ro_role.sql + bootstrap_analytics_ro.sh (dedicated
  least-privilege role, password in host-only ~/.analytics_ro.pw).
- Dockerfile + pyproject (uv, package=false) for Coolify build; deploy.sh
  manual host fallback (standalone Traefik bridge on the tracksolid_db host).
- docs/ANALYTICS_MCP.{md,html} + README: architecture, deploy runbook,
  add-to-Claude, verification, security notes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 23:43:24 +03:00

39 lines
2.1 KiB
Bash
Executable file

#!/usr/bin/env bash
# bootstrap_analytics_ro.sh — create/refresh the analytics_ro read-only role.
# ─────────────────────────────────────────────────────────────────────────────
# Run ON THE HOST. Generates a strong password into ~/.analytics_ro.pw (0600) on
# first run (reused thereafter), then applies scripts/analytics_ro_role.sql to the
# prod DB as the postgres superuser. The password is NEVER printed and never
# leaves the host — the MCP deploy script (deploy_analytics_mcp.sh) reads the same
# ~/.analytics_ro.pw.
#
# Deploy:
# scp scripts/analytics_ro_role.sql scripts/bootstrap_analytics_ro.sh \
# kianiadee@twala.rahamafresh.com:~/
# ssh kianiadee@twala.rahamafresh.com 'bash ~/bootstrap_analytics_ro.sh'
#
# Idempotent: re-running rotates nothing unless ~/.analytics_ro.pw is deleted
# first (then it generates + sets a fresh password and you must redeploy the MCP).
# ─────────────────────────────────────────────────────────────────────────────
set -euo pipefail
PW_FILE="${ANALYTICS_RO_PW_FILE:-$HOME/.analytics_ro.pw}"
SQL_FILE="${1:-$HOME/analytics_ro_role.sql}"
test -f "$SQL_FILE" || { echo "ERROR: role SQL not found at $SQL_FILE (scp scripts/analytics_ro_role.sql to ~ first)"; exit 1; }
if [ ! -s "$PW_FILE" ]; then
( umask 077; openssl rand -hex 24 > "$PW_FILE" )
chmod 600 "$PW_FILE"
echo "Generated new analytics_ro password -> $PW_FILE (0600)"
else
echo "Reusing existing analytics_ro password from $PW_FILE"
fi
PW=$(cat "$PW_FILE")
DB=$(docker ps --filter name=timescale_db --format "{{.Names}}" | head -1)
[ -n "$DB" ] || { echo "ERROR: timescale_db container not found"; exit 1; }
echo "Applying analytics_ro role DDL to $DB as postgres ..."
docker exec -i "$DB" psql -U postgres -d tracksolid_db -v ON_ERROR_STOP=1 -v ro_pw="$PW" < "$SQL_FILE"
echo "analytics_ro ready (password not printed). Now (re)run deploy_analytics_mcp.sh."