-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
TheMinecraftGuyGuru edited this page Jun 8, 2026
·
5 revisions
┌────────────────────┐
│ Core Admin UI │
│ (HTMX + Go tmpl) │
└─────────┬──────────┘
│
┌─────────▼──────────┐
│ Module UIs │
│ (SvelteKit / any) │
└─────────┬──────────┘
│
┌─────────▼──────────┐
│ API Gateway │
│ (REST + OpenAPI) │
└───────┬────────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌───────▼──────┐ ┌─────▼─────┐ ┌──────▼──────┐
│ Event Bus │ │ Scheduler │ │ Service Reg │
│ (in-memory) │ │ (module) │ │ │
└───────┬──────┘ └─────┬─────┘ └──────┬──────┘
│ │ │
└──────┬───────┴───────┬──────┘
│ │
┌──────────▼─────────────────────────┐
│ Modules (all capabilities) │
│ │
│ Downloaders Transcoding │
│ Indexers Media Analysis │
│ Metadata File Ops │
│ Sup.Content ML Tasks │
│ Notifications Replication │
│ Media Server Worker Pool │
│ Storage │
└───────────────────────────────────┘
- External: REST + OpenAPI 3.1
- Internal mesh: gRPC + protobuf (proto contracts defined in
proto/; mesh server/client planned for a future phase) - Handles auth, rate limiting, routing
- Composes UI panels from registered modules
- Not built into core — provided by a cluster module implementing
contracts.Cluster - Gossip-based membership available as a module (
cluster-gossip) with leader election and failure detection - Core discovers the cluster module from the registry at bootstrap; single-node deployments run without one
- Modules receive the cluster via
ModuleDeps.Cluster— nil when running standalone
- Core provides an in-memory pub/sub bus for bootstrapping and single-node
- NATS available as a module (
eventbus-nats) for distributed messaging - Pub/sub for loose coupling, request/reply for synchronous queries
- Cron-based scheduling provided by the
scheduler-cronmodule - Publishes
scheduler.task.executeevents on the bus - Replaceable — swap in a distributed scheduler module without touching core
- Module registration and discovery
- Health checking
- Capability advertisement
- Version tracking
- Validates sessions and enforces RBAC based on session roles/permissions
- Actual authentication is delegated to auth modules (Plex, OAuth/OIDC, LDAP, local accounts)
- Multiple auth modules can be active simultaneously — users sign in via any configured provider
- Per-module permissions, API token management, audit logging
- Abstracts all storage behind object IDs
- Capability-based provider negotiation
- Multi-provider routing via policies
- Cache layer management
- Defines and executes multi-step pipelines
- Retry with backoff
- Idempotency guarantees
- Compensation on failure
- Module lifecycle management (load, init, start, stop)
- Dependency resolution
- Version compatibility checking
- Process isolation (external modules)
-
Multi-kind registration — a single module can register under multiple
ModuleKindvalues (e.g., Jellyfin as bothplaybackANDproviderANDauth). The registry indexes by all declared kinds. -
Capability-based discovery —
FindByCapability(cap string)complementsFindByKind(kind), enabling fine-grained service discovery (e.g., "find all modules that provide supplementary content")
All internal communication goes through the event bus or gRPC:
Module A ──(NATS pub)──> Event Bus ──(NATS sub)──> Module B
Module A ──(gRPC)─────> Module B (request/reply)
Module A ──(NATS req)──> Event Bus ──(NATS rep)──> Module A
Modules never call each other directly. This ensures:
- Crash isolation — one module failure doesn't cascade
- Independent updates — modules can be upgraded separately
- Language agnosticism — future modules can be in any language
- Testability — modules can be mocked via event replay
Decision: Compile-time modules for MVP, external services planned for later phases
| Approach | Pros | Cons |
|---|---|---|
| Compile-time (chosen for MVP) | No network overhead, simple deployment, single binary option | Must recompile to add modules, all Go |
| External services (planned) | Language agnostic, crash isolation, independent updates, HA-friendly | More complexity, network overhead |
Modules are compiled into the core binary via blank imports + build tags. The -tags default preset bundles essential modules. Future phases will support external modules over gRPC/NATS.
Decision: Event-driven with request/reply for queries
- Events for state changes:
download.completed,media.requested - Request/reply for queries: "what's the status of download X?"
- Events are the primary integration pattern
Decision: Small interfaces, not giant monolithic ones
Instead of one giant StorageProvider interface, use capability interfaces:
-
Streamable,Seekable,Watchable,AtomicMovable,Hardlinkable
Modules detect capabilities at runtime and adapt.