ტექნიკური სტეკი
FastAPI + Postgres 16 + ChromaDB + R2, четыре приложения за одним Caddy, изолированный workers-контейнер, Plan-100 control plane.
TL;DR
Backend — единственный FastAPI-апп. Workers — отдельный singleton-контейнер с восемью loop'ами. Frontend — три приложения (landing на Astro, dash на React 19 + TanStack Router, admin на React 19) за одним Caddy с TLS, CSP и WS-таймаутами 1h для длинных пайплайнов. Postgres 16 + ChromaDB + Cloudflare R2 — основное хранилище. 61 миграция, Plan-100 control plane с координатором/раннером/реконсилером.
Стек и архитектура
Топология
Caddy (TLS + per-host CSP + WS timeouts 1h, flush_interval -1)
↓
├── landing.goragen.com → Astro static
├── app.goragen.com → React 19 SPA (dash)
├── admin.goragen.com → React 19 SPA (admin)
└── api.goragen.com → FastAPI + WS endpoints
↓
↓ workers (replicas:1, stop_grace 120s)
↓ ├── coordinator_loop
↓ ├── runner_loop
↓ ├── reconciler_loop
↓ ├── auto_provisioner_loop
↓ ├── daily_cleanup_loop
↓ ├── distribution_worker
↓ ├── voice/lip_sync workers
↓ └── heartbeat_loop
↓
├── Postgres 16 (psycopg v3)
├── ChromaDB (vectors)
└── External:
Vast.ai (GPU rental)
Kling Direct API
Anthropic Claude
Google Gemini
Cloudflare R2
Sentry
API (FastAPI)
api/main.py — entry-point. Стартап-последовательность:
- Запуск SQL-миграций (61 файл, идемпотентные, ordered,
pg_advisory_lock(42)для multi-replica safety). - Миграция legacy
vast_instance.jsonsingleton-конфига вvast_instancesтаблицу. - Инициализация auth-таблиц, seed админа из ENV, seed
__system__sentinel-юзера (для FK наusers(id)в системных записях). reconcile_interrupted_on_startup()— пере-queue job'ы оставшиеся вrunningпосле некорректного shutdown'а.- Restore in-flight WS-сессий из БД, помечаем зависшие как
interrupted. - Генерация voice-семплов на первом boot'е (если не было).
- Запуск
scheduler_loop(остальные loop'ы — в workers-контейнере).
При shutdown: cancel app.state.bg_tasks, broadcast WS close-code 1012 («Service Restart») активным сессиям. Dash-клиент видит 1012 и сразу пытается reconnect — без timeout-wait.
Workers контейнер
Отдельный сидекар, replicas: 1 (singleton). Это критично — некоторые loop'ы держат состояние в памяти (lease tracking, in-progress provisions), которое нельзя разделять без сложной координации.
Хост:
- coordinator_loop — admission control. Читает
instance_requests, applies parallel-limits + velocity-guard, INSERTsprovision_attempts. Leader-elected черезprovision_leader. - runner_loop — execution. Pulls claimed attempts, вызывает
scripts/vast_provision.ensure_instance(...), applies terminal reducer (success → ready, failed → terminated, etc). - reconciler_loop — recovery. Сканирует stale leases пяти типов источников, force-terminates застрявшие
cancelling, освобождает destroyed instances. - auto_provisioner_loop — минутный tick, rule-based spawn/destroy. Сейчас эмитит через control plane на
source_kind='auto_pool'. - daily_cleanup_loop — 24h retention sweep:
job_queue, decisions, lora_workspaces, idempotency keys, domain events >180d, disk usage check. - distribution_worker — 30s tick, scheduled posts → social platforms.
- voice_clone_worker / lip_sync_worker — voice job mocks (для будущего полноценного inference).
- heartbeat_loop — каждые 30 секунд пишет
system_settings.workers_last_heartbeat.
SIGTERM → 120s graceful. Координатор, раннер, реконсилер observe heartbeat_task.done() чтобы leadership loss не молчал.
База данных
PostgreSQL 16, доступ через api/db.py:get_connection — psycopg v3 wrapper с SQLite-style API. Это нужно потому что legacy runtime SQL писалась под SQLite (row[0], row["col"], ? placeholders); api/db_dialect.translate_runtime_sql переписывает placeholders + datetime('now') на каждом execute.
Это compromise: вместо переписывания 100k+ строк SQL — runtime-транслятор. Phase 11→12 cutover (2026-05-12) удалил dual-driver.
Миграции
61 SQL-файл в api/migrations/:
- 001–027 — Phase 0–7 foundation: auth, workspaces, plans, personas, storyboards, gallery, distribution, voice, API keys, webhooks.
- 028–029 — Phase 8.1: job_queue, instance LoRA cache.
- 030–032 — Phase 8.2: vast_instances, provisioning_rules, provisioner_decisions.
- 033 — Phase 8.3: lora_training_jobs.
- 034–037 — Phase 9: domain_events, idempotency_keys, user_daily_spend, cost-protection knobs.
- 040–044 — Phase 11→12: Postgres-cutover artefacts.
- 047–054 — Phase 14 slim_provisioning: model_registry_v2, node_packages, presets, bootstrap meta, Telegram bot storage.
- 055–061 — Plan-100 Stage 1–6: shadow tables, atomic reuse claim, cutover flags, observability thresholds.
Каждая миграция — идемпотентная, _migrations tracker-таблица, pg_advisory_lock(42) сериализует multi-replica startups.
ChromaDB
knowledge/db/chroma/ — векторный индекс для рецептов промптов и Civitai-данных. 2 458 ComfyUI node-схем закэшированы в node_info_cache.py. 40 reference workflows (WAN 2.1, HunyuanVideo, LTX 2.3) лежат в knowledge/db/workflow_examples/.
knowledge/workflow_builder.py — программная сборка ComfyUI-графов; patch_workflow_with_lora инжектит LoRA-loaders в base-workflow.
Аутентификация
Два режима:
- Cookie JWT — primary, browser flow.
access_token+refresh_tokenна.goragen.com. Argon2 для паролей. - Bearer
gk_live_*— SDK / CI. Argon2-хэшированные ключи вapi_keys.key_hash. Falls throughget_current_userпосле cookie-пути.
require_admin, require_plan(...) — depends-декораторы для авторизации.
Middleware
- rate_limit.py — slowapi keyed по JWT user → API key prefix → IP.
- idempotency.py — Phase 9 C5
@idempotent(ttl_hours=24). ЧитаетIdempotency-Key, lookupidempotency_keysДО выполнения handler'а. Hit → replay cached JSONResponse; hash mismatch (тот же ключ, разное тело) → 409; missing header → 400. Применено к 5 mutating endpoints. Dash-клиент авто-инжектитcrypto.randomUUID()для этих путей и переиспользует key на 401/429/5xx retries. - maintenance_middleware — глобальный 503 при
system_settings.maintenance_mode == 'true'(admin/auth/health passthrough). - CORS — pinned к
["GET","POST","PATCH","DELETE","OPTIONS"]. - _record_unhandled — фидит unhandled exceptions в
error_bufferring-buffer для admin dashboard.
Frontend
apps/dash — пользовательский кабинет
React 19 + TypeScript + Vite 6 + Tailwind 4. Routing — TanStack Router (file-based, flat).
State split:
- TanStack Query — серверный кэш, refetch, mutations.
- Zustand — cross-component session signals (live cost ticker).
lib/_http.ts— HTTP client, обрабатывает 401/refresh, 429 retry, 5xx exponential backoff, offline detection.
WebSocket reconnect — backoff [1s, 2s, 5s, 10s, 30s] в src/lib/ws.ts.
Routes (high-level): /, /library, /library/$slug, /generate, /storyboard, /gallery, /voice, /lora, /distribution, /compliance, /settings/* (7 sub-routes).
Onboarding-tour лениво подгружается через Driver.js.
apps/admin — админ SPA
React 19. Sidebar shell AdminShell.tsx с keyboard shortcuts.
Routes: /dashboard, /users, /personas, /presets, /workflows, /instances, /tasks (4 sub-tabs), /lora, /system (6 sub-tabs включая control-plane), /compliance, /settings.
apps/landing — маркетинг
Astro static. Без SPA-runtime. Talks только к /auth/register, /auth/login, /auth/forgot-password.
Caddy
Single TLS-terminator. Per-host CSP. Reverse-proxy на четыре сервиса. Критическая настройка для длинных пайплайнов:
read_timeout 1h
write_timeout 1h
flush_interval -1
LoRA-тренировка занимает 45-90 минут, storyboard — 10-15 минут. Без этих таймаутов WS-сессия дропалась бы каждые 30-60 секунд.
External
- Vast.ai — rented GPU pool. Provisioning через
scripts/vast_provision.py. SSH ControlMaster + retry helpers вssh_utils.py. - Kling Direct API — endpoints для T2V, I2V, omni, extend, lip-sync, effects. JWT (HS256) в
KlingClient. - Anthropic Claude — orchestration (model selection, prompt engineering, captions).
- Google Gemini 2.5 Pro — output evaluation (4 criteria, verdict).
- Cloudflare R2 — canonical storage (
STORAGE_BACKEND=s3, bucketgoragen-prod, CDN atcdn.goragen.com). - Sentry — error tracking (no-op when
SENTRY_DSNempty).
Что бы я переписал
— Workers единичная реплика. Сейчас singleton по необходимости (in-memory state). Можно разбить на per-loop контейнеры с разделением ответственности — каждый loop отдельный процесс с persistent state в Postgres. Но это значительная работа и сейчас не блокер.
— Workflow templates как код. Сейчас reference workflows — JSON-файлы. Можно сделать DSL для построения графов из примитивов (camera move + model + lora + sampler), который компилируется в JSON.
— Self-hosted Kling alternative. Kling — закрытый API. Когда open-source alternative подтянется (WAN 3.x / Hunyuan v2), можно переключить через model_selector без изменений сверху.
Платформа в проде на goragen.com. 61 миграция — это шесть месяцев активной разработки с регулярным деплоем без даунтайма (Phase 11→12 cutover был критичный, прошёл chunked).