gora.
GoraGen — платформа AI-генерации видео

Tech stack

FastAPI + Postgres 16 + ChromaDB + R2, четыре приложения за одним Caddy, изолированный workers-контейнер, Plan-100 control plane.

~6 min read · 1115 words

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. Стартап-последовательность:

  1. Запуск SQL-миграций (61 файл, идемпотентные, ordered, pg_advisory_lock(42) для multi-replica safety).
  2. Миграция legacy vast_instance.json singleton-конфига в vast_instances таблицу.
  3. Инициализация auth-таблиц, seed админа из ENV, seed __system__ sentinel-юзера (для FK на users(id) в системных записях).
  4. reconcile_interrupted_on_startup() — пере-queue job'ы оставшиеся в running после некорректного shutdown'а.
  5. Restore in-flight WS-сессий из БД, помечаем зависшие как interrupted.
  6. Генерация voice-семплов на первом boot'е (если не было).
  7. Запуск 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, INSERTs provision_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.

Аутентификация

Два режима:

  1. Cookie JWT — primary, browser flow. access_token + refresh_token на .goragen.com. Argon2 для паролей.
  2. Bearer gk_live_* — SDK / CI. Argon2-хэшированные ключи в api_keys.key_hash. Falls through get_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, lookup idempotency_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_buffer ring-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, bucket goragen-prod, CDN at cdn.goragen.com).
  • Sentry — error tracking (no-op when SENTRY_DSN empty).

Что бы я переписал

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).

GoraGen — стек и архитектура · hiregora.com