CRM-Backend-Architektur

Phase 22.0–48. Status: DEPLOYED (VPS live, 68+ Endpunkte, modulare Architektur). Zuletzt aktualisiert: 2026-04-28 (Phase 48 — Architektur-Dekomposition)

Überblick

Browser (:18888)
  │
  ├── Static files ──► Nginx ──► Docker frontend (React + Nginx)
  │
  ├── /api/crm/* ────► Nginx ──► Bun Master Bot (:19210)
  │                                 ├── crmAuthMiddleware (HMAC-SHA256)
  │                                 └── shared/routes/router.ts → 17 Domain-Module
  │
  ├── /api/auth/* ───► Nginx ──► Bun (:19210) → master-bot/routes/auth.ts
  ├── /api/cli/*  ───► Nginx ──► Bun (:19210) → master-bot/routes/cli.ts
  ├── /api/internal/* ► Bun (:19210, loopback only) → master-bot/routes/internal.ts
  │
  ├── /api/sse/* ────► Nginx (unbuffered) ──► Bun (:19210)
  │
  └── /ws/terminal/* ► Nginx (upgrade) ──► Bun (:19210) → master-bot/routes/websocket.ts

Modulstruktur (Phase 48)

API-Server (master-bot/api-server.ts, 196 Zeilen) — schlanker Dispatcher:

CRM-Routen (shared/routes/router.ts, 512 Zeilen) — Dispatch-Tabelle für 17 Domain-Module:

Authentifizierungsfluss

User ──► LoginOverlay (frontend)
         │
         ├── E-Mail/Passwort → POST /api/auth/login
         └── OAuth → /api/auth/{google,github} → callback
         │
         ▼
    Erfolgreiche Auth → HMAC-SHA256-Token
         │
         ├── payload = { sub: userId, iat, exp: +24h }
         ├── signature = HMAC-SHA256(base64url(payload), CRM_SECRET)
         └── token = base64url(payload) + "." + signature
         │
         ▼
    localStorage('crm-token') → Alle API-Aufrufe: Authorization: Bearer <token>
         │
         ▼
    Token läuft ab → 401 → Login erforderlich

Wichtige Dateien

Datei Rolle
shared/auth.ts Token-Generierung/-Verifizierung, E-Mail/OAuth, Passwort-Reset, E-Mail-Verifizierung, CORS, Middleware
shared/vault.ts AES-256-GCM Secret-Speicher (CRM_SECRET wird auto-erstellt)
frontend/src/crm/components/LoginOverlay.jsx Login-UI (E-Mail, OAuth, Passwort-Reset, E-Mail-Verifizierung)

Sicherheitseigenschaften

CRM API-Routen

Alle Routen unter /api/crm/ erfordern Authorization: Bearer <token>.

GET /api/crm/projects

Listet alle registrierten Projekte mit Live-Health-Status auf. Master ist als Pseudo-Projekt enthalten (DEBT-6).

Feld Quelle
name, type, bot_username config/bot_registry.json (Master: virtueller Eintrag, type: "system")
healthy HTTP-Health-Check (Children: Child-Port, Master: /api/master/health)
tmux_alive tmux has-session -t <name> (Master: citadel-master)
status Heartbeat-Datei oder abgeleitet aus health+tmux (Master: healthy/degraded/down)

Master ist immer als erster Eintrag im Response-Array vorangestellt.

GET /api/crm/projects/:name

Vollständige Projektdetailkarte.

Feld Quelle
manifest Rohtext aus <cwd>/MANIFEST.md
heartbeat JSON aus state/heartbeat_<name>.json
skills[] <cwd>/skills/*.md-Dateinamen (ohne _*.md)
healthy, tmux_alive Wie beim List-Endpunkt

GET /api/crm/projects/:name/logs

JSONL-strukturierte Logs tailing.

Parameter Standard Max
lines 100 500
category dialog system, dialog, error

Quelle: /var/log/citadel/<name>/<category>-YYYY-MM-DD.log

Liest die neuesten Log-Dateien zuerst, parst JSONL, gibt in chronologischer Reihenfolge zurück.

GET /api/crm/projects/:name/files

Verzeichnis-Listing mit Path-Traversal-Schutz.

Parameter Standard Beschreibung
path / Relativer Pfad innerhalb des Projekt-cwd

Sicherheit: resolve(base, requested) muss startsWith(resolve(base)). Verletzung → 403.

Response-Einträge: { name, type: "file"|"directory", size?, modified }, sortiert Verzeichnisse zuerst.

GET /api/crm/projects/:name/skills

Installierte Skills mit Quellzuordnung.

Quelle Erkennung
file <cwd>/skills/*.md (ohne _*.md)
manifest MANIFEST.mdskills[] + library_skills[] (JSON-Parse)

Dedupliziert: Datei-Quelle hat Vorrang vor Manifest.

POST /api/crm/projects/:name/restart (Phase 22.3)

Startet einen Child Bot über den Watchdog neu.

Aspekt Detail
Methode Nur POST (GET gibt 405 zurück)
Auth HMAC-SHA256-Token (wie andere CRM-Routen)
Cooldown 30 Sekunden pro Projekt (gibt 429 zurück, wenn zu häufig)
Mechanismus Ruft restartChild() von watchdog.ts auf
Antwort { ok: true } oder { error: "..." }

GET /api/crm/projects/:name/metrics (Phase 22.1)

Qualitätsmetriken-Zeitreihen für Sparkline-Diagramme.

Parameter Standard Beschreibung
days 7 Anzahl der zu aggregierenden Tage

Quelle: /var/log/citadel/<name>/quality-events.log → tägliche Erfolgs-/Fehleranzahlen.

WS /ws/terminal/:name (Phase 22.3)

WebSocket-Terminal, streamt tmux-Pane-Inhalt.

Aspekt Detail
Auth ?token=-Query-Parameter (verifiziert via verifyToken().valid)
Modus Standardmäßig nur lesend; ?mode=interactive aktiviert send-keys
Poll tmux capture-pane -p -e -t {session} -S -80 alle 200 ms
Delta Sendet nur bei Inhaltsänderung gegenüber vorherigem Zustand

Sicherheit: Path-Traversal-Schutz (Phase 22.3, DEBT-7)

Alle Endpunkte validieren Projektnamen vor der Verarbeitung:

const SAFE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
export function isValidProjectName(name: string): boolean {
  return name.length > 0 && name.length <= 64 && SAFE_NAME_RE.test(name);
}

Angewendet an 3 Einstiegspunkten: routeCrmRequest(), routeSseRequest(), WebSocket-Upgrade-Handler in bot.ts. Ungültige Namen → 403 Forbidden.

Datenintegrität: Atomare Schreibvorgänge (Phase 22.3, DEBT-2)

Registry-Schreibvorgänge in onboarding.ts nutzen atomares Muster:

async function atomicWriteJson(filePath: string, data: unknown): Promise<void> {
  const tmp = `${filePath}.tmp.${process.pid}`;
  await Bun.write(tmp, JSON.stringify(data, null, 2));
  Bun.spawn(["mv", tmp, filePath]); // POSIX atomic rename
}

Wichtige Dateien

Datei Rolle Zeilen
shared/crm-routes.ts 55+ Handler + routeCrmRequest() Router + isValidProjectName() ~4800
shared/sage.ts Sage Worker: runSageAnalysis(), runBenchmark(), LLM-Judge, A/B-Testing ~550
shared/db.ts SQLite SSOT: userQueries, projectQueries, skillQueries, benchmarkQueries, chatQueries ~600
shared/auth.ts Token-Generierung, -Verifizierung, CORS, Middleware ~100
master-bot/bot.ts Bun.serve: HTTP-Router + WebSocket-Terminal-Handler ~1160
master-bot/onboarding.ts atomicWriteJson() für Registry-Schreibvorgänge ~850

Nginx-Proxy

Datei: infra/nginx/citadel-crm.conf — ersetzt Legacy-Konfiguration citadel-os.

Drei Upstreams:

Upstream Port Service
crm_backend 19210 Bun Master Bot (CRM API, Health)
phaser_frontend 18889 Docker Phaser Office (Legacy)
mcp_bridge 19200 Docker MCP State-Bridge (Legacy)
Location Backend Auth Sonderkonfiguration
/ phaser_frontend Basic Auth Docker Phaser Frontend
/api/crm/ crm_backend Token (kein Basic) 30s Timeout, Keepalive
/api/master/health crm_backend Keine Öffentlicher Health-Check
/api/sse/ crm_backend Token (kein Basic) proxy_buffering off, 1h Timeout (Phase 22.1)
/ws/ crm_backend Token (kein Basic) WebSocket-Upgrade, 1h Timeout (Phase 22.1)
/api/ mcp_bridge Keine (kein Basic) Legacy State API + SSE
/health mcp_bridge Keine Legacy Bridge Health

Alle Locations leiten X-Real-IP, X-Forwarded-For, X-Forwarded-Proto weiter.

Blockierte Pfade: /.* (Dotfiles), /config/, /state/, /scripts/.

Hinweis: Die Nginx-Location-Priorität stellt sicher, dass /api/crm/ und /api/sse/ vor dem Catch-All /api/ (Legacy MCP) abgeglichen werden.

Integration im Master Bot

master-bot/bot.ts Bun.serve (:19210):

async fetch(req) {
  /api/master/health  →  öffentlich, keine Auth
  /api/crm/*          →  CORS Preflight → crmAuthMiddleware → routeCrmRequest()
  *                   →  404
}

Der Router dispatcht zu Handler-Funktionen aus shared/crm-routes.ts. CORS-Header werden bei jeder CRM-Antwort eingefügt (einschließlich 401-Fehler).

Endpunkt-Übersicht (Alle Phasen)

Phase Feature Endpunkt Status
22.0 Projektliste GET /api/crm/projects DONE
22.0 Projektdetails GET /api/crm/projects/:name DONE
22.0 Log-Tailing GET /api/crm/projects/:name/logs DONE
22.0 Datei-Listing GET /api/crm/projects/:name/files DONE
22.0 Skills-Listing GET /api/crm/projects/:name/skills DONE
22.1 SSE-Log-Streaming GET /api/sse/logs/:name DONE
22.1 Metriken-Historie GET /api/crm/projects/:name/metrics DONE
22.3 Projekt-Restart POST /api/crm/projects/:name/restart DONE
22.3 WebSocket-Terminal WS /ws/terminal/:name DONE
24.5 Specs CRUD GET/POST /api/crm/projects/:name/specs DONE
24.5 Aktive Rolle GET/POST /api/crm/projects/:name/active-role DONE
25 Skills-Bundle GET /api/crm/projects/:name/skills-bundle DONE
25 Learnings-Sync GET/POST /api/crm/projects/:name/learnings DONE
26 Workers CRUD GET/POST /api/crm/projects/:name/workers DONE
32 Wiki speichern PUT /api/crm/projects/:name/wiki/save DONE
32 Skills speichern/löschen PUT/DELETE /api/crm/projects/:name/skills/* DONE
33 Account-Einstellungen GET/PUT /api/crm/account/settings DONE
33 Projekt erstellen POST /api/crm/projects/create DONE
34 Issue CRUD POST/GET/PUT /api/mcp/issues/:project DONE
34 Wiki MCP PUT /api/mcp/wiki/:project DONE
34 Roadmap-Sync GET/PUT /api/mcp/roadmap/:project DONE
34 CLI-Init GET /api/cli/init/:project/:mode DONE
35 Terminal-Log-Ingest POST /api/crm/projects/:name/terminal/log DONE
36 Cloud PM-Chat POST /api/crm/projects/:name/chat DONE
36.6 Skill-Generierung POST /api/crm/projects/:name/skills/generate DONE
36.7 Notebooks-Listing GET /api/crm/projects/:name/notebooks DONE
40.14 Roadmap (Inhalt) GET /api/mcp/roadmap/:project (+ content-Feld) DONE

Gesamt: 46+ Endpunkte (REST + SSE + WebSocket + CLI/MCP)

NotebookLM Bridge (Phase 36.3)

Separater Python FastAPI-Service unter :19213 (nur localhost). Kein Bestandteil der Bun CRM API.

Endpunkt Methode Zweck
/query POST Semantische Suche via Google NotebookLM
/sync POST Quelle für asynchronen Sync in Warteschlange stellen (fire-and-forget)
/notebooks/init POST Notebook für neues Projekt erstellen
/health GET Auth-Status, Notebook-Anzahl, Queue-Statistiken

Der Bun CRM löst Fire-and-forget-Aufrufe an die Bridge aus bei:

Technische Schulden

Vollständige Registry unter docs/backlog/technical-debt.md. Wichtige Einträge:

ID Titel Status
DEBT-2 Atomare JSON-Schreibvorgänge GELÖST (Phase 22.3)
DEBT-3 Eingebackener Docker-Token → Sitzungs-Auth GELÖST (Phase 31)
DEBT-6 Master Bot CRM-Blindstelle GELÖST (Phase 22.3)
DEBT-7 Path-Traversal-Abwehr GELÖST (Phase 22.3)

Gepflegt von Rick (Orchestrator). Nach jeder Phasen-Fertigstellung aktualisiert.