Skip to content

Architecture

Single binary, three listeners

grex intentionally uses three TCP listeners so each surface has its own network exposure and (eventually) auth boundary:

┌────────────────────────────────────────────────────────────┐
│                          grex                              │
│  ┌─────────────┐  ┌──────────────┐  ┌───────────────────┐  │
│  │ OpAMP :4320 │  │ UI :8080     │  │ Telemetry :9090   │  │
│  │ /v1/opamp   │  │ UI + /api/*  │  │ /healthz /readyz  │  │
│  │ TLS / mTLS  │  │ (open today) │  │ /metrics          │  │
│  └──────┬──────┘  └──────┬───────┘  │ /metrics/fleet    │  │
│         │                │          │ /debug/pprof?     │  │
│         ▼                ▼          └───────────────────┘  │
│         in-memory fleet.Registry                           │
└────────────────────────────────────────────────────────────┘

Wiring is centralized in cmd/grex/main.go:

  1. Load config
  2. Create two Prometheus registries (server vs fleet)
  3. Construct fleet.Registry with heartbeat settings + metrics events
  4. Attach opamp.Handler to the registry
  5. Mount api + ui on one HTTP mux
  6. server.New(...).Start() for all listeners
  7. Run registry background loop; wait for signal or fatal listener error

Data flow

Collector / gateway
    │  OpAMP AgentToServer
opamp.Handler  ──writes──►  fleet.Registry
                ┌───────────────┼────────────────┐
                ▼               ▼                ▼
           api.Handler     ui.Handler    metrics.FleetCollector
           (JSON)          (HTML)        (scrape-time gauges)
  • Writes happen on OpAMP callbacks (and registry eviction ticks)
  • Reads happen on API/UI handlers and Prometheus collect
  • Registry is concurrency-safe; metrics event hooks must not re-enter the registry while it holds locks (documented on fleet.Events)

In-memory state, with an opt-in durability layer underneath

fleet.Registry is still the sole source of truth for every live read path (API, UI, metrics). Correctness after restart depends on:

  • Agents reconnecting (direct), or
  • Gateways continuing to relay check-ins while grex requests full state

A write-only, opt-in Postgres layer now exists under the registry (internal/persistence) — durable, but nothing reads from it yet, and it does no good on its own without a second grex process to reconcile against. replicaCount is still pinned to 1 in the Helm chart; horizontal scale of grex itself isn't built. See Persistence for the write path and the safeguards already in place for when it is.

Library boundaries

Concern Library / approach
OpAMP protocol open-telemetry/opamp-go server packages
Metrics prometheus/client_golang
Config gopkg.in/yaml.v3
UI stdlib html/template, vendored htmx, hand-written CSS, go:embed
Logging stdlib log/slog to stderr

grex does not fork OpAMP; it implements callbacks and the gateway custom capability on top of opamp-go.

Auth boundaries (intended)

Design assigns:

  • Collectors → OpAMP mTLS (partially implemented)
  • Humans/API → UI mTLS then OIDC (issue #11)
  • Scrapers → telemetry network policy (no app-level auth)

Today only the OpAMP TLS path is real. See Admin: authentication.