CRM Backend Architecture
Phase 22.0–48. Статус: DEPLOYED (VPS live, 68+ эндпоинтов, модульная архитектура). Последнее обновление: 2026-04-28 (Phase 48 — Architecture Decomposition)
Обзор
Browser (:18888)
│
├── Static files ──► Nginx ──► Docker frontend (React + Nginx)
│
├── /api/crm/* ────► Nginx ──► Bun Master Bot (:19210)
│ ├── crmAuthMiddleware (HMAC-SHA256)
│ └── shared/routes/router.ts → 17 доменных модулей
│
├── /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) → master-bot/routes/internal.ts
│
├── /api/sse/* ────► Nginx (unbuffered) ──► Bun (:19210)
│
└── /ws/terminal/* ► Nginx (upgrade) ──► Bun (:19210) → master-bot/routes/websocket.ts
Структура модулей (Phase 48)
API Server (master-bot/api-server.ts, 196 строк) — тонкий диспетчер:
master-bot/routes/auth.ts(562) — OAuth, login, register, 2FA, device codemaster-bot/routes/internal.ts(282) — chat/save, bridge-event, relay, бинарные загрузкиmaster-bot/routes/cli.ts(293) — ARC CLI + MCP-интеграцияmaster-bot/routes/websocket.ts(303) — терминал, event-stream, local-bridge
CRM Routes (shared/routes/router.ts, 512 строк) — таблица диспетчеризации для 17 доменных модулей:
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) — общие типы, path safety, auth-хелперы
Поток аутентификации
Пользователь ──► LoginOverlay (фронтенд)
│
├── Email/password → POST /api/auth/login
└── OAuth → /api/auth/{google,github} → callback
│
▼
Успешная аутентификация → HMAC-SHA256 токен
│
├── payload = { sub: userId, iat, exp: +24h }
├── signature = HMAC-SHA256(base64url(payload), CRM_SECRET)
└── token = base64url(payload) + "." + signature
│
▼
localStorage('crm-token') → Все API-вызовы: Authorization: Bearer <token>
│
▼
Токен истёк → 401 → требуется вход
Ключевые файлы
| Файл | Роль |
|---|---|
shared/auth.ts |
Генерация/верификация токена, email/OAuth, сброс пароля, верификация email, CORS, middleware |
shared/vault.ts |
Хранилище секретов AES-256-GCM (CRM_SECRET создаётся автоматически) |
frontend/src/crm/components/LoginOverlay.jsx |
UI входа (email, OAuth, сброс пароля, верификация email) |
Свойства безопасности
- CRM_SECRET: 32-байтный случайный hex, зашифрован через vault (AES-256-GCM)
- TTL токена: 24 часа, не обновляется
- Подпись: HMAC-SHA256, кодирование base64url (без padding)
- CORS: origin echo, ограничен заголовками Authorization + Content-Type
- Верификация email обязательна перед входом для новых аккаунтов
- nginx basic auth остаётся как внешний слой защиты в глубину
CRM API Routes
Все маршруты под /api/crm/ требуют Authorization: Bearer <token>.
GET /api/crm/projects
Список всех зарегистрированных проектов с live статусом health. Включает master как псевдопроект (DEBT-6).
| Поле | Источник |
|---|---|
name, type, bot_username |
config/bot_registry.json (master: виртуальная запись, type: "system") |
healthy |
HTTP health check (дочерние: child port, master: /api/master/health) |
tmux_alive |
tmux has-session -t <name> (master: citadel-master) |
status |
Файл heartbeat или производное из health+tmux (master: healthy/degraded/down) |
Master всегда добавляется первым элементом в массиве ответа.
GET /api/crm/projects/:name
Полная карточка деталей проекта.
| Поле | Источник |
|---|---|
manifest |
Текст из <cwd>/MANIFEST.md |
heartbeat |
JSON из state/heartbeat_<name>.json |
skills[] |
Имена файлов <cwd>/skills/*.md (исключая _*.md) |
healthy, tmux_alive |
Как в list-эндпоинте |
GET /api/crm/projects/:name/logs
Tail JSONL-структурированных логов.
| Параметр | По умолчанию | Максимум |
|---|---|---|
lines |
100 | 500 |
category |
dialog |
system, dialog, error |
Источник: /var/log/citadel/<name>/<category>-YYYY-MM-DD.log
Читает самые свежие файлы логов первыми, парсит JSONL, возвращает в хронологическом порядке.
GET /api/crm/projects/:name/files
Листинг директории с защитой от path traversal.
| Параметр | По умолчанию | Описание |
|---|---|---|
path |
/ |
Относительный путь внутри cwd проекта |
Безопасность: resolve(base, requested) должен startsWith(resolve(base)). Нарушение → 403.
Элементы ответа: { name, type: "file"|"directory", size?, modified }, отсортированы: директории первыми.
GET /api/crm/projects/:name/skills
Установленные скиллы с атрибуцией источника.
| Источник | Определение |
|---|---|
file |
<cwd>/skills/*.md (исключая _*.md) |
manifest |
MANIFEST.md → skills[] + library_skills[] (JSON parse) |
Дедуплицировано: источник file имеет приоритет над manifest.
POST /api/crm/projects/:name/restart (Phase 22.3)
Перезапуск дочернего бота через watchdog.
| Аспект | Деталь |
|---|---|
| Метод | Только POST (GET возвращает 405) |
| Auth | HMAC-SHA256 токен (аналогично другим CRM-маршрутам) |
| Кулдаун | 30 секунд на проект (возвращает 429 при слишком частых запросах) |
| Механизм | Вызывает restartChild() из watchdog.ts |
| Ответ | { ok: true } или { error: "..." } |
GET /api/crm/projects/:name/metrics (Phase 22.1)
Временной ряд метрик качества для sparkline-графиков.
| Параметр | По умолчанию | Описание |
|---|---|---|
days |
7 | Количество дней для агрегации |
Источник: /var/log/citadel/<name>/quality-events.log → подсчёт успехов/ошибок по дням.
WS /ws/terminal/:name (Phase 22.3)
WebSocket-терминал, транслирующий содержимое tmux-панели.
| Аспект | Деталь |
|---|---|
| Auth | Query-параметр ?token= (проверяется через verifyToken().valid) |
| Режим | По умолчанию read-only; ?mode=interactive включает send-keys |
| Опрос | tmux capture-pane -p -e -t {session} -S -80 каждые 200мс |
| Дельта | Отправляет только при изменении содержимого относительно предыдущего |
Безопасность: защита от Path Traversal (Phase 22.3, DEBT-7)
Все эндпоинты валидируют имена проектов перед обработкой:
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);
}
Применяется в 3 точках входа: routeCrmRequest(), routeSseRequest(), WebSocket upgrade handler в bot.ts.
Невалидные имена → 403 Forbidden.
Целостность данных: атомарные записи (Phase 22.3, DEBT-2)
Записи реестра в onboarding.ts используют атомарный паттерн:
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
}
Ключевые файлы
| Файл | Роль | Строки |
|---|---|---|
shared/crm-routes.ts |
55+ обработчиков + роутер routeCrmRequest() + isValidProjectName() |
~4800 |
shared/sage.ts |
Sage Worker: runSageAnalysis(), runBenchmark(), LLM judge, A/B тестирование |
~550 |
shared/db.ts |
SQLite SSOT: userQueries, projectQueries, skillQueries, benchmarkQueries, chatQueries | ~600 |
shared/auth.ts |
Генерация токена, верификация, CORS, middleware | ~100 |
master-bot/bot.ts |
Bun.serve: HTTP роутер + WebSocket терминал-хэндлер | ~1160 |
master-bot/onboarding.ts |
atomicWriteJson() для записей реестра |
~850 |
Nginx Proxy
Файл: infra/nginx/citadel-crm.conf — заменяет legacy-конфиг citadel-os.
Три upstream-а:
| Upstream | Порт | Сервис |
|---|---|---|
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 | Бэкенд | Auth | Особая конфигурация |
|---|---|---|---|
/ |
phaser_frontend |
basic auth | Docker Phaser frontend |
/api/crm/ |
crm_backend |
токен (без basic) | таймаут 30с, keepalive |
/api/master/health |
crm_backend |
нет | Публичный health check |
/api/sse/ |
crm_backend |
токен (без basic) | proxy_buffering off, таймаут 1ч (Phase 22.1) |
/ws/ |
crm_backend |
токен (без basic) | WebSocket upgrade, таймаут 1ч (Phase 22.1) |
/api/ |
mcp_bridge |
нет (без basic) | Legacy state API + SSE |
/health |
mcp_bridge |
нет | Legacy bridge health |
Все location'ы передают X-Real-IP, X-Forwarded-For, X-Forwarded-Proto.
Заблокированные пути: /.* (dotfiles), /config/, /state/, /scripts/.
Примечание: Приоритет location в Nginx гарантирует, что /api/crm/ и /api/sse/ матчатся раньше catch-all /api/ (legacy MCP).
Интеграция в Master Bot
master-bot/bot.ts Bun.serve (:19210):
async fetch(req) {
/api/master/health → публичный, без auth
/api/crm/* → CORS preflight → crmAuthMiddleware → routeCrmRequest()
* → 404
}
Роутер диспетчеризует к функциям-обработчикам из shared/crm-routes.ts. CORS-заголовки вставляются в каждый CRM-ответ (включая 401-ошибки).
Сводка эндпоинтов (все фазы)
| Фаза | Фича | Эндпоинт | Статус |
|---|---|---|---|
| 22.0 | Список проектов | GET /api/crm/projects |
DONE |
| 22.0 | Детали проекта | GET /api/crm/projects/:name |
DONE |
| 22.0 | Хвост логов | GET /api/crm/projects/:name/logs |
DONE |
| 22.0 | Листинг файлов | GET /api/crm/projects/:name/files |
DONE |
| 22.0 | Листинг скиллов | GET /api/crm/projects/:name/skills |
DONE |
| 22.1 | SSE log streaming | GET /api/sse/logs/:name |
DONE |
| 22.1 | История метрик | GET /api/crm/projects/:name/metrics |
DONE |
| 22.3 | Перезапуск проекта | POST /api/crm/projects/:name/restart |
DONE |
| 22.3 | WebSocket терминал | WS /ws/terminal/:name |
DONE |
| 24.5 | Specs CRUD | GET/POST /api/crm/projects/:name/specs |
DONE |
| 24.5 | Активная роль | GET/POST /api/crm/projects/:name/active-role |
DONE |
| 25 | Skills bundle | GET /api/crm/projects/:name/skills-bundle |
DONE |
| 25 | Синхронизация learnings | GET/POST /api/crm/projects/:name/learnings |
DONE |
| 26 | Workers CRUD | GET/POST /api/crm/projects/:name/workers |
DONE |
| 32 | Сохранение вики | PUT /api/crm/projects/:name/wiki/save |
DONE |
| 32 | Сохранение/удаление скиллов | PUT/DELETE /api/crm/projects/:name/skills/* |
DONE |
| 33 | Настройки аккаунта | GET/PUT /api/crm/account/settings |
DONE |
| 33 | Создание проекта | 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 | 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 | Генерация скиллов | POST /api/crm/projects/:name/skills/generate |
DONE |
| 36.7 | Список ноутбуков | GET /api/crm/projects/:name/notebooks |
DONE |
| 40.14 | Roadmap (контент) | GET /api/mcp/roadmap/:project (+ поле content) | DONE |
Итого: 46+ эндпоинтов (REST + SSE + WebSocket + CLI/MCP)
NotebookLM Bridge (Phase 36.3)
Отдельный Python FastAPI-сервис на :19213 (только localhost). Не является частью Bun CRM API.
| Эндпоинт | Метод | Назначение |
|---|---|---|
/query |
POST | Семантический поиск через Google NotebookLM |
/sync |
POST | Поставить источник в очередь асинхронной синхронизации (fire-and-forget) |
/notebooks/init |
POST | Создать ноутбук для нового проекта |
/health |
GET | Статус auth, количество ноутбуков, статистика очереди |
Bun CRM инициирует fire-and-forget вызовы к bridge при:
handleUpdateIssue(закрытие/открытие задачи) →/synchandleMcpWikiUpdate(сохранение вики) →/synchandleCreateProject/handleOnboardingSetup→/notebooks/initexecuteAskNotebooklm→/query(таймаут 15с, fallback на локальный)
Технический долг
Полный реестр в docs/backlog/technical-debt.md. Ключевые позиции:
| ID | Заголовок | Статус |
|---|---|---|
| DEBT-2 | Атомарные JSON-записи | RESOLVED (Phase 22.3) |
| DEBT-3 | Baked Docker token → session auth | RESOLVED (Phase 31) |
| DEBT-6 | CRM слепое пятно Master Bot | RESOLVED (Phase 22.3) |
| DEBT-7 | Защита от path traversal | RESOLVED (Phase 22.3) |
Поддерживается Rick (Orchestrator). Обновляется после завершения каждой фазы.