Architektura backendu CRM
Phase 22.0–48. Status: DEPLOYED (VPS aktywny, 68+ endpointów, architektura modułowa). Ostatnia aktualizacja: 2026-04-28 (Phase 48 — Dekompozycja Architektury)
Ogólny opis
Przeglądarka (:18888)
│
├── Pliki statyczne ──► Nginx ──► Docker frontend (React + Nginx)
│
├── /api/crm/* ────► Nginx ──► Bun Master Bot (:19210)
│ ├── crmAuthMiddleware (HMAC-SHA256)
│ └── shared/routes/router.ts → 17 modułów domenowych
│
├── /api/auth/* ───► Nginx ──► Bun (:19210) → master-bot/routes/auth.ts
├── /api/cli/* ───► Nginx ──► Bun (:19210) → master-bot/routes/cli.ts
├── /api/internal/* ► Bun (:19210, tylko loopback) → master-bot/routes/internal.ts
│
├── /api/sse/* ────► Nginx (bez buforowania) ──► Bun (:19210)
│
└── /ws/terminal/* ► Nginx (upgrade) ──► Bun (:19210) → master-bot/routes/websocket.ts
Struktura modułów (Phase 48)
Serwer API (master-bot/api-server.ts, 196 linii) — cienki dispatcher:
master-bot/routes/auth.ts(562) — OAuth, logowanie, rejestracja, 2FA, kod urządzeniamaster-bot/routes/internal.ts(282) — chat/save, bridge-event, relay, pobieranie binarekmaster-bot/routes/cli.ts(293) — integracja ARC CLI + MCPmaster-bot/routes/websocket.ts(303) — terminal, strumień zdarzeń, local-bridge
Trasy CRM (shared/routes/router.ts, 512 linii) — tabela dispatchingu do 17 modułów domenowych:
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) — wspólne typy, bezpieczeństwo ścieżek, helpery auth
Przepływ uwierzytelniania
Użytkownik ──► LoginOverlay (frontend)
│
├── Email/hasło → POST /api/auth/login
└── OAuth → /api/auth/{google,github} → callback
│
▼
Pomyślne uwierzytelnienie → token HMAC-SHA256
│
├── payload = { sub: userId, iat, exp: +24h }
├── signature = HMAC-SHA256(base64url(payload), CRM_SECRET)
└── token = base64url(payload) + "." + signature
│
▼
localStorage('crm-token') → Wszystkie wywołania API: Authorization: Bearer <token>
│
▼
Token wygasł → 401 → wymagane zalogowanie
Kluczowe pliki
| Plik | Rola |
|---|---|
shared/auth.ts |
Generowanie/weryfikacja tokenów, email/OAuth, reset hasła, weryfikacja email, CORS, middleware |
shared/vault.ts |
Przechowywanie sekretów AES-256-GCM (CRM_SECRET tworzony automatycznie) |
frontend/src/crm/components/LoginOverlay.jsx |
UI logowania (email, OAuth, reset hasła, weryfikacja email) |
Właściwości bezpieczeństwa
- CRM_SECRET: 32-bajtowy losowy hex, zaszyfrowany w vault (AES-256-GCM)
- TTL tokenu: 24 godziny, nieodnawialny
- Sygnatura: HMAC-SHA256, kodowanie base64url (bez paddingu)
- CORS: echo origin, ograniczone do nagłówków Authorization + Content-Type
- Wymagana weryfikacja email przed logowaniem dla nowych kont
- Basic auth nginx pozostaje jako zewnętrzna warstwa obrony wgłębnej
Trasy CRM API
Wszystkie trasy pod /api/crm/ wymagają Authorization: Bearer <token>.
GET /api/crm/projects
Lista wszystkich zarejestrowanych projektów ze statusem zdrowia na żywo. Zawiera mastera jako pseudo-projekt (DEBT-6).
| Pole | Źródło |
|---|---|
name, type, bot_username |
config/bot_registry.json (master: wirtualny wpis, type: "system") |
healthy |
Kontrola zdrowia HTTP (dzieci: port child, master: /api/master/health) |
tmux_alive |
tmux has-session -t <nazwa> (master: citadel-master) |
status |
Plik heartbeat lub pochodny ze zdrowia+tmux (master: healthy/degraded/down) |
Master jest zawsze dołączany jako pierwszy element tablicy odpowiedzi.
GET /api/crm/projects/:name
Pełna karta szczegółów projektu.
| Pole | Źródło |
|---|---|
manifest |
Tekst surowy z <cwd>/MANIFEST.md |
heartbeat |
JSON z state/heartbeat_<nazwa>.json |
skills[] |
Nazwy plików <cwd>/skills/*.md (z wyłączeniem _*.md) |
healthy, tmux_alive |
Tak samo jak endpoint listy |
GET /api/crm/projects/:name/logs
Ogon logów strukturalnych JSONL.
| Parametr | Domyślnie | Maks |
|---|---|---|
lines |
100 | 500 |
category |
dialog |
system, dialog, error |
Źródło: /var/log/citadel/<nazwa>/<kategoria>-YYYY-MM-DD.log
Odczytuje najnowsze pliki logów jako pierwsze, parsuje JSONL, zwraca w kolejności chronologicznej.
GET /api/crm/projects/:name/files
Listing katalogu z ochroną przed path traversal.
| Parametr | Domyślnie | Opis |
|---|---|---|
path |
/ |
Względna ścieżka wewnątrz cwd projektu |
Bezpieczeństwo: resolve(base, requested) musi mieć startsWith(resolve(base)). Naruszenie → 403.
Elementy odpowiedzi: { name, type: "file"|"directory", size?, modified }, posortowane — katalogi pierwsze.
GET /api/crm/projects/:name/skills
Zainstalowane skille z atrybucją źródła.
| Źródło | Wykrywanie |
|---|---|
file |
<cwd>/skills/*.md (z wyłączeniem _*.md) |
manifest |
MANIFEST.md → skills[] + library_skills[] (parsowanie JSON) |
Deduplikowane: źródło plikowe ma priorytet nad manifestem.
POST /api/crm/projects/:name/restart (Phase 22.3)
Restartuj child bota przez watchdoga.
| Aspekt | Szczegół |
|---|---|
| Metoda | Tylko POST (GET zwraca 405) |
| Auth | Token HMAC-SHA256 (tak samo jak inne trasy CRM) |
| Cooldown | 30 sekund per projekt (zwraca 429 jeśli zbyt często) |
| Mechanizm | Wywołuje restartChild() z watchdog.ts |
| Odpowiedź | { ok: true } lub { error: "..." } |
GET /api/crm/projects/:name/metrics (Phase 22.1)
Szereg czasowy metryk jakości dla wykresów sparkline.
| Parametr | Domyślnie | Opis |
|---|---|---|
days |
7 | Liczba dni do agregacji |
Źródło: /var/log/citadel/<nazwa>/quality-events.log → liczby sukces/porażka per dzień.
WS /ws/terminal/:name (Phase 22.3)
Terminal WebSocket streamujący zawartość panelu tmux.
| Aspekt | Szczegół |
|---|---|
| Auth | Parametr query ?token= (weryfikowany przez verifyToken().valid) |
| Tryb | Domyślnie tylko do odczytu; ?mode=interactive umożliwia send-keys |
| Polling | tmux capture-pane -p -e -t {sesja} -S -80 co 200ms |
| Delta | Wysyła tylko gdy zawartość zmieniła się względem poprzedniej |
Bezpieczeństwo: ochrona przed Path Traversal (Phase 22.3, DEBT-7)
Wszystkie endpointy walidują nazwy projektów przed przetworzeniem:
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);
}
Stosowane w 3 punktach wejścia: routeCrmRequest(), routeSseRequest(), handler upgrade WebSocket w bot.ts.
Nieprawidłowe nazwy → 403 Forbidden.
Integralność danych: Atomiczne zapisy (Phase 22.3, DEBT-2)
Zapisy do rejestru w onboarding.ts stosują wzorzec atomiczny:
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]); // atomiczne rename POSIX
}
Kluczowe pliki
| Plik | Rola | Linie |
|---|---|---|
shared/crm-routes.ts |
55+ handlerów + router routeCrmRequest() + isValidProjectName() |
~4800 |
shared/sage.ts |
Sage Worker: runSageAnalysis(), runBenchmark(), sędzia LLM, testowanie A/B |
~550 |
shared/db.ts |
SQLite jako SSOT: userQueries, projectQueries, skillQueries, benchmarkQueries, chatQueries | ~600 |
shared/auth.ts |
Generowanie tokenów, weryfikacja, CORS, middleware | ~100 |
master-bot/bot.ts |
Bun.serve: router HTTP + handler terminala WebSocket | ~1160 |
master-bot/onboarding.ts |
atomicWriteJson() do zapisów w rejestrze |
~850 |
Proxy Nginx
Plik: infra/nginx/citadel-crm.conf — zastępuje starszy config citadel-os.
Trzy upstream:
| Upstream | Port | Usługa |
|---|---|---|
crm_backend |
19210 | Bun Master Bot (CRM API, zdrowie) |
phaser_frontend |
18889 | Docker Phaser office (legacy) |
mcp_bridge |
19200 | Docker MCP state-bridge (legacy) |
| Lokalizacja | Backend | Auth | Specjalna konfiguracja |
|---|---|---|---|
/ |
phaser_frontend |
basic auth | Docker Phaser frontend |
/api/crm/ |
crm_backend |
token (bez basic) | timeout 30s, keepalive |
/api/master/health |
crm_backend |
brak | Publiczna kontrola zdrowia |
/api/sse/ |
crm_backend |
token (bez basic) | proxy_buffering off, timeout 1h (Phase 22.1) |
/ws/ |
crm_backend |
token (bez basic) | Upgrade WebSocket, timeout 1h (Phase 22.1) |
/api/ |
mcp_bridge |
brak (bez basic) | Starsze state API + SSE |
/health |
mcp_bridge |
brak | Starsze zdrowie bridge |
Wszystkie lokalizacje przekazują X-Real-IP, X-Forwarded-For, X-Forwarded-Proto.
Zablokowane ścieżki: /.* (dotfiles), /config/, /state/, /scripts/.
Uwaga: Priorytet lokalizacji nginx zapewnia, że /api/crm/ i /api/sse/ są dopasowane przed catch-all /api/ (legacy MCP).
Integracja w Master Bocie
master-bot/bot.ts Bun.serve (:19210):
async fetch(req) {
/api/master/health → publiczne, bez auth
/api/crm/* → preflight CORS → crmAuthMiddleware → routeCrmRequest()
* → 404
}
Router dispatchuje do funkcji handlerów z shared/crm-routes.ts. Nagłówki CORS są wstrzykiwane do każdej odpowiedzi CRM (włącznie z błędami 401).
Podsumowanie endpointów (wszystkie Phase)
| Phase | Funkcja | Endpoint | Status |
|---|---|---|---|
| 22.0 | Lista projektów | GET /api/crm/projects |
DONE |
| 22.0 | Szczegóły projektu | GET /api/crm/projects/:name |
DONE |
| 22.0 | Ogon logów | GET /api/crm/projects/:name/logs |
DONE |
| 22.0 | Listing plików | GET /api/crm/projects/:name/files |
DONE |
| 22.0 | Listing skilli | GET /api/crm/projects/:name/skills |
DONE |
| 22.1 | Streaming logów SSE | GET /api/sse/logs/:name |
DONE |
| 22.1 | Historia metryk | GET /api/crm/projects/:name/metrics |
DONE |
| 22.3 | Restart projektu | POST /api/crm/projects/:name/restart |
DONE |
| 22.3 | Terminal WebSocket | WS /ws/terminal/:name |
DONE |
| 24.5 | CRUD specyfikacji | GET/POST /api/crm/projects/:name/specs |
DONE |
| 24.5 | Aktywna rola | GET/POST /api/crm/projects/:name/active-role |
DONE |
| 25 | Pakiet skilli | GET /api/crm/projects/:name/skills-bundle |
DONE |
| 25 | Synchronizacja learnings | GET/POST /api/crm/projects/:name/learnings |
DONE |
| 26 | CRUD workerów | GET/POST /api/crm/projects/:name/workers |
DONE |
| 32 | Zapis wiki | PUT /api/crm/projects/:name/wiki/save |
DONE |
| 32 | Zapis/usunięcie skilli | PUT/DELETE /api/crm/projects/:name/skills/* |
DONE |
| 33 | Ustawienia konta | GET/PUT /api/crm/account/settings |
DONE |
| 33 | Tworzenie projektu | POST /api/crm/projects/create |
DONE |
| 34 | CRUD zgłoszeń | POST/GET/PUT /api/mcp/issues/:project |
DONE |
| 34 | Wiki MCP | PUT /api/mcp/wiki/:project |
DONE |
| 34 | Synchronizacja roadmapy | GET/PUT /api/mcp/roadmap/:project |
DONE |
| 34 | Inicjalizacja CLI | GET /api/cli/init/:project/:mode |
DONE |
| 35 | Ingest logów terminala | POST /api/crm/projects/:name/terminal/log |
DONE |
| 36 | Chat Cloud PM | POST /api/crm/projects/:name/chat |
DONE |
| 36.6 | Generowanie skilli | POST /api/crm/projects/:name/skills/generate |
DONE |
| 36.7 | Listing notatników | GET /api/crm/projects/:name/notebooks |
DONE |
| 40.14 | Roadmapa (treść) | GET /api/mcp/roadmap/:project (+ pole content) | DONE |
Łącznie: 46+ endpointów (REST + SSE + WebSocket + CLI/MCP)
NotebookLM Bridge (Phase 36.3)
Osobna usługa Python FastAPI na :19213 (tylko localhost). Nie jest częścią Bun CRM API.
| Endpoint | Metoda | Cel |
|---|---|---|
/query |
POST | Wyszukiwanie semantyczne przez Google NotebookLM |
/sync |
POST | Kolejkuj źródło do asynchronicznej synchronizacji (fire-and-forget) |
/notebooks/init |
POST | Utwórz notatnik dla nowego projektu |
/health |
GET | Status auth, liczba notatników, statystyki kolejki |
Bun CRM wywołuje fire-and-forget do bridge przy:
handleUpdateIssue(zamknięcie/otwarcie zgłoszenia) →/synchandleMcpWikiUpdate(zapis wiki) →/synchandleCreateProject/handleOnboardingSetup→/notebooks/initexecuteAskNotebooklm→/query(timeout 15s, fallback do lokalnego)
Dług techniczny
Pełny rejestr: docs/backlog/technical-debt.md. Kluczowe pozycje:
| ID | Tytuł | Status |
|---|---|---|
| DEBT-2 | Atomiczne zapisy JSON | ROZWIĄZANY (Phase 22.3) |
| DEBT-3 | Zbakowany token Docker → auth sesji | ROZWIĄZANY (Phase 31) |
| DEBT-6 | Martwa strefa CRM master bota | ROZWIĄZANA (Phase 22.3) |
| DEBT-7 | Ochrona przed path traversal | ROZWIĄZANA (Phase 22.3) |
Prowadzone przez Ricka (Orkiestratora). Aktualizowane po ukończeniu każdej phase.