From e96d76ab8e978766b2b37bdb95e59436da66bff1 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Jan 2026 00:37:38 +0000 Subject: [PATCH] feat(infra): integrate matrix-mana-bot into deployment - Add matrix-mana-bot to docker-compose.macmini.yml - Add setup-mana-bot.sh script for bot registration - Add dev:matrix:* scripts to root package.json - Add devlog entry documenting the new architecture The gateway bot is now ready for deployment alongside the existing standalone Matrix bots. https://claude.ai/code/session_015bwcqVRiFmSydYTjvDJGTc --- .../2026-01-29-matrix-mana-gateway-bot.md | 268 ++++++++++++++++++ docker-compose.macmini.yml | 38 +++ package.json | 10 + scripts/mac-mini/setup-mana-bot.sh | 160 +++++++++++ 4 files changed, 476 insertions(+) create mode 100644 apps/manacore/apps/landing/src/content/devlog/2026-01-29-matrix-mana-gateway-bot.md create mode 100755 scripts/mac-mini/setup-mana-bot.sh diff --git a/apps/manacore/apps/landing/src/content/devlog/2026-01-29-matrix-mana-gateway-bot.md b/apps/manacore/apps/landing/src/content/devlog/2026-01-29-matrix-mana-gateway-bot.md new file mode 100644 index 000000000..5bcba3ce2 --- /dev/null +++ b/apps/manacore/apps/landing/src/content/devlog/2026-01-29-matrix-mana-gateway-bot.md @@ -0,0 +1,268 @@ +--- +title: 'Matrix Mana Gateway Bot: Unified Bot Architecture' +description: 'Einführung des matrix-mana-bot als zentraler Gateway mit Shared Business Logic Package für alle Matrix Bots' +date: 2026-01-29 +author: 'Till Schneider' +category: 'architecture' +tags: + [ + 'matrix', + 'bot', + 'gateway', + 'architecture', + 'nestjs', + 'monorepo', + 'shared-packages', + ] +featured: true +commits: 3 +readTime: 8 +--- + +Einführung einer neuen Bot-Architektur mit dem **Matrix Mana Gateway Bot** - ein zentraler Bot, der alle Features vereint, während die Einzelbots weiterhin verfügbar bleiben. + +--- + +## Das Problem + +Bisher hatten wir **8 separate Matrix Bots**, jeder für eine spezifische Funktion: + +- matrix-ollama-bot (AI Chat) +- matrix-todo-bot (Aufgaben) +- matrix-calendar-bot (Termine) +- matrix-clock-bot (Timer/Alarme) +- matrix-nutriphi-bot (Ernährung) +- matrix-zitare-bot (Zitate) +- matrix-stats-bot (Analytics) +- matrix-project-doc-bot (Dokumentation) + +**Nachteile:** +- User müssen 8 verschiedene Bots einladen +- Kein Cross-Feature-Support ("Erstelle Todo aus Kalender-Event") +- Code-Duplikation zwischen Bots +- 8 Matrix-Verbindungen zu Synapse +- Hoher Ressourcenverbrauch + +--- + +## Die Lösung: Hybrid-Architektur + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ User Wahl │ +├──────────────────────────────┬──────────────────────────────────────────┤ +│ │ │ +│ @mana:mana.how │ @todo:mana.how │ +│ (Gateway - alles) │ (Nur Todos) │ +│ │ │ │ │ +│ ▼ │ ▼ │ +│ ┌─────────────────┐ │ ┌─────────────────┐ │ +│ │ matrix-mana-bot │ │ │ matrix-todo-bot │ │ +│ └────────┬────────┘ │ └────────┬────────┘ │ +│ │ │ │ │ +│ └──────────────────┴────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────────────────────┐ │ +│ │ @manacore/bot-services │ │ +│ │ (Shared Business Logic) │ │ +│ └──────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +**User können wählen:** +- **@mana** für alle Features in einem Bot +- **@todo/@calendar/etc.** für dedizierte Nutzung + +--- + +## Neues Package: @manacore/bot-services + +Ein **Shared Package** mit transport-agnostischer Business Logic: + +``` +packages/bot-services/ +├── src/ +│ ├── todo/ +│ │ ├── todo.service.ts # CRUD, Parsing, Stats +│ │ ├── todo.module.ts # NestJS Module +│ │ └── types.ts +│ ├── calendar/ +│ │ ├── calendar.service.ts +│ │ └── ... +│ ├── ai/ +│ │ ├── ai.service.ts # Ollama Integration +│ │ └── ... +│ ├── clock/ +│ │ ├── clock.service.ts # Timer, Alarm, WorldClock +│ │ └── ... +│ └── shared/ +│ ├── storage.ts # File/Memory Provider +│ └── utils.ts +``` + +**Vorteile:** +- Kein Matrix-Code in Services +- Testbar ohne Matrix +- Wiederverwendbar in Gateway und Einzelbots +- Pluggable Storage (File, Memory, Database) + +### Beispiel: TodoService + +```typescript +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class TodoService { + // Pure business logic - kein Matrix! + + async createTask(userId: string, input: CreateTaskInput): Promise { + const parsed = this.parseTaskInput(input.text); + return this.storage.create({ userId, ...parsed }); + } + + parseTaskInput(text: string): ParsedTask { + // "Einkaufen !p1 @morgen #haushalt" + // → { title: "Einkaufen", priority: 1, dueDate: "...", project: "haushalt" } + } +} +``` + +--- + +## Gateway Bot: matrix-mana-bot + +Der neue **Unified Gateway** kombiniert alle Features: + +``` +services/matrix-mana-bot/ +├── src/ +│ ├── bot/ +│ │ ├── matrix.service.ts # Matrix-Verbindung +│ │ └── command-router.service.ts # Routing +│ ├── handlers/ +│ │ ├── ai.handler.ts # !model, !all, chat +│ │ ├── todo.handler.ts # !todo, !list, !done +│ │ ├── calendar.handler.ts # !cal, !event +│ │ ├── clock.handler.ts # !timer, !alarm +│ │ └── help.handler.ts +│ └── orchestration/ +│ └── orchestration.service.ts # Cross-Feature AI +``` + +### Features + +| Kategorie | Commands | +|-----------|----------| +| **AI Chat** | Einfach tippen, `!model`, `!models`, `!all` | +| **Todos** | `!todo`, `!list`, `!today`, `!done`, `!delete` | +| **Kalender** | `!cal`, `!week`, `!event` | +| **Timer** | `!timer`, `!alarm`, `!time`, `!timers` | +| **Smart** | `!summary`, `!ai-todo` | + +### Cross-Feature Orchestration + +Der große Vorteil des Gateways - Features die mehrere Services kombinieren: + +```typescript +// !summary - AI-generierte Tages-Zusammenfassung +async dailySummary(ctx: CommandContext): Promise { + const [todoStats, todayTodos, todayEvents] = await Promise.all([ + this.todoService.getStats(ctx.userId), + this.todoService.getTodayTasks(ctx.userId), + this.calendarService.getTodayEvents(ctx.userId), + ]); + + const prompt = `Erstelle eine motivierende Tages-Zusammenfassung: + Todos: ${todoStats.pending} offen, ${todoStats.completed} erledigt + Termine: ${todayEvents.map(e => e.title).join(', ')}`; + + return this.aiService.chat(ctx.userId, prompt); +} + +// !ai-todo - AI extrahiert Todos aus Text +async aiToTodos(ctx: CommandContext, text: string): Promise { + const extracted = await this.aiService.extract(text); + for (const todo of extracted) { + await this.todoService.createTask(ctx.userId, todo); + } + return `✅ ${extracted.length} Todos erstellt`; +} +``` + +--- + +## Setup & Deployment + +### Bot registrieren + +```bash +./scripts/mac-mini/setup-mana-bot.sh +``` + +### Docker Compose + +```yaml +matrix-mana-bot: + image: matrix-mana-bot:latest + environment: + MATRIX_HOMESERVER_URL: http://synapse:8008 + MATRIX_ACCESS_TOKEN: ${MATRIX_MANA_BOT_TOKEN} + OLLAMA_URL: http://host.docker.internal:11434 + CLOCK_API_URL: http://matrix-clock-bot:3318/api/v1 + volumes: + - matrix_mana_bot_data:/app/data + ports: + - "3310:3310" +``` + +### Development + +```bash +pnpm dev:matrix:mana # Gateway starten +pnpm dev:matrix:todo # Todo-Bot starten +pnpm build:matrix:all # Alle Bots bauen +``` + +--- + +## Architektur-Entscheidungen + +### Warum Hybrid statt nur Gateway? + +| Aspekt | Nur Gateway | Nur Einzelbots | Hybrid ✓ | +|--------|-------------|----------------|----------| +| User Experience | ⭐⭐⭐ Einfach | ⭐ Komplex | ⭐⭐⭐ Flexibel | +| Cross-Features | ✅ Ja | ❌ Nein | ✅ Ja | +| Fehler-Isolation | ❌ | ✅ | ✅ | +| Power-User | ❌ | ✅ | ✅ | +| Ressourcen | ⭐⭐⭐ | ⭐ | ⭐⭐ | + +### Warum Shared Package? + +- **Kein Code-Duplikation** - Services einmal geschrieben +- **Testbarkeit** - Services ohne Matrix testbar +- **Flexibilität** - Neue Clients (CLI, Web) nutzen gleiche Logic +- **Konsistenz** - Gleiche Daten in Gateway und Einzelbots + +--- + +## Nächste Schritte + +1. **Bestehende Bots refactoren** um `@manacore/bot-services` zu nutzen +2. **Weitere Services implementieren** (Nutrition, Quotes, Stats, Docs) +3. **E2EE Support** für verschlüsselte Räume +4. **Reactions** für Feedback (`✅` = verstanden) + +--- + +## Zusammenfassung + +Mit dem **matrix-mana-bot** haben wir jetzt: + +- ✅ Einen zentralen Bot für alle Features +- ✅ Shared Business Logic Package +- ✅ Cross-Feature AI-Orchestration +- ✅ Weiterhin Einzelbots für Power-User +- ✅ DSGVO-konform (Self-Hosted) +- ✅ Natürliche Sprache + Commands diff --git a/docker-compose.macmini.yml b/docker-compose.macmini.yml index 20ec32283..c16b3a9ed 100644 --- a/docker-compose.macmini.yml +++ b/docker-compose.macmini.yml @@ -869,6 +869,42 @@ services: timeout: 10s retries: 3 + # ============================================ + # Matrix Mana Bot (Unified Gateway - All Features) + # ============================================ + + matrix-mana-bot: + image: matrix-mana-bot:latest + container_name: manacore-matrix-mana-bot + restart: always + depends_on: + synapse: + condition: service_healthy + environment: + NODE_ENV: production + PORT: 3310 + TZ: Europe/Berlin + MATRIX_HOMESERVER_URL: http://synapse:8008 + MATRIX_ACCESS_TOKEN: ${MATRIX_MANA_BOT_TOKEN} + MATRIX_ALLOWED_ROOMS: ${MATRIX_MANA_BOT_ROOMS:-} + MATRIX_STORAGE_PATH: /app/data/mana-bot-storage.json + OLLAMA_URL: http://host.docker.internal:11434 + OLLAMA_MODEL: ${OLLAMA_MODEL:-gemma3:4b} + OLLAMA_TIMEOUT: 120000 + CLOCK_API_URL: http://matrix-clock-bot:3318/api/v1 + TODO_STORAGE_PATH: /app/data/todos.json + CALENDAR_STORAGE_PATH: /app/data/calendar.json + volumes: + - matrix_mana_bot_data:/app/data + ports: + - "3310:3310" + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3310/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + # ============================================ # Matrix Ollama Bot (GDPR-compliant AI Chat) # ============================================ @@ -1172,6 +1208,8 @@ volumes: name: manacore-n8n synapse_data: name: manacore-synapse + matrix_mana_bot_data: + name: manacore-matrix-mana-bot matrix_ollama_bot_data: name: manacore-matrix-ollama-bot matrix_stats_bot_data: diff --git a/package.json b/package.json index 9785afb43..df61eab79 100644 --- a/package.json +++ b/package.json @@ -226,6 +226,16 @@ "dev:nutriphi-bot:full": "./scripts/setup-databases.sh nutriphi_bot && pnpm dev:nutriphi-bot", "nutriphi-bot:db:push": "pnpm --filter @manacore/telegram-nutriphi-bot db:push", "nutriphi-bot:db:studio": "pnpm --filter @manacore/telegram-nutriphi-bot db:studio", + "dev:matrix:mana": "pnpm --filter matrix-mana-bot start:dev", + "dev:matrix:ollama": "pnpm --filter matrix-ollama-bot start:dev", + "dev:matrix:todo": "pnpm --filter matrix-todo-bot start:dev", + "dev:matrix:calendar": "pnpm --filter matrix-calendar-bot start:dev", + "dev:matrix:clock": "pnpm --filter matrix-clock-bot start:dev", + "dev:matrix:stats": "pnpm --filter matrix-stats-bot start:dev", + "dev:matrix:zitare": "pnpm --filter matrix-zitare-bot start:dev", + "dev:matrix:nutriphi": "pnpm --filter matrix-nutriphi-bot start:dev", + "build:matrix:mana": "pnpm --filter matrix-mana-bot build", + "build:matrix:all": "pnpm --filter 'matrix-*-bot' build", "prepare": "husky" }, "devDependencies": { diff --git a/scripts/mac-mini/setup-mana-bot.sh b/scripts/mac-mini/setup-mana-bot.sh new file mode 100755 index 000000000..2769d7bdc --- /dev/null +++ b/scripts/mac-mini/setup-mana-bot.sh @@ -0,0 +1,160 @@ +#!/bin/bash +# Register and setup Matrix Mana Bot (Gateway) +# Run this after Matrix Synapse is running + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' + +echo "============================================" +echo " Matrix Mana Bot Setup" +echo "============================================" +echo "" + +# Default values +HOMESERVER_URL="${MATRIX_HOMESERVER_URL:-http://localhost:8008}" +BOT_USERNAME="mana" +BOT_DISPLAY_NAME="Mana" + +# Check if Synapse is running +echo "Checking Synapse..." +if ! curl -s "${HOMESERVER_URL}/health" > /dev/null 2>&1; then + echo -e "${RED}Error: Synapse is not reachable at ${HOMESERVER_URL}${NC}" + echo "Start it with: docker compose -f docker-compose.macmini.yml up -d synapse" + exit 1 +fi +echo -e "${GREEN}Synapse is running${NC}" +echo "" + +# Check if registration secret is available +if [ -z "$SYNAPSE_REGISTRATION_SECRET" ]; then + echo -e "${YELLOW}SYNAPSE_REGISTRATION_SECRET not set.${NC}" + echo "Please provide the registration secret from your .env file:" + read -sp "Registration Secret: " SYNAPSE_REGISTRATION_SECRET + echo "" +fi + +# Generate bot password +BOT_PASSWORD=$(openssl rand -base64 24) + +echo "Registering bot user @${BOT_USERNAME}..." + +# Generate HMAC for registration +generate_mac() { + local nonce=$1 + local user=$2 + local password=$3 + local user_type=$4 + local admin=$5 + + local mac_input="${nonce}\x00${user}\x00${password}\x00${user_type}\x00${admin}" + echo -n "$mac_input" | openssl dgst -sha1 -hmac "$SYNAPSE_REGISTRATION_SECRET" | cut -d' ' -f2 +} + +# Get nonce +NONCE=$(curl -s "${HOMESERVER_URL}/_synapse/admin/v1/register" | jq -r '.nonce') + +if [ -z "$NONCE" ] || [ "$NONCE" = "null" ]; then + echo -e "${RED}Failed to get registration nonce. Is admin registration enabled?${NC}" + exit 1 +fi + +# Calculate MAC +MAC=$(generate_mac "$NONCE" "$BOT_USERNAME" "$BOT_PASSWORD" "bot" "false") + +# Register user +REGISTER_RESPONSE=$(curl -s -X POST "${HOMESERVER_URL}/_synapse/admin/v1/register" \ + -H "Content-Type: application/json" \ + -d "{ + \"nonce\": \"${NONCE}\", + \"username\": \"${BOT_USERNAME}\", + \"password\": \"${BOT_PASSWORD}\", + \"displayname\": \"${BOT_DISPLAY_NAME}\", + \"user_type\": \"bot\", + \"admin\": false, + \"mac\": \"${MAC}\" + }") + +# Check if registration was successful +if echo "$REGISTER_RESPONSE" | jq -e '.access_token' > /dev/null 2>&1; then + ACCESS_TOKEN=$(echo "$REGISTER_RESPONSE" | jq -r '.access_token') + USER_ID=$(echo "$REGISTER_RESPONSE" | jq -r '.user_id') + + echo -e "${GREEN}Bot registered successfully!${NC}" + echo "" + echo -e "${CYAN}User ID:${NC} ${USER_ID}" + echo "" +else + ERROR=$(echo "$REGISTER_RESPONSE" | jq -r '.error // .errcode // "Unknown error"') + + # Check if user already exists + if echo "$ERROR" | grep -qi "user.*exists\|already.*registered\|M_USER_IN_USE"; then + echo -e "${YELLOW}User @${BOT_USERNAME} already exists. Getting access token via login...${NC}" + + echo "Please enter the existing bot password:" + read -sp "Password: " EXISTING_PASSWORD + echo "" + + LOGIN_RESPONSE=$(curl -s -X POST "${HOMESERVER_URL}/_matrix/client/r0/login" \ + -H "Content-Type: application/json" \ + -d "{ + \"type\": \"m.login.password\", + \"user\": \"${BOT_USERNAME}\", + \"password\": \"${EXISTING_PASSWORD}\" + }") + + if echo "$LOGIN_RESPONSE" | jq -e '.access_token' > /dev/null 2>&1; then + ACCESS_TOKEN=$(echo "$LOGIN_RESPONSE" | jq -r '.access_token') + USER_ID=$(echo "$LOGIN_RESPONSE" | jq -r '.user_id') + echo -e "${GREEN}Login successful!${NC}" + else + echo -e "${RED}Login failed. Please check the password.${NC}" + exit 1 + fi + else + echo -e "${RED}Registration failed: ${ERROR}${NC}" + exit 1 + fi +fi + +echo "" +echo "============================================" +echo " Add to .env file" +echo "============================================" +echo "" +echo -e "${CYAN}# Matrix Mana Bot (Gateway)${NC}" +echo "MATRIX_MANA_BOT_TOKEN=${ACCESS_TOKEN}" +echo "" + +# Optional: Set display name and avatar +echo "Setting display name..." +curl -s -X PUT "${HOMESERVER_URL}/_matrix/client/r0/profile/${USER_ID}/displayname" \ + -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{\"displayname\": \"🤖 ${BOT_DISPLAY_NAME}\"}" > /dev/null + +echo "" +echo "============================================" +echo " Next Steps" +echo "============================================" +echo "" +echo "1. Add the MATRIX_MANA_BOT_TOKEN to your .env file" +echo "" +echo "2. Build the bot image:" +echo " docker build -t matrix-mana-bot ./services/matrix-mana-bot" +echo "" +echo "3. Start the bot:" +echo " docker compose -f docker-compose.macmini.yml up -d matrix-mana-bot" +echo "" +echo "4. Invite the bot to a room in Element:" +echo " /invite @mana:mana.how" +echo "" +echo -e "${GREEN}Setup complete!${NC}"