Přeskočit na obsah

Architektura

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.


SlužbaÚčelPortJazyk
sencai.spaceStrapi v5 backend — REST API, content-types, lifecycle hooks1337Node.js / TS
sencai-backendGo strangler-fig náhrada backendu — shadow-only, žádný živý provoz (viz níže)8080 (interně)Go
sencai.space-frontendPlatform UI — auth, dashboard, gravity UI3000Nuxt 3 / Vue 3
sencai-adminInterní admin dashboard (KC-auth)3100Nuxt 3
sencai-auditViewer auditních záznamů (read-only SPA)3300Nuxt 3
sencai-webMarketingový web (Astro 5 SSG, 15 locales)Astro
api-managerSpráva šifrovaných service-to-service tokenů3200Fastify / TS
auth-service-consumerRabbitMQ → Keycloak sync + audit událostiNode.js / TS
webhook-publisherBridge Strapi webhooks → RabbitMQ1347Node.js (JS)
git-connectorRabbitMQ → Gitea sync (Toolbox + Apps)3002Node.js / TS
cloud-connectorWorker pro multi-cloud provisioning (AWS/GCP/Azure/Hetzner/DO)4321Node.js / TS
opsloop-consumerIntelligence loop — korelace incidentů, návrhy runbooků3005Node.js / TS
ssh-proxy-serviceTerminál v prohlížeči přes WebSocket (SSH proxy)3400Node.js / TS
graphql-gatewaySjednocená GraphQL API brána4000Node.js / TS
billing-adapterLago usage-based billing + platby přes Stripe (ADR-002)3600Node.js / TS
sencai-watchdogDunning microservice — cron pro suspend/archive při selhání platby3700Node.js / TS
llm-serviceMulti-provider LLM fasáda (primárně Anthropic, OpenAI-kompatibilní fallback)4430Node.js / TS
workspace-connectorSync Google Workspace Directory API3500Node.js / TS
notification-serviceKonzument pro email + Slack (notification.events, billing.events)Node.js / TS
event-store-consumerKonzument fanoutu platform.events → ingest do StrapiNode.js / TS
backup-schedulerPlánovaný cron pro zálohu MySQL → S3/GCS + restore drillNode.js / TS
agent-gatewayFleet C2 brána — enrollment + WSS heartbeat/command kanál4400Go
sencai-agentOn-host Fleet agent binárka (monitoring, patch, inventory, runbooky)— (klient)Go
k8s-agentKubernetes DaemonSet Fleet agentGo
gamification-consumerJediný writer content-typů user-rank/gamification-event (F4.GAM) — počítá změny XP/hodnosti/odznaků na straně serveru3800Node.js / TS
forum-connectorJIT provisioning most mezi Keycloakem a sencai-forum (Lemmy)4181Fastify / TS
sencai-forumKomunitní fórum (Lemmy 0.19 + oauth2-proxy pro KC SSO)8536— (upstream)
auth-serviceKonfigurace Keycloak + vlastní témata8080— (upstream)
sencai-mqRabbitMQ broker5673— (upstream)

┌──────────────────────────────────────────────────────────────────┐
│ 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──▶ Strapi

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

Routing keys na topic exchangech mají formát <entita>.<akce>. user a organisation jsou direct exchange; platform.events je fanout.

ExchangeTypPříklady routing keysKonzumenti
userdirectcreate, updateauth-service-consumer
organisationdirectcreate, member.added, member.removedgit-connector, opsloop-consumer
git.eventstopicgit.repo.created, git.key.rotatedgit-connector
cloud.eventstopiccloud-instance.provision, cloud-instance.stop, cloud-instance.terminatecloud-connector worker
audit.eventstopicaudit.writeauth-service-consumer / audit-consumer (ukládá hash-chained záznamy)
notification.eventstopicbilling.dunning.grace_period, feedback.submitted, dpa.requested, …notification-service (email + Slack)
billing.eventstopicbilling.subscription_activated, billing.payment_succeeded, billing.payment_failed, …notification-service
platform.eventsfanout(všechny platformní eventy, bez routing key)event-store-consumer → POST /api/platform-events/ingest
alertmanager.eventstopicalerty z monitoringuopsloop-consumer
gamification.eventstopicxp.granted, badge.check, streak.tick, easter-egg.rollgamification-consumer (re-publikuje rank.up/badge.awarded/easter-egg.triggered pro frontendový notifikační most)
{
"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" }
}
}
}
  • 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)

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ásit

První 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/dashboard

Provisioning 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á aktualizaci
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ý socket

ssh-proxy-service s enrollmentem Fleet agenta nesouvisí — slouží pouze k terminálu v prohlížeči (viz níže).

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 trail
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 frontendu
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í
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 Strapi
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.*)
*.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)

SlužbaEngineDocker HostDev PortPoznámky
Strapi (backend)MySQL 8backend-db3306Knex auto-migrace
KeycloakMySQL 8keycloak-db3307KC-managed schema
Gitea (obě instance)MySQL 8git-service-mysql3308Sdílená DB
Cloud ConnectorPostgreSQL 15cloud-connector-db5434TypeORM sync
Forum (Lemmy)PostgreSQL 15forum-db5435Lemmy-managed schema
sencai-backend (Go)MySQL 8go-backend-db3309Rozdě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.


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.


  • 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
  • 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

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.


  • 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í /enroll je záměrně neautentizované (řešeno tokenem), ale výsledný řídicí kanál /ws vyžaduje per-agent klientský certifikát vydaný při enrollmentu
  • 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ů
  • Každá mutující akce publikuje do exchangu audit.events (routing key audit.write)
  • Balíček @sencai/audit: publishAuditEvent(action, payload) s kontextem AsyncLocalStorage
  • 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-type audit-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-chain př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-log vynucuje pokrytí auditu na mutujících funkcích

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-policy content-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_radius a volitelný allowlist typů akcí. Deny-by-default: pokud žádná aktivní politika akci nepovoluje, je zablokována.
  • approval-request a elevation-request content-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_level organizace a případných aktivních freeze_windows, dřív než požadavek dorazí do controlleru.
  • Hlavička X-Service-Secret pro 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

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.


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í web

Služby deklarují Docker health checky; depends_on: { condition: service_healthy } vynucuje pořadí:

  1. 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
  2. Messaging — RabbitMQ (sencai-mq)
  3. Proxy — Traefik
  4. Core — Strapi, Keycloak (start 30–90s), API Manager
  5. 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
  6. Frontendy — sencai.space-frontend, sencai-admin, sencai-audit
  7. Volitelné — Forum, docs, monitoring

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