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:
master-bot/routes/auth.ts(562) — OAuth, Login, Register, 2FA, Device Codemaster-bot/routes/internal.ts(282) — chat/save, bridge-event, relay, Binary Downloadsmaster-bot/routes/cli.ts(293) — ARC CLI + MCP-Integrationmaster-bot/routes/websocket.ts(303) — Terminal, Event-Stream, Local Bridge
CRM-Routen (shared/routes/router.ts, 512 Zeilen) — Dispatch-Tabelle für 17 Domain-Module:
projects.ts(723),workers.ts(636),skills.ts(665),chat.ts(862)sage.ts(757),onboarding.ts(601),files.ts(381),wiki.ts(258)system.ts,pins.ts,learnings.ts,docs.ts,specs.ts,restart.ts,analytics.ts_utils.ts(343) — gemeinsame Typen, Pfadsicherheit, Auth-Helfer
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_SECRET: 32-Byte zufälliges Hex, vault-verschlüsselt (AES-256-GCM)
- Token-TTL: 24 Stunden, nicht erneuerbar
- Signatur: HMAC-SHA256, Base64url-Encoding (ohne Padding)
- CORS: Origin-Echo, beschränkt auf Authorization + Content-Type-Header
- E-Mail-Verifizierung für neue Accounts vor dem Login erforderlich
- Nginx Basic Auth bleibt als äußere Defense-in-Depth-Schicht
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.md → skills[] + 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:
handleUpdateIssue(Issue schließen/öffnen) →/synchandleMcpWikiUpdate(Wiki speichern) →/synchandleCreateProject/handleOnboardingSetup→/notebooks/initexecuteAskNotebooklm→/query(15s Timeout, Fallback auf lokal)
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.