mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
The tree was a debranded snapshot of upstream taken around 2026-02-20, and upstream has moved about five months since. This replaces it with upstream master, mechanically re-derived: bin/debrand does the rename, and the pipeline and squashed migrations are restored from the shipped tree. Not a merge. The previous import replaced the tree wholesale in a single commit, so there is no shared content history to merge against. Reproducing the rename on the new tree is what actually converges. What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints, compose files, LLM.md, and insights/migrations. The squashed initial migration is load-bearing — the live database records it by name, so upstream's ~918 individual migrations cannot come back. Nine products are dropped as unreferenced. Twenty-one more that we do not run are kept anyway because the core app still imports them; unpicking those is a refactor and does not belong in a rebase. This base does not build yet, and the reasons are not in the rename: upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against our 4.2, and the dependency manifests still name upstream packages that we republish under our own names. Those are decisions, not renames.
280 lines
8.8 KiB
Bash
Executable File
280 lines
8.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Migrate from single-server Grafana MCP setup to multi-server (US + EU simultaneous)
|
|
#
|
|
# Usage:
|
|
# grafana-migrate-multi # Run migration with confirmation prompt
|
|
# grafana-migrate-multi --dry-run # Show what would change without writing
|
|
# grafana-migrate-multi --slim # Also normalize to recommended (smaller) disable-flag set
|
|
#
|
|
# This script automates the steps in the README under "Migrating from single-server
|
|
# to multi-server". It is idempotent and safe to run multiple times.
|
|
#
|
|
# Scans for 'grafana' MCP entries in:
|
|
# - ~/.config/claude-code/.mcp.json (Claude Code global config)
|
|
# - .claude.json (Claude Code project config, in current directory)
|
|
#
|
|
# Prerequisites:
|
|
# - macOS (uses Keychain for token verification)
|
|
# - US and EU tokens stored in Keychain (see grafana-token)
|
|
# - At least one MCP config file with a 'grafana' entry
|
|
|
|
set -euo pipefail
|
|
|
|
SETTINGS_JSON="$HOME/.claude/settings.json"
|
|
DRY_RUN=false
|
|
SLIM=false
|
|
|
|
show_usage() {
|
|
echo "Usage: grafana-migrate-multi [--dry-run] [--slim]"
|
|
echo ""
|
|
echo "Migrates your Grafana MCP setup from a single server to multi-server"
|
|
echo "(US + EU available simultaneously, no restart needed to switch regions)."
|
|
echo ""
|
|
echo "Scans for MCP config in:"
|
|
echo " ~/.config/claude-code/.mcp.json (Claude Code global)"
|
|
echo " .claude.json (Claude Code project, current directory)"
|
|
echo ""
|
|
echo "Options:"
|
|
echo " --dry-run Show what would change without writing"
|
|
echo " --slim Normalize args to the recommended (smaller) disable-flag set"
|
|
echo " -h, --help Show this help message"
|
|
echo ""
|
|
echo "Prerequisites:"
|
|
echo " - US and EU tokens in Keychain (use grafana-token to add them)"
|
|
echo " - Existing 'grafana' entry in at least one MCP config file"
|
|
}
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--dry-run) DRY_RUN=true ;;
|
|
--slim) SLIM=true ;;
|
|
-h|--help) show_usage; exit 0 ;;
|
|
*) echo "Error: Unknown argument '$1'" >&2; echo ""; show_usage; exit 1 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
# macOS check
|
|
if [[ "$OSTYPE" != "darwin"* ]]; then
|
|
echo "Error: This script requires macOS Keychain." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Token checks
|
|
missing_tokens=()
|
|
if ! security find-generic-password -a "$USER" -s "grafana-service-account-token-us" &>/dev/null; then
|
|
missing_tokens+=("us")
|
|
fi
|
|
if ! security find-generic-password -a "$USER" -s "grafana-service-account-token-eu" &>/dev/null; then
|
|
missing_tokens+=("eu")
|
|
fi
|
|
|
|
if [ ${#missing_tokens[@]} -gt 0 ]; then
|
|
echo "Error: Missing Grafana tokens for: ${missing_tokens[*]}" >&2
|
|
echo "" >&2
|
|
for region in "${missing_tokens[@]}"; do
|
|
echo " Add with: grafana-token $region <your-token>" >&2
|
|
done
|
|
exit 1
|
|
fi
|
|
|
|
# Build list of MCP config files to check
|
|
MCP_FILES=()
|
|
GLOBAL_MCP="$HOME/.config/claude-code/.mcp.json"
|
|
PROJECT_MCP=".claude.json"
|
|
|
|
if [ -f "$GLOBAL_MCP" ]; then
|
|
MCP_FILES+=("$GLOBAL_MCP")
|
|
fi
|
|
if [ -f "$PROJECT_MCP" ]; then
|
|
MCP_FILES+=("$PWD/$PROJECT_MCP")
|
|
fi
|
|
|
|
if [ ${#MCP_FILES[@]} -eq 0 ]; then
|
|
echo "Error: No MCP config files found." >&2
|
|
echo "Checked:" >&2
|
|
echo " $GLOBAL_MCP" >&2
|
|
echo " $PWD/$PROJECT_MCP" >&2
|
|
echo "Set up a single-server Grafana MCP first (see README)." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Analyze and optionally apply changes
|
|
python3 - "$DRY_RUN" "$SLIM" "$SETTINGS_JSON" "${MCP_FILES[@]}" << 'PYEOF'
|
|
import copy, json, sys, os, shutil
|
|
|
|
dry_run = sys.argv[1] == "true"
|
|
slim = sys.argv[2] == "true"
|
|
settings_path = sys.argv[3]
|
|
mcp_paths = sys.argv[4:]
|
|
|
|
SLIM_ARGS = ["-disable-admin", "-disable-alerting", "-disable-incident", "-disable-oncall"]
|
|
|
|
# --- Load MCP config files and find ones with a 'grafana' entry ---
|
|
|
|
mcp_configs = [] # list of (path, data, servers) tuples
|
|
for path in mcp_paths:
|
|
with open(path) as f:
|
|
data = json.load(f)
|
|
servers = data.get("mcpServers", {})
|
|
if "grafana" in servers:
|
|
mcp_configs.append((path, data, servers))
|
|
|
|
if not mcp_configs:
|
|
checked = "\n ".join(mcp_paths)
|
|
print(f"Error: No 'grafana' entry found in any MCP config file.\nChecked:\n {checked}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# --- Load settings ---
|
|
|
|
settings = None
|
|
if os.path.isfile(settings_path):
|
|
with open(settings_path) as f:
|
|
settings = json.load(f)
|
|
|
|
# --- Analyze MCP changes across all config files ---
|
|
|
|
all_mcp_changes = [] # list of (path, data, servers, changes_list) tuples
|
|
|
|
for path, data, servers in mcp_configs:
|
|
changes = []
|
|
grafana = servers["grafana"]
|
|
env = grafana.get("env", {})
|
|
|
|
if env.get("GRAFANA_REGION") != "us":
|
|
changes.append("Pin GRAFANA_REGION=us on existing 'grafana' entry")
|
|
|
|
if "grafana-eu" not in servers:
|
|
changes.append("Add new 'grafana-eu' entry (clone of 'grafana' with GRAFANA_REGION=eu)")
|
|
elif servers["grafana-eu"].get("env", {}).get("GRAFANA_REGION") != "eu":
|
|
changes.append("Pin GRAFANA_REGION=eu on existing 'grafana-eu' entry")
|
|
|
|
if slim:
|
|
if grafana.get("args", []) != SLIM_ARGS:
|
|
changes.append(f"Normalize 'grafana' args to recommended set: {' '.join(SLIM_ARGS)}")
|
|
eu_entry = servers.get("grafana-eu")
|
|
if eu_entry is None or eu_entry.get("args", []) != SLIM_ARGS:
|
|
changes.append(f"Normalize 'grafana-eu' args to recommended set: {' '.join(SLIM_ARGS)}")
|
|
|
|
if changes:
|
|
all_mcp_changes.append((path, data, servers, changes))
|
|
|
|
# --- Analyze settings changes ---
|
|
|
|
settings_changes = []
|
|
if settings is not None:
|
|
allow = settings.get("permissions", {}).get("allow", [])
|
|
existing = set(allow)
|
|
grafana_perms = [p for p in allow if p.startswith("mcp__grafana__")]
|
|
for perm in grafana_perms:
|
|
eu_perm = perm.replace("mcp__grafana__", "mcp__grafana-eu__", 1)
|
|
if eu_perm not in existing:
|
|
settings_changes.append(eu_perm)
|
|
|
|
# --- Check if anything to do ---
|
|
|
|
if not all_mcp_changes and not settings_changes:
|
|
print("Already migrated, nothing to do.")
|
|
sys.exit(0)
|
|
|
|
# --- Print summary ---
|
|
|
|
for path, _data, _servers, changes in all_mcp_changes:
|
|
print(f"Changes to {path}:")
|
|
for c in changes:
|
|
print(f" + {c}")
|
|
print()
|
|
|
|
if settings_changes:
|
|
print(f"Changes to {settings_path}:")
|
|
for p in settings_changes:
|
|
print(f" + Allow {p}")
|
|
print()
|
|
elif settings is None:
|
|
print(f"Note: {settings_path} not found — permissions will not be migrated.")
|
|
print(" You may need to grant permissions manually after restarting.")
|
|
print()
|
|
elif not grafana_perms:
|
|
print(f"Note: No mcp__grafana__* permissions found in {settings_path}.")
|
|
print(" You may need to add permissions manually after restarting.")
|
|
print()
|
|
|
|
if dry_run:
|
|
print("(Dry run -- no changes written)")
|
|
sys.exit(0)
|
|
|
|
# --- Prompt for confirmation ---
|
|
|
|
try:
|
|
confirm = input("Apply these changes? [y/N] ")
|
|
except EOFError:
|
|
confirm = ""
|
|
|
|
if confirm.strip().lower() != "y":
|
|
print("Aborted.")
|
|
sys.exit(0)
|
|
|
|
# --- Apply MCP changes ---
|
|
|
|
for path, data, servers, _changes in all_mcp_changes:
|
|
grafana = servers["grafana"]
|
|
|
|
bak_path = path + ".bak"
|
|
if not os.path.exists(bak_path):
|
|
shutil.copy2(path, bak_path)
|
|
|
|
# Clone for EU before mutating the US entry
|
|
if "grafana-eu" not in servers:
|
|
eu_entry = copy.deepcopy(grafana)
|
|
eu_entry.setdefault("env", {})["GRAFANA_REGION"] = "eu"
|
|
servers["grafana-eu"] = eu_entry
|
|
else:
|
|
servers["grafana-eu"].setdefault("env", {})["GRAFANA_REGION"] = "eu"
|
|
|
|
# Pin GRAFANA_REGION on existing entry
|
|
grafana.setdefault("env", {})["GRAFANA_REGION"] = "us"
|
|
|
|
# Normalize args to recommended set
|
|
if slim:
|
|
grafana["args"] = SLIM_ARGS
|
|
if "grafana-eu" in servers:
|
|
servers["grafana-eu"]["args"] = SLIM_ARGS
|
|
|
|
with open(path, "w") as f:
|
|
json.dump(data, f, indent=2)
|
|
f.write("\n")
|
|
|
|
# --- Apply settings changes ---
|
|
|
|
if settings_changes and settings is not None:
|
|
bak_path = settings_path + ".bak"
|
|
if not os.path.exists(bak_path):
|
|
shutil.copy2(settings_path, bak_path)
|
|
|
|
if "permissions" not in settings:
|
|
settings["permissions"] = {}
|
|
if "allow" not in settings["permissions"]:
|
|
settings["permissions"]["allow"] = []
|
|
|
|
settings["permissions"]["allow"].extend(settings_changes)
|
|
|
|
with open(settings_path, "w") as f:
|
|
json.dump(settings, f, indent=2)
|
|
f.write("\n")
|
|
|
|
# --- Summary ---
|
|
|
|
print()
|
|
print("Migration complete!")
|
|
print()
|
|
for path, _data, _servers, changes in all_mcp_changes:
|
|
print(f" {path} -- {len(changes)} change(s) applied")
|
|
if settings_changes:
|
|
print(f" {settings_path} -- {len(settings_changes)} permission(s) added")
|
|
print()
|
|
print("Restart your MCP client to apply changes:")
|
|
print(" Claude Code: type /exit then restart")
|
|
print(" Cursor: close and reopen the editor")
|
|
print(" VS Code: Cmd+Shift+P > Developer: Reload Window")
|
|
PYEOF
|