Architektura
Architektura platformy
Section titled “Architektura platformy”Sencai je EU-suverénní platforma pro správu cloudu — event-driven, multi-tenant SaaS, který dává týmům jednotný pohled na jejich cloudovou i on-prem infrastrukturu. Architektura je mikroservisová, orchestrovaná Docker Compose a postavená na asynchronní komunikaci přes RabbitMQ.
„DevOps kolega s rukama na cloudu i on-prem” — automatizovat nudné věci, vyzdvihnout ty důležité a vynucovat politiku automaticky.
Mapa služeb
Section titled “Mapa služeb”| Služba | Účel | Port | Jazyk |
|---|---|---|---|
| sencai.space | Strapi v5 backend — REST API, content-types, lifecycle hooks | 1337 | Node.js / TS |
| sencai-backend | Go strangler-fig náhrada backendu — shadow-only, žádný živý provoz (viz níže) | 8080 (interně) | Go |
| sencai.space-frontend | Platform UI — auth, dashboard, gravity UI | 3000 | Nuxt 3 / Vue 3 |
| sencai-admin | Interní admin dashboard (KC-auth) | 3100 | Nuxt 3 |
| sencai-audit | Viewer auditních záznamů (read-only SPA) | 3300 | Nuxt 3 |
| sencai-web | Marketingový web (Astro 5 SSG, 15 locales) | — | Astro |
| api-manager | Správa šifrovaných service-to-service tokenů | 3200 | Fastify / TS |
| auth-service-consumer | RabbitMQ → Keycloak sync + audit události | — | Node.js / TS |
| webhook-publisher | Bridge Strapi webhooks → RabbitMQ | 1347 | Node.js (JS) |
| git-connector | RabbitMQ → Gitea sync (Toolbox + Apps) | 3002 | Node.js / TS |
| cloud-connector | Worker pro multi-cloud provisioning (AWS/GCP/Azure/Hetzner/DO) | 4321 | Node.js / TS |
| opsloop-consumer | Intelligence loop — korelace incidentů, návrhy runbooků | 3005 | Node.js / TS |
| ssh-proxy-service | Terminál v prohlížeči přes WebSocket (SSH proxy) | 3400 | Node.js / TS |
| graphql-gateway | Sjednocená GraphQL API brána | 4000 | Node.js / TS |
| billing-adapter | Lago usage-based billing + platby přes Stripe (ADR-002) | 3600 | Node.js / TS |
| sencai-watchdog | Dunning microservice — cron pro suspend/archive při selhání platby | 3700 | Node.js / TS |
| llm-service | Multi-provider LLM fasáda (primárně Anthropic, OpenAI-kompatibilní fallback) | 4430 | Node.js / TS |
| workspace-connector | Sync Google Workspace Directory API | 3500 | Node.js / TS |
| notification-service | Konzument pro email + Slack (notification.events, billing.events) | — | Node.js / TS |
| event-store-consumer | Konzument fanoutu platform.events → ingest do Strapi | — | Node.js / TS |
| backup-scheduler | Plánovaný cron pro zálohu MySQL → S3/GCS + restore drill | — | Node.js / TS |
| agent-gateway | Fleet C2 brána — enrollment + WSS heartbeat/command kanál | 4400 | Go |
| sencai-agent | On-host Fleet agent binárka (monitoring, patch, inventory, runbooky) | — (klient) | Go |
| k8s-agent | Kubernetes DaemonSet Fleet agent | — | Go |
| gamification-consumer | Jediný writer content-typů user-rank/gamification-event (F4.GAM) — počítá změny XP/hodnosti/odznaků na straně serveru | 3800 | Node.js / TS |
| forum-connector | JIT provisioning most mezi Keycloakem a sencai-forum (Lemmy) | 4181 | Fastify / TS |
| sencai-forum | Komunitní fórum (Lemmy 0.19 + oauth2-proxy pro KC SSO) | 8536 | — (upstream) |
| auth-service | Konfigurace Keycloak + vlastní témata | 8080 | — (upstream) |
| sencai-mq | RabbitMQ broker | 5673 | — (upstream) |
Vysokoúrovňová architektura
Section titled “Vysokoúrovňová architektura”┌──────────────────────────────────────────────────────────────────┐│ Uživatelská a marketingová vrstva │├──────────────────────────────────────────────────────────────────┤│ sencai.space-frontend (Nuxt 3) │ sencai-web (Astro SSG) ││ sencai-admin (Nuxt 3) │ sencai-audit (Nuxt 3 SPA) │└───────────────────────┬──────────────────────────────────────────┘ │ HTTPS┌───────────────────────▼──────────────────────────────────────────┐│ Traefik (Edge Proxy) ││ Routy: /api/* → Strapi | /api/token → API Manager ││ /kc/* → Keycloak | /api/auth/* → Frontend server route │└──────┬───────────────┬───────────────────┬───────────────────────┘ │ │ │ ┌────▼────┐ ┌─────▼──────┐ ┌──────▼──────┐ │ Strapi │ │ API Manager│ │ Keycloak │ │ (1337) │ │ (3200) │ │ (8080) │ │ MySQL │ │ AES-256-GCM│ │ MySQL │ └────┬────┘ └────────────┘ └─────────────┘ │ │ webhooky┌──────▼──────────────────────────────────────────────────────────┐│ Webhook Publisher (1347) ││ Strapi eventy → RabbitMQ routing │└──────┬──────────────────────────────────────────────────────────┘ │ AMQP┌──────▼──────────────────────────────────────────────────────────┐│ RabbitMQ (5673 AMQP / 15673 UI) ││ Exchange: user | organisation | git.events ││ cloud.events | audit.events | notification.events ││ billing.events | platform.events | alertmanager.events │└──┬───────────┬──────────────┬────────────────┬───────────────────┘ │ │ │ │ ▼ ▼ ▼ ▼Auth Git Cloud OpsLoop /Consumer Connector Connector Notification /(KC sync) (Gitea) (AWS/GCP/...) Event-Store konzumenti │ ┌──────▼──────┐ │ Provider SDK │ │ AWS/GCP/AZ │ │ Hetzner/DO │ └─────────────┘
Fleet rovina (oddělená od RabbitMQ sběrnice):
sencai-agent (Go, na hostu) ──WSS+mTLS──▶ agent-gateway (Go, :4400) ──REST──▶ StrapiEvent-driven architektura
Section titled “Event-driven architektura”Většina cross-service komunikace prochází přes RabbitMQ. Business události se pokud možno vyhýbají přímým synchronním HTTP voláním mezi službami — díky tomu jsou služby decoupled a systém odolný vůči dočasným výpadkům. (Fleet enrollment/heartbeat je záměrná výjimka — viz Enrollment Fleet agenta — běží přes vyhrazený WSS+mTLS kanál mezi sencai-agent a agent-gateway, ne přes RabbitMQ.)
RabbitMQ Exchange
Section titled “RabbitMQ Exchange”Routing keys na topic exchangech mají formát <entita>.<akce>. user a organisation jsou direct exchange; platform.events je fanout.
| Exchange | Typ | Příklady routing keys | Konzumenti |
|---|---|---|---|
user | direct | create, update | auth-service-consumer |
organisation | direct | create, member.added, member.removed | git-connector, opsloop-consumer |
git.events | topic | git.repo.created, git.key.rotated | git-connector |
cloud.events | topic | cloud-instance.provision, cloud-instance.stop, cloud-instance.terminate | cloud-connector worker |
audit.events | topic | audit.write | auth-service-consumer / audit-consumer (ukládá hash-chained záznamy) |
notification.events | topic | billing.dunning.grace_period, feedback.submitted, dpa.requested, … | notification-service (email + Slack) |
billing.events | topic | billing.subscription_activated, billing.payment_succeeded, billing.payment_failed, … | notification-service |
platform.events | fanout | (všechny platformní eventy, bez routing key) | event-store-consumer → POST /api/platform-events/ingest |
alertmanager.events | topic | alerty z monitoringu | opsloop-consumer |
gamification.events | topic | xp.granted, badge.check, streak.tick, easter-egg.roll | gamification-consumer (re-publikuje rank.up/badge.awarded/easter-egg.triggered pro frontendový notifikační most) |
Obálka zprávy (příklad cloud.events)
Section titled “Obálka zprávy (příklad cloud.events)”{ "event": "cloud-instance.provision", "data": { "jobId": "uuid-v4", "type": "cloud-instance.provision", "cloudInstanceId": "strapi-document-id", "credential_id": "byoc-credential-document-id", "payload": { "provider": "hetzner", "region": "nbg1", "config": { "serverType": "cx22", "image": "ubuntu-24.04" } } }}Zpracování chyb
Section titled “Zpracování chyb”- Fallback fronty: TTL 14 dní pro nedoručitelné zprávy
- Retry politika: max 5 pokusů s exponenciálním backoffem
- Dead-letter exchange zachycují trvale selhávající zprávy pro inspekci (např.
notification.events.dlq,platform.events.dlq)
Klíčové datové toky
Section titled “Klíčové datové toky”Registrace uživatele
Section titled “Registrace uživatele”POST /api/registrations (Strapi) → Strapi afterCreate lifecycle → webhook-publisher → RabbitMQ: exchange user / create → auth-service-consumer → KC Admin API (vytvoří uživatele, akce VERIFY_EMAIL) → KC odešle verifikační email
Admin nastaví Registration.emailVerified = true v Strapi UI → Strapi lifecycle → webhook-publisher → auth-service-consumer odstraní VERIFY_EMAIL z KC uživatele → Uživatel se může přihlásitPrvní přihlášení (dokončení profilu)
Section titled “První přihlášení (dokončení profilu)”Login → Keycloak token → /callback.vue → GET /api/users?filters[email]=... (Strapi) → pokud !name NEBO !surname → přesměrování /auth/complete-profile → Odeslání formuláře → PUT /api/users/me/profile (Strapi) → Strapi User afterUpdate lifecycle → KC sync (přes webhook-publisher) → přesměrování /gravity/dashboardProvisioning cloudové instance (ADR-001 kanonická cesta)
Section titled “Provisioning cloudové instance (ADR-001 kanonická cesta)”Frontend → POST /api/cloud-instances (Strapi) → afterCreate lifecycle vygeneruje jobId + kanonickou zprávu → webhook-publisher → RabbitMQ cloud.events (cloud-instance.provision) → cloud-connector worker zprávu zpracuje → provider SDK/REST (Hetzner API, AWS EC2, GCP Compute, atd.) → POST /api/cloud-instances/:documentId/status-callback (X-Service-Secret) → Strapi aktualizuje stav instance na "ready" → Frontend polluje/přijímá aktualizaciEnrollment Fleet agenta
Section titled “Enrollment Fleet agenta”Admin serveru vygeneruje org-scoped enrollment token ve Strapi → sencai-agent (binárka v Go) zavolá POST /enroll na agent-gateway (port 4400) tělo: { enrollment_token, hostname, os, arch, version } (bootstrap volání, mTLS zatím není vyžadováno) → agent-gateway ověří token proti Strapi (fail-closed při výpadku Strapi), zkontroluje limit počtu agentů organizace, poté vydá per-agent mTLS leaf certifikát → agent-gateway aktivuje agenta ve Strapi (status=active, fingerprint certifikátu, enrolled_at) → agent uloží podepsaný certifikát + CA bundle, otevře GET /ws přes WSS+mTLS → agent posílá heartbeat každých 30s; agent-gateway škrtí zápisy do Strapi pole last_heartbeat_at (max jeden zápis za ~25s) a posílá zprávy o udělení/odebrání capability dolů přes stejný socketssh-proxy-service s enrollmentem Fleet agenta nesouvisí — slouží pouze k terminálu v prohlížeči (viz níže).
Terminál v prohlížeči (SSH Proxy)
Section titled “Terminál v prohlížeči (SSH Proxy)”Uživatel klikne „Connect" na enrolled serveru v UI → Frontend otevře WebSocket na ssh-proxy-service (wss://) → ssh-proxy-service ověří JWT uživatele → Otevře SSH spojení na cílový server přes Fleet agenta → Obousměrný terminálový stream přes WebSocket → Všechny stisknuté klávesy se logují do audit trailOPSLOOP Intelligence
Section titled “OPSLOOP Intelligence”Dorazí incident event (alert, anomálie, spike chyb) → opsloop-consumer přijme alertmanager.events / cloud.events → llm-service (multi-provider LLM fasáda, primárně Anthropic) → Koreluje s posledními runbooky, výsledky CIS skenu, inventářem → Generuje: souhrn incidentu, kandidáty root cause, navrhovaný runbook → Vytvoří OPSLOOP bundle, pošle notifikaci do frontenduSpráva API tokenů
Section titled “Správa API tokenů”Microservice potřebuje token pro jinou službu → GET /api/token/:service (API Manager, port 3200, X-Service-Secret) → API Manager dešifruje token z /data volume (AES-256-GCM) → Pokud chybí nebo je starší než 7 dní → vyžádá/rotuje přes Strapi admin API → Nový token zašifrován (AES-256-GCM) a uložen → Auto-rotace každých 7 dníGit synchronizace
Section titled “Git synchronizace”Strapi webhook (akce uživatel/organizace) → webhook-publisher → RabbitMQ: git.events → git-connector (port 3002) → Gitea Toolbox API + Gitea Apps API → Potvrzovací callback zpět do StrapiBilling / usage metering (ADR-002)
Section titled “Billing / usage metering (ADR-002)”Platformní eventy (provisioning, usage) → billing-adapter (port 3600) agreguje usage eventy → Lago (usage metering) → Stripe (fakturace/platby) → subscription-tier / vynucení kvót ve Strapi → selhání plateb eskalují přes dunning žebřík sencai-watchdog (notification.events, routing keys billing.dunning.*)Notifikace + Event Store
Section titled “Notifikace + Event Store”*.events (notification.events, billing.events) → notification-service → doručení email + Slack
*.events (fanout přes platform.events) → event-store-consumer → POST /api/platform-events/ingest (Strapi)Topologie databází
Section titled “Topologie databází”| Služba | Engine | Docker Host | Dev Port | Poznámky |
|---|---|---|---|---|
| Strapi (backend) | MySQL 8 | backend-db | 3306 | Knex auto-migrace |
| Keycloak | MySQL 8 | keycloak-db | 3307 | KC-managed schema |
| Gitea (obě instance) | MySQL 8 | git-service-mysql | 3308 | Sdílená DB |
| Cloud Connector | PostgreSQL 15 | cloud-connector-db | 5434 | TypeORM sync |
| Forum (Lemmy) | PostgreSQL 15 | forum-db | 5435 | Lemmy-managed schema |
| sencai-backend (Go) | MySQL 8 | go-backend-db | 3309 | Rozdělena z backend-db 2026-07-06; žádný automatický migrační nástroj, schéma se vyvíjí přes ručně aplikované SQL |
Multi-region HA: Active-passive PostgreSQL replikace s automatickým failoverem. Cíl: RTO < 15 minut, RPO < 5 minut.
Migrace Go backendu (Strangler-Fig)
Section titled “Migrace Go backendu (Strangler-Fig)”sencai-backend je Go přepis Strapi backendu, portovaný content-type po content-type za identickým HTTP kontraktem. Běží proti vlastní databázi (go-backend-db, viz výše), udržované aktuální vůči Strapi’s backend-db přes jednosměrný, event-triggered sync mechanismus (internal/dbsync), ne přes sdílenou databázi. Je shadow-only — nezapojeno do výchozích orchestračních profilů a neroutuje žádný reálný zákaznický provoz; Strapi zůstává jediný živý backend platformy. Plný detail: Migrace Go backendu — vývojářská reference.
Autentizace a identita
Section titled “Autentizace a identita”Keycloak (primární IdP)
Section titled “Keycloak (primární IdP)”- Realm:
sencai - Client IDs:
sencai-backend,sencai-frontend - URL za Traefik: prefix
/kc(KC_HTTP_RELATIVE_PATH=/kc) - Interní URL:
http://keycloak:8080/kc - JWT access tokeny: expirace 12 hodin, s proaktivním refresh-token flow (implementováno, WAVE-5) — frontend obnovuje token ~60s před expirací, s single-flight retry logikou a odhlášením pouze při skutečné 401 z refresh endpointu
- TOTP/2FA: knihovna Speakeasy
Enterprise SSO
Section titled “Enterprise SSO”- EntraId / Azure AD: OIDC federace do Keycloak realmu
- Google Workspace SSO: OAuth2 přes Keycloak social login
- SCIM provisioning: SCIM 2.0 endpoint pro automatizovaný lifecycle uživatelů z IdP
Strapi a KC Sync
Section titled “Strapi a KC Sync”Obousměrná synchronizace, chráněná smyčkovým guardem přes AsyncLocalStorage:
Strapi → KC (lifecycle hooks): plugin::users-permissions.user afterCreate/afterUpdate → webhook-publisher → RabbitMQ → auth-service-consumer → KC Admin API
KC → Strapi (při přihlášení): keycloak-jwt middleware ověří RS256 JWT → pokud zastaralý profil: fetchKcUserById() → runWithoutKcSync(syncKcUserToStrapi)Synchronizovaná pole: email, username (= KC sub UUID), firstName/lastName, emailVerified, blocked/enabled, jobTitle, country.
Bezpečnostní model
Section titled “Bezpečnostní model”Transport
Section titled “Transport”- Externí provoz: TLS přes Traefik (Let’s Encrypt nebo vlastní certifikát)
- Interní Docker síť: Plain TCP (bez TLS — izolovaná bridge síť)
- Fleet agent ↔ agent-gateway: mTLS přes WebSocket (WSS) — úvodní bootstrap volání
/enrollje záměrně neautentizované (řešeno tokenem), ale výsledný řídicí kanál/wsvyžaduje per-agent klientský certifikát vydaný při enrollmentu
Secrets v klidu
Section titled “Secrets v klidu”- API tokeny (API Manager): AES-256-GCM, klíč z
API_MANAGER_ENCRYPTION_KEY - BYOC credentials: AES-256-GCM per organizace, uloženo ve Strapi, nikdy se neloguje
- Vault integrace: HashiCorp Vault (profil
vault) dostupný pro injektování secrets do služeb a CI jobů
Audit trail
Section titled “Audit trail”- Každá mutující akce publikuje do exchangu
audit.events(routing keyaudit.write) - Balíček
@sencai/audit:publishAuditEvent(action, payload)s kontextemAsyncLocalStorage - Imutabilní hash chain: každý záznam obsahuje SHA-256 předchozího záznamu
- Kotvení na vyžádání:
POST /api/audit-logs/anchor(pouze admin,X-Service-Secret) podepíše poslední záznam a uloží ho do content-typeaudit-anchor(anchor_hash,signature,public_key_fingerprint,anchored_entry_id,entries_count) — žádný pevný hodinový rozvrh neexistuje, kotvení se spouští explicitně - Ověření:
GET /api/audit-logs/verify-chainpřepočítá celý hash chain;GET /api/audit-logs/verify?from=&to=&organisation=ověří rozsah v daném filtru - ESLint gate:
eslint-plugin-sencai/require-audit-logvynucuje pokrytí auditu na mutujících funkcích
Autonomie agentů a vynucování politik
Section titled “Autonomie agentů a vynucování politik”Neexistuje samostatná služba „Action Gateway”, žádný formát policy bundlů OPA/Rego/Cedar ani content-type api::capability nebo cloud-approval-request. Skutečný mechanismus:
agent-policycontent-type — inspirovaný OPA, ale nativní Strapi data:autonomy_level(L0 pouze pro čtení → L1 pouze návrh → L2 provedení po schválení → L3 pre-blessed automatizace),freeze_windows,max_blast_radiusa volitelný allowlist typů akcí. Deny-by-default: pokud žádná aktivní politika akci nepovoluje, je zablokována.approval-requestaelevation-requestcontent-types drží skutečné záznamy human-in-the-loop pro akce vyžadující schválení nebo dočasné zvýšení oprávnění (elevaci).- Vynucovací bod:
sencai.space/src/middlewares/autonomy-gate.ts— Strapi middleware, který blokuje agentem iniciované mutující požadavky na základěautonomy_levelorganizace a případných aktivníchfreeze_windows, dřív než požadavek dorazí do controlleru.
Autentizace service-to-service
Section titled “Autentizace service-to-service”- Hlavička
X-Service-Secretpro interní REST volání (API Manager, Strapi webhooky, agent-gateway) - Rate limiting na API Manageru: 100 req / 15 min per IP
- Helmet + CORS na všech Fastify/Express službách
Model multi-tenancy
Section titled “Model multi-tenancy”Organizace (tenant) ├── Členové (role: Owner / Admin / Member / Viewer) ├── Cloud Instance (rozsah per org) ├── BYOC Credentials (šifrované per org) ├── Fleet Agenti (enrolled per org) ├── Agent Policies (autonomy_level, freeze_windows, per org) └── Billing Subscription (Lago + Stripe per org)Cross-tenant operace jsou povoleny pouze pro MSP/agency role (B2B2B track). Všechny API endpointy filtrují podle organizationId z KC JWT claims. Izolace dat je vynucována na vrstvě Strapi politik.
Struktura monorepa
Section titled “Struktura monorepa”code/├── orchestration/ # Docker Compose vstupní bod + .env├── sencai.space/ # Strapi v5 backend├── sencai.space-frontend/ # Platform Nuxt UI├── sencai-admin/ # Admin Nuxt UI├── sencai-audit/ # Audit viewer SPA├── sencai-web/ # Astro marketingový web├── api-manager/ # Fastify token service├── auth-service/ # Keycloak konfig + témata├── auth-service-consumer/ # RabbitMQ → KC sync├── webhook-publisher/ # Strapi → RabbitMQ bridge├── git-connector/ # RabbitMQ → Gitea sync├── cloud-connector/ # Multi-cloud provisioning worker├── opsloop-consumer/ # AI intelligence loop consumer├── ssh-proxy-service/ # Browser terminal WebSocket service├── graphql-gateway/ # GraphQL API gateway├── billing-adapter/ # Lago + Stripe billing bridge├── sencai-watchdog/ # Dunning microservice (cron při selhání platby)├── llm-service/ # Multi-provider LLM fasáda├── workspace-connector/ # Sync Google Workspace Directory├── notification-service/ # Konzument email + Slack├── event-store-consumer/ # fanout platform.events → ingest do Strapi├── backup-scheduler/ # Cron pro zálohu MySQL → S3/GCS├── agent-gateway/ # Fleet C2 brána (Go)├── sencai-agent/ # On-host Fleet agent binárka (Go)├── k8s-agent/ # Kubernetes Fleet agent (Go)├── infra-gcp/ # CDKTF IaC — vlastní GCP infrastruktura platformy (není compose služba)├── gamification-consumer/ # RabbitMQ konzument XP/hodnost/odznak (F4.GAM)├── forum-connector/ # KC ↔ Lemmy JIT provisioning most├── sencai-mq/ # RabbitMQ konfig├── git-service/ # Gitea konfig (Toolbox + Apps)├── sencai-forum/ # Lemmy komunitní fórum├── bootstrap/ # Bootstrap skripty databáze├── sencai-e2e/ # Playwright E2E test suite├── scripts/ # Pomocné skripty napříč repozitářem├── packages/│ └── audit/ # @sencai/audit sdílený balíček├── tooling/│ └── eslint-plugin-sencai/ # ESLint pravidlo require-audit-log└── sencai-docs/ # Tento dokumentační webPořadí startu
Section titled “Pořadí startu”Služby deklarují Docker health checky; depends_on: { condition: service_healthy } vynucuje pořadí:
- Databáze a secrets — MySQL (x3), PostgreSQL (x2) a Vault (pokud je zapnut profil
vault) startují brzy, protože několik služeb získává secrets z Vaultu při bootu - Messaging — RabbitMQ (
sencai-mq) - Proxy — Traefik
- Core — Strapi, Keycloak (start 30–90s), API Manager
- Konzumenti / standalone služby — auth-service-consumer, git-connector, cloud-connector worker, opsloop-consumer, notification-service, event-store-consumer, agent-gateway, billing-adapter, gamification-consumer, forum-connector
- Frontendy — sencai.space-frontend, sencai-admin, sencai-audit
- Volitelné — Forum, docs, monitoring
Škálování
Section titled “Škálování”Bezstavové (horizontální scale-out):
- Strapi (sdílená MySQL), API Manager, Webhook Publisher, Git Connector, Cloud Connector workery, OpsLoop Consumer, agent-gateway, billing-adapter, sencai-watchdog, llm-service, notification-service, event-store-consumer, workspace-connector, gamification-consumer, forum-connector
Stavové (single-instance nebo cluster mód):
- RabbitMQ (cluster podporován, ale náročný na konfiguraci), Keycloak (cluster se sdílenou DB), Gitea (jedna instance per účel)
Databáze:
- MySQL: read replicas doporučeny pro Strapi ve větším měřítku
- PostgreSQL: active-passive streaming replikace (Cloud Connector), multi-AZ pro HA