From 289e41a040dab21bf77b391e5eac3f211bb44af6 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Tue, 17 Mar 2026 22:09:13 +0100 Subject: [PATCH] feat: integrate and deploy kabelfachmann mcp --- .gitea/workflows/deploy.yml | 2 + docker-compose.dev.yml | 15 ++++- docker-compose.yml | 14 +++++ package.json | 4 ++ scripts/qdrant-sync.sh | 120 ++++++++++++++++++++++++++++++++++++ 5 files changed, 154 insertions(+), 1 deletion(-) create mode 100755 scripts/qdrant-sync.sh diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index c8b16db2..0e3a4ac6 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -292,6 +292,7 @@ jobs: QDRANT_URL: ${{ secrets.QDRANT_URL || vars.QDRANT_URL || 'http://klz-qdrant:6333' }} QDRANT_API_KEY: ${{ secrets.QDRANT_API_KEY || vars.QDRANT_API_KEY }} REDIS_URL: ${{ secrets.REDIS_URL || vars.REDIS_URL || 'redis://klz-redis:6379' }} + KABELFACHMANN_MCP_URL: ${{ secrets.KABELFACHMANN_MCP_URL || vars.KABELFACHMANN_MCP_URL || 'http://klz-kabelfachmann:3007/sse' }} # Container Registry (standalone) REGISTRY_USER: ${{ secrets.REGISTRY_USER }} REGISTRY_PASS: ${{ secrets.REGISTRY_PASS }} @@ -358,6 +359,7 @@ jobs: echo "QDRANT_URL=$QDRANT_URL" echo "QDRANT_API_KEY=$QDRANT_API_KEY" echo "REDIS_URL=$REDIS_URL" + echo "KABELFACHMANN_MCP_URL=$KABELFACHMANN_MCP_URL" echo "" echo "TARGET=$TARGET" echo "SENTRY_ENVIRONMENT=$TARGET" diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 4f8610d3..a8bf525a 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -112,9 +112,22 @@ services: - klz_qdrant_data:/qdrant/storage networks: - default - ports: - "16333:6333" + klz-kabelfachmann: + image: registry.infra.mintel.me/mintel/kabelfachmann-mcp:${IMAGE_TAG:-latest} + restart: unless-stopped + networks: + - default + env_file: + - ${ENV_FILE:-.env} + environment: + QDRANT_URL: http://klz-qdrant:6333 + ports: + - "3007:3007" + depends_on: + - klz-qdrant + networks: default: name: ${PROJECT_NAME:-klz-cables}-internal diff --git a/docker-compose.yml b/docker-compose.yml index 939f9cb8..ee5c1c56 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -119,6 +119,20 @@ services: networks: - default + klz-kabelfachmann: + image: registry.infra.mintel.me/mintel/kabelfachmann-mcp:${IMAGE_TAG:-latest} + restart: unless-stopped + networks: + - default + env_file: + - ${ENV_FILE:-.env} + environment: + QDRANT_URL: http://klz-qdrant:6333 + ports: + - "3007:3007" + depends_on: + - klz-qdrant + networks: default: name: ${PROJECT_NAME:-klz-cables}-internal diff --git a/package.json b/package.json index 6cdda423..031f28bc 100644 --- a/package.json +++ b/package.json @@ -138,6 +138,10 @@ "assets:pull:prod": "bash ./scripts/assets-sync.sh prod local", "assets:sync:testing-to-staging": "bash ./scripts/assets-sync.sh testing staging", "assets:sync:staging-to-prod": "bash ./scripts/assets-sync.sh staging prod", + "qdrant:push:testing": "bash ./scripts/qdrant-sync.sh testing", + "qdrant:push:staging": "bash ./scripts/qdrant-sync.sh staging", + "qdrant:push:prod": "bash ./scripts/qdrant-sync.sh prod", + "qdrant:push:branch": "bash ./scripts/qdrant-sync.sh", "pagespeed:test": "tsx ./scripts/pagespeed-sitemap.ts", "pagespeed:audit": "./scripts/audit-local.sh", "pagespeed:urls": "tsx -e \"import sitemap from './app/sitemap'; sitemap().then(urls => console.log(urls.map(u => u.url).join('\\n')))\"", diff --git a/scripts/qdrant-sync.sh b/scripts/qdrant-sync.sh new file mode 100755 index 00000000..6d7e4462 --- /dev/null +++ b/scripts/qdrant-sync.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash +# ──────────────────────────────────────────────────────────────────────────── +# Qdrant Snapshot Sync Tool +# Syncs a Qdrant collection from the local machine to a remote environment +# using the safe Snapshot API to avoid RocksDB corruption. +# ──────────────────────────────────────────────────────────────────────────── +set -euo pipefail + +# Load environment variables +if [ -f .env ]; then + set -a; source .env; set +a +fi + +# ── Configuration ────────────────────────────────────────────────────────── +TARGET_ENV="${1:-}" # testing | staging | branch_slug | prod +COLLECTION="${2:-kabelfachmann}" +SSH_HOST="root@alpha.mintel.me" + +if [[ -z "$TARGET_ENV" ]]; then + echo "Usage: pnpm run qdrant:push [collection]" + echo "Example: pnpm run qdrant:push testing kabelfachmann" + echo "Example: pnpm run qdrant:push mein-feature-slug kabelfachmann" + exit 1 +fi + +LOCAL_QDRANT_URL=${QDRANT_URL:-"http://localhost:16333"} + +get_target_path() { + case "$1" in + testing) echo "/home/deploy/sites/testing.klz-cables.com" ;; + staging) echo "/home/deploy/sites/staging.klz-cables.com" ;; + prod|production) echo "/home/deploy/sites/klz-cables.com" ;; + *) echo "/home/deploy/sites/branch.klz-cables.com/$1" ;; + esac +} + +get_project_name() { + case "$1" in + testing) echo "klz-testing" ;; + staging) echo "klz-staging" ;; + prod|production) echo "klz-cablescom" ;; + *) echo "klz-branch-$1" ;; + esac +} + +TGT_PATH=$(get_target_path "$TARGET_ENV") +PROJECT_NAME=$(get_project_name "$TARGET_ENV") +QDRANT_CONTAINER="${PROJECT_NAME}-klz-qdrant-1" +WORK_DIR=$(mktemp -d) + +echo "🚀 Syncing Qdrant Collection '$COLLECTION' to: $TARGET_ENV" + +# 1. Create Snapshot Locally +echo "📸 1/5 Creating snapshot on local Qdrant ($LOCAL_QDRANT_URL)..." +SNAPSHOT_INFO=$(curl -s -X POST "$LOCAL_QDRANT_URL/collections/$COLLECTION/snapshots") + +if ! echo "$SNAPSHOT_INFO" | grep -q '"status":"ok"'; then + echo "❌ Failed to create snapshot." + echo "Response: $SNAPSHOT_INFO" + exit 1 +fi + +SNAPSHOT_NAME=$(echo "$SNAPSHOT_INFO" | grep -o '"name":"[^"]*' | cut -d'"' -f4) +echo " ✅ Snapshot created: $SNAPSHOT_NAME" + +# 2. Download Snapshot +echo "⬇️ 2/5 Downloading snapshot..." +curl -s -o "$WORK_DIR/$SNAPSHOT_NAME" "$LOCAL_QDRANT_URL/collections/$COLLECTION/snapshots/$SNAPSHOT_NAME" +echo " ✅ Downloaded to $WORK_DIR/$SNAPSHOT_NAME" + +# 3. Transfer Snapshot +echo "📤 3/5 Uploading snapshot to Alpha ($SSH_HOST)..." +ssh "$SSH_HOST" "mkdir -p $TGT_PATH/qdrant_tmp" +scp "$WORK_DIR/$SNAPSHOT_NAME" "$SSH_HOST:$TGT_PATH/qdrant_tmp/$SNAPSHOT_NAME" +echo " ✅ Upload complete." + +# 4. Restore Snapshot on Remote Server +echo "🔄 4/5 Restoring snapshot on target container ($QDRANT_CONTAINER)..." + +# Qdrant restore process: +# - Recreate collection (so it is clean) +# - Download snapshot to container +# - Recover from snapshot file + +ssh "$SSH_HOST" << EOF + set -e + # Step A: Copy file into the container + docker cp "$TGT_PATH/qdrant_tmp/$SNAPSHOT_NAME" $QDRANT_CONTAINER:/qdrant/$SNAPSHOT_NAME + + # Step B: Delete existing collection + curl -s -X DELETE "http://127.0.0.1:6333/collections/$COLLECTION" > /dev/null + + # Step C: Re-create empty collection (required before recovery) + # wir nutzen die standard vector config vom Kabelfachmann (Cosine, 384 dim für all-MiniLM-L6-v2) + curl -s -X PUT "http://127.0.0.1:6333/collections/$COLLECTION" \ + -H 'Content-Type: application/json' \ + -d '{ "vectors": { "size": 384, "distance": "Cosine" } }' > /dev/null + + # Step D: Recover + echo " [Remote] Triggering recover API..." + curl -s -X PUT "http://127.0.0.1:6333/collections/$COLLECTION/snapshots/recover" \ + -H 'Content-Type: application/json' \ + -d '{ "location": "file:///qdrant/'$SNAPSHOT_NAME'" }' > /dev/null + + # Step E: Cleanup + docker exec $QDRANT_CONTAINER rm /qdrant/$SNAPSHOT_NAME + rm -rf "$TGT_PATH/qdrant_tmp" +EOF + +echo " ✅ Restore complete." + +# 5. Local Cleanup +echo "🧹 5/5 Cleaning up..." +rm -rf "$WORK_DIR" +# Delete snapshot from local Qdrant server to save space +curl -s -X DELETE "$LOCAL_QDRANT_URL/collections/$COLLECTION/snapshots/$SNAPSHOT_NAME" > /dev/null +echo " ✅ Local cleanup done." + +echo "" +echo "🎉 Successfully synced Qdrant collection '$COLLECTION' to $TARGET_ENV!"