R2-D2
Dashboard
Node Red
Restreaming
The Force
UpdatedNever
v1.0.0

Overview

Loading…
R2-D2
Dashboard
Node Red
Restreaming
The Force
UpdatedNever
v1.0.0
DDroidspeak / Docs
Operator handbook
Droidspeak
What is R2-D2?
Runtime architectureAuth — Keycloak migration plan
FleetRestreamingGalaxy MapThe RepublicTemple ArchivesSTANAG 4817The Force
Tech StackNext.js 15 + React 19Tailwind v4ZustandTanStack Table + DataViewhls.jsLow-latency playerreagraphoglreact-grid-layoutMonacoScalar API Referencefumadocs
Cluster InfraTrailBaseReductStoreRestreamer (datarhei/core)TBMQKeycloakLonghornkube-vipIngress (Caddy + nginx-ingress + Traefik)Netbird
Operator QA Runbook
Install — RKE2Install — Dokploy
Contributor guideRelease Notes
Architecture

Auth — Keycloak migration plan

Why TrailBase today, why Keycloak eventually, the migration sequence, and what's already in-tree under nodered/keycloak/.

Planning + scaffolding. The realm template, theme, and provider JAR are committed at nodered/keycloak/, but Keycloak isn't routing live traffic yet. TrailBase is still the only identity backend in production until this plan is signed off and the cutover is scheduled.

Where we are today

R2-D2 uses TrailBase for identity. The login form POSTs to /api/auth/login, which forwards { email, password } to TrailBase. On success, the dashboard sets an httpOnly session cookie that every authenticated route checks via headers populated by the edge middleware (x-user-id, x-user-email, x-user-role).

This is a good shape for a single private tenant:

  • Zero config friction. TrailBase is already in the stack for the Republic and Holoprojector data — adding identity to the same service is free.
  • Server-side state. No bearer tokens floating in localStorage, no token-refresh endpoint, no rotation logic.
  • Inspectable. TrailBase admin lets you see who's registered and revoke them in seconds.

But it's also a shape with a ceiling.

Where it stops working

There are three concrete reasons the current model wants to be replaced:

  1. The Republic has member nations. Each nation will eventually need its own namespace of operators with its own admin scope. TrailBase models users but doesn't have a first-class realm/tenant abstraction; we'd have to bolt one on.
  2. External SSO. Some nations have existing identity providers (GitLab/Keycloak/ AzureAD). Asking them to maintain a second password in TrailBase is a non-starter.
  3. Audit + group→role mapping. Today every operator is effectively an admin. We need at least three roles (operator, editor, admin) and an audit trail per mutation. The audit trail exists for Restreamer mutations; making it consistent across planes is a refactor that's easier on top of a real RBAC model.

Keycloak handles all three out of the box, and gives us OIDC for free.

What's already in the repo

The scaffolding for Phase A is committed under nodered/keycloak/. Nothing here is wired into the running cluster yet — the realm JSON is a template the deploy step will hand to kcadm, and the theme is a directory the container will mount.

nodered/keycloak/
├── realms/
│   └── r2d2-realm.json         # realm=r2d2, client=r2d2-fleet, hosts r2d2.office.ilab.zone
├── themes/
│   └── r2d2/                   # dark-space + teal Keycloak theme, aligned with the dashboard
│       ├── login/              # PF v5 cosmetic overlay (r2d2.css), brand macro, FTL templates
│       ├── account/            # Keycloak v3 React Account UI (logo swap only)
│       ├── email/              # dark email card (teal #2dd4bf brand bar + buttons)
│       └── STYLEGUIDE.md       # token map vs r2d2-fleet/src/styles/globals.css
└── providers/
    └── keycloak-altcha-jar-with-dependencies.jar   # CAPTCHA on the registration flow

Brand voice: wordmark "R2-D2 Fleet", tagline "Astromech Fleet Console", redirect copy reads "Boarding the fleet in N seconds…". The theme forces dark mode regardless of the operator's system preference so the SSO chrome doesn't look like a separate product from the dashboard.

Realm identifiers (everything downstream must agree on these):

FieldValue
Realmr2d2
Client (dashboard)r2d2-fleet
Login / account / email themer2d2
Public hostr2d2.office.ilab.zone
Dev redirecthttp://localhost:3210/api/auth/callback/keycloak
Logo URL (for emails)https://r2d2.office.ilab.zone/image.png

Target end-state

┌───────────────────────┐
│ Member nation IdP     │ (Azure / GitLab / etc — optional federation)
└──────────┬────────────┘
           │ OIDC
           ▼
┌───────────────────────┐
│   Keycloak realm      │  realm: r2d2
│   ┌─────────────────┐ │  - clients: r2d2-fleet, restreamer, trailbase
│   │ groups          │ │  - groups → roles (operator/editor/admin)
│   │ - admin         │ │  - identity providers (per-nation OIDC)
│   │ - editors       │ │
│   │ - operators     │ │
│   └─────────────────┘ │
└──────────┬────────────┘
           │ OIDC authorization code flow
           ▼
┌───────────────────────┐       ┌────────────────────────────────┐
│   r2d2-fleet          │ ─────►│ TrailBase (records only)       │
│   - NextAuth/Auth.js  │       │  -> identity comes from        │
│     client            │       │     Keycloak JWT, not from     │
└──────────┬────────────┘       │     TrailBase auth tables      │
           │ JWT in cookie      └────────────────────────────────┘
           ▼
   per-route role check  →  audit log row in plane-specific audit table

Migration sequence (planned, not committed)

Phase A — coexistence (no operator-visible change)

  1. Stand up a Keycloak realm in-cluster. Scaffolding committed — realms/r2d2-realm.json has the realm, the r2d2-fleet client (auth code + PKCE, public + private redirect URIs for local dev on port 3210), and the role catalog. The container needs to import this JSON on first boot; that deploy step is the next concrete TODO.
  2. Add an AUTH_BACKEND=trailbase|keycloak env var to r2d2-fleet. Default trailbase for compat. Implement the Keycloak path behind it but don't switch.
  3. Mirror users: every TrailBase user becomes a Keycloak user with the same email and a forced password reset on first login.
  4. Run both side-by-side in a non-production deploy. Smoke-test every authenticated route.

Phase B — switch

  1. Flip AUTH_BACKEND=keycloak on the prod deploy. TrailBase sessions still work for anyone who logged in before the switch (until cookie expiry), but new logins go through Keycloak.
  2. Watch the audit log for failures. Roll back by flipping the env back — no schema changes have happened.

Phase C — RBAC and federation

  1. Define Keycloak groups (admin, editor, operator) and roles. Map JWT realm_access.roles to a per-request role in the dashboard. The realm JSON already declares the role catalog (pending, read-only, editor, admin, super-admin) so this is wiring, not schema design.
  2. Every mutating route gains a role check. The minimum role is documented in the OpenAPI spec.
  3. For each member nation that wants SSO, federate their existing IdP into the r2d2 Keycloak realm. The dashboard doesn't change — Keycloak handles the federation.

Phase D — TrailBase cleanup

  1. Drop TrailBase's auth tables. TrailBase keeps serving the Republic/Holoprojector record APIs; it stops being an identity provider.
  2. Delete the AUTH_BACKEND=trailbase code path.

Theming notes

The themes/r2d2/ theme is a cosmetic overlay on Keycloak's keycloak.v2 parent (login) and keycloak.v3 parent (account UI). It doesn't fork any FreeMarker templates that the parent could update — the rebrand is concentrated in:

  • login/resources/css/r2d2.css — dark background #0a0e1a, glass card rgba(17,24,39,0.78) with backdrop-filter: blur(20px), teal #2dd4bf accent + focus glow, tri-tone (teal → blue → violet) 3px brand bar pinned to the viewport top, ambient radial-gradient galaxy overlay. All tokens are documented in themes/r2d2/STYLEGUIDE.md and 1:1 mapped to r2d2-fleet/src/styles/globals.css.
  • login/r2d2-brand.ftl — the logo + wordmark + tagline macro the rest of the login templates import.
  • email/html/template.ftl — dark email card (#111827 surface on #0a0e1a body, teal brand bar, JetBrains Mono for OTP codes). All inline CSS, no prefers-color-scheme, because Outlook strips it.

When Keycloak ships a new minor version, only r2d2.css and the four R2-D2-specific FTLs (r2d2-brand.ftl, login.ftl, info.ftl, error.ftl, login-verify-email.ftl, register.ftl, delete-account-confirm.ftl, buttons.ftl) need a re-review against the parent's diff. The rest is inherited.

Open questions

These are the things this plan does not yet answer. They're the next conversation to have before scheduling implementation:

  • Where does Keycloak run? Same cluster as r2d2-fleet, or a separate "platform" cluster? The argument for separate is the usual one — identity outages shouldn't be caused by the system that depends on identity.
  • Database backend for Keycloak. Yugabyte (consistent with the rest of the stack) or a dedicated postgres? Keycloak's Yugabyte story isn't perfectly smooth; a small postgres might be the pragmatic answer.
  • Service-to-service auth. Right now the dashboard talks to Restreamer with no auth (same-cluster network policy). With Keycloak we have the option of issuing service accounts and JWTs — worth doing for Restreamer specifically, since it has state.
  • TrailBase record API authentication. When TrailBase stops being the IdP, the dashboard needs to authenticate to TrailBase's record API as itself, not as the operator. That's either a TrailBase service token or a Keycloak-issued client cred.

Rollback plan

The migration is two-stepped on purpose: the AUTH_BACKEND env flag lets us flip back to TrailBase identity at any point during Phase A or Phase B without touching schema or code. Phase C/D introduce role checks and remove the TrailBase auth code; rolling back from there means redeploying the previous tag.

What this plan does not propose

  • It does not propose mTLS between services. Network policy + TrailBase service tokens cover the threat model in a small private cluster.
  • It does not propose a separate user-management UI. Keycloak's admin console is good enough for the populations we have.
  • It does not propose passkeys/WebAuthn. Could be added later inside Keycloak; not needed for the initial cut.

Runtime architecture

How the dashboard, Node-RED, Restreamer, TrailBase, and Yugabyte fit together at runtime.

Fleet

The Node-RED swarm — registry, status probes, flow lifecycle, and the staging→prod promotion path.

On this page

Where we are todayWhere it stops workingWhat's already in the repoTarget end-stateMigration sequence (planned, not committed)Phase A — coexistence (no operator-visible change)Phase B — switchPhase C — RBAC and federationPhase D — TrailBase cleanupTheming notesOpen questionsRollback planWhat this plan does not propose