hub

package
v0.1.17 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 12, 2026 License: Apache-2.0 Imports: 35 Imported by: 0

Documentation

Overview

Package server ties the box manager to the HTTP front-ends that share one process and one port:

  • the box-control JSON API (under /api/v1/), used by the UI and by callers like the llmbox-mcp binary to create/list/destroy/exec boxes.
  • the admin web UI (/admin) and the OIDC sign-in that gates it and the per-box HTTP proxies.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Serve

func Serve(parent context.Context, cfg *config.Config, name, version string) error

Serve assembles and serves the llmbox hub from cfg: it opens the box store, sets up optional lifecycle hooks and admin/proxy auth, attaches the cluster hub (spokes join over the cluster transport and run every box), enables HTTP proxying of box ports, restores persisted sessions, starts the spoke-sync loop, and serves the box-control API and the UI on one HTTP port until the parent context is cancelled (SIGINT/SIGTERM) at which point it shuts down gracefully. The hub runs no box backend of its own — boxes run only on remote spokes. name and version label the startup banner.

@arg parent The parent context whose cancellation (or a termination signal) triggers graceful shutdown. @arg cfg The resolved configuration driving the store, auth, clustering, and HTTP server. @arg name The binary name shown in the startup banner. @arg version The build version shown in the startup banner. @error error if the store or authenticator cannot be built, a state directory cannot be created, or the HTTP server fails for a reason other than a clean shutdown.

@testcase TestServe starts the hub, serves the health endpoint, and shuts down on context cancel.

Types

type IdentitySession added in v0.0.48

type IdentitySession = store.IdentitySession

IdentitySession is a completed sign-in, used by the admin handlers.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server orchestrates boxes and owns the session registry.

func New

func New(hooks boxHooks, publicURL string, store Store, auth *auth.Authenticator) *Server

New builds a Server. publicURL is the externally reachable base URL used to construct proxy and admin links. store persists the box registry; pass a no-op store to disable persistence. hooks runs lifecycle hooks per box; pass nil to disable hook integration. auth gates the admin UI and per-box proxies behind provider sign-in; pass nil to leave them unauthenticated. The server routes every box to a remote spoke, so attach the cluster hub via SetHub before serving. Call Restore to load any saved sessions.

@arg hooks The box lifecycle hook runner; nil disables hook integration. @arg publicURL The externally reachable base URL for proxy and admin links. @arg store The box store used to persist the registry; a no-op store disables it. @arg auth The admin/proxy authenticator; nil leaves those surfaces unauthenticated. @return *Server A ready-to-use Server with an empty in-memory session registry.

@testcase TestCreateBoxRegistersSession builds a Server via New. @testcase TestCreateBoxRunsCreateHooks builds a Server with a hook runner.

func (*Server) APIHandler

func (s *Server) APIHandler() http.Handler

APIHandler builds the server's single HTTP handler: the box-control JSON API (under /api/v1/) plus the UI (auth pages, provider sign-in, admin UI, spoke connect, health, favicon). Everything is served on one port; with proxying enabled, requests to a proxy sub-domain are reverse-proxied to the box and all other Hosts fall through to these routes.

@return http.Handler A mux routing the box-control API and UI endpoints.

@testcase TestAPIHandlerServesUIAndAPI serves both the UI routes and the box-control API.

func (*Server) CloseBoxPort

func (s *Server) CloseBoxPort(ctx context.Context, spokeName, boxID string, port int) error

CloseBoxPort unpublishes a port of the box a spoke-originated request came from, implementing cluster.BoxPortService. It works even when the proxy base domain has since been unset, so a box can always clean up after itself.

@arg ctx Context for the request (unused; proxy state is hub-local). @arg spokeName The authenticated name of the spoke connection the request arrived on. @arg boxID The spoke-stamped box ID the request originates from. @arg port The published port to close. @error error if the box is unknown or on another spoke, or no proxy exists for that port.

@testcase TestCloseBoxPortDeletesProxy closes a previously published port. @testcase TestCloseBoxPortWrongSpoke rejects a box owned by a different spoke.

func (*Server) DefaultSpoke

func (s *Server) DefaultSpoke() (string, error)

DefaultSpoke returns the admin-chosen default spoke a box with no explicit spoke runs on, or "" when none has been set.

@return string The default spoke name, or "" when unset. @error error if the setting cannot be read from the store.

@testcase TestDefaultSpokeRoundTrip reads back a default spoke set via SetDefaultSpoke.

func (*Server) ListBoxPorts

func (s *Server) ListBoxPorts(ctx context.Context, spokeName, boxID string) ([]cluster.BoxPortInfo, error)

ListBoxPorts returns the published ports of the box a spoke-originated request came from — and only that box's — implementing cluster.BoxPortService.

@arg ctx Context for the request (unused; proxy state is hub-local). @arg spokeName The authenticated name of the spoke connection the request arrived on. @arg boxID The spoke-stamped box ID the request originates from. @return []cluster.BoxPortInfo The box's published ports. @error error if the box is unknown or on another spoke, or the proxy list cannot be read.

@testcase TestListBoxPortsScopedToBox lists only the requesting box's ports.

func (*Server) OpenBoxPort

func (s *Server) OpenBoxPort(ctx context.Context, spokeName, boxID string, port int, description string) (cluster.BoxPortInfo, error)

OpenBoxPort publishes a port of the box a spoke-originated request came from, implementing cluster.BoxPortService. It reuses createProxy, so it inherits its idempotency, port validation, and stale-container replacement; the proxy is recorded as created by "box:<box id>".

@arg ctx Context for the request (unused; proxy state is hub-local). @arg spokeName The authenticated name of the spoke connection the request arrived on. @arg boxID The spoke-stamped box ID the request originates from. @arg port The TCP port inside the box to publish. @arg description An optional human-readable note for the port. @return cluster.BoxPortInfo The published port with its public URL. @error error if port publishing is disabled, the box is unknown or on another spoke, the port is invalid, or persistence fails.

@testcase TestOpenBoxPortCreatesProxy publishes a port and stamps the box as creator. @testcase TestOpenBoxPortWrongSpoke rejects a box owned by a different spoke. @testcase TestOpenBoxPortProxyDisabled errors clearly when no proxy base domain is set.

func (*Server) ProxyEnabled

func (s *Server) ProxyEnabled() bool

ProxyEnabled reports whether the HTTP proxy feature is configured (a base domain was set via SetProxyBaseDomain).

@return bool True when proxying is enabled.

@testcase TestCreateProxyDisabled reports disabled without a base domain.

func (*Server) PruneDepartedSpokes

func (s *Server) PruneDepartedSpokes() ([]string, error)

PruneDepartedSpokes removes every session and proxy pinned to a spoke that no longer exists — a spoke de-enrolled from the cluster. Any still-enrolled spoke (even if momentarily disconnected) is kept, so a spoke that is merely offline is never purged; only one that has truly disappeared. Purged sessions have their destroy hooks replayed, exactly like a reap. It returns the box IDs of the purged sessions.

This is what keeps box-ID resolution unambiguous over time: a box whose spoke was removed can no longer linger as a duplicate session and be selected at random. Call it at startup (after Restore) and periodically from ReapLoop.

@return []string The box IDs of the sessions that were purged. @error error if the enrolled spokes cannot be read from the store.

@testcase TestPruneDepartedSpokesRemovesStaleObjects purges a de-enrolled spoke's session and proxy. @testcase TestPruneDepartedSpokesKeepsOfflineEnrolledSpoke keeps an enrolled-but-offline spoke's objects.

func (*Server) ReapLoop

func (s *Server) ReapLoop(ctx context.Context, every time.Duration, log func(string))

ReapLoop periodically destroys orphaned (never-authenticated) boxes, prunes their sessions, and runs the sync pass that folds each connected spoke's live inventory into the box records (see syncSpokes) — the continuous convergence keeping the store honest instead of a one-shot startup reconciliation. It blocks until ctx is cancelled.

@arg ctx Context whose cancellation stops the loop. @arg every How often to run a reap-and-sync pass. @arg log Optional sink for reaper log messages; may be nil.

@testcase TestPruneSessionsAfterReap covers the session pruning ReapLoop relies on. @testcase TestSyncMarksVanishedBoxTerminated covers the sync pass ReapLoop runs.

func (*Server) Restore

func (s *Server) Restore() (int, error)

Restore loads persisted sessions into the registry. It deliberately talks to no spoke: the store is the system of record, so startup only reads it back — a box's record is never dropped here just because its spoke is offline at boot. Drift between the records and what the spokes actually run is corrected continuously by the sync pass (see syncSpokes), which runs the same way at startup and an hour later rather than as a one-shot boot step. The only purge is store-driven: sessions and proxies pinned to a spoke that was de-enrolled (departed, not merely offline) while the hub was down are removed via PruneDepartedSpokes. It returns the number of sessions restored. Call it once at startup, before serving.

@return int The number of sessions restored into the registry. @error error if the store cannot be read.

@testcase TestRestoreLoadsWithoutSpokes restores every record without contacting any spoke. @testcase TestRestoreKeepsDisconnectedSpokeSessions keeps a session whose spoke is offline.

func (*Server) SetDefaultSpoke

func (s *Server) SetDefaultSpoke(name string) error

SetDefaultSpoke persists the default spoke an unqualified box create routes to. An empty name clears the default (unqualified creates then error).

@arg name The spoke name to make the default, or "" to clear it. @error error if the setting cannot be written to the store.

@testcase TestDefaultSpokeRoundTrip persists and clears the default spoke.

func (*Server) SetHub

func (s *Server) SetHub(hub clusterHub)

SetHub attaches the cluster hub holding connected remote spokes. Call it once at startup, before serving. Every box runs on a remote spoke resolved through the hub, so a running server always has one.

@arg hub The cluster hub resolving remote spokes by name.

@testcase TestCreateBoxRoutesToSpoke routes a box to a remote spoke via the hub.

func (*Server) SetProxyBaseDomain

func (s *Server) SetProxyBaseDomain(domain string)

SetProxyBaseDomain sets the parent domain per-box HTTP proxies are served under (e.g. "proxy.example.com"), enabling the proxy feature. An empty domain leaves it disabled. Call it once at startup before serving.

@arg domain The proxy parent domain; empty disables proxying.

@testcase TestCreateProxyRegistersAndBuildsURL enables proxying via this setter.

func (*Server) SpokeStatuses

func (s *Server) SpokeStatuses(_ context.Context) ([]SpokeStatus, error)

SpokeStatuses reports every enrolled remote spoke and its health, marking which are currently connected and which is the default. It returns nothing when no hub is attached.

@arg _ Context (unused; the data is in-memory and in the store). @return []SpokeStatus One entry per enrolled remote spoke. @error error if the enrolled spokes cannot be read from the store.

@testcase TestSpokeStatusesReportsHealth marks enrolled spokes connected or not. @testcase TestSpokeStatusesMarksDefault flags the default spoke.

type SpokeStatus

type SpokeStatus struct {
	Name       string    `json:"name" jsonschema:"the spoke's name"`
	Connected  bool      `json:"connected" jsonschema:"whether the spoke currently has a live connection to the hub"`
	Default    bool      `json:"default,omitempty" jsonschema:"true for the default spoke unqualified box creates run on"`
	EnrolledAt time.Time `json:"enrolled_at" jsonschema:"when the spoke enrolled"`
}

SpokeStatus describes one enrolled cluster spoke and its health: whether it currently holds a live connection to the hub, and whether it is the default spoke unqualified box creates route to.

type Store

type Store = store.Store

Store persists the box registry, sign-in (identity) state, and cluster records.

func OpenStore

func OpenStore(path string) (Store, error)

OpenStore opens (creating if needed) a SQLite-backed Store at path.

@arg path The filesystem path to the store's database file. @return Store A ready-to-use, SQLite-backed store. @error error if the store cannot be opened or initialized.

@testcase TestCreateBoxPersistsSession opens a store and persists a session through it.

Directories

Path Synopsis
Package apikey mints and verifies the API keys that authenticate callers of the llmbox box-control API.
Package apikey mints and verifies the API keys that authenticate callers of the llmbox box-control API.
Package auth gates the admin UI and the per-box HTTP proxies behind OIDC provider sign-in.
Package auth gates the admin UI and the per-box HTTP proxies behind OIDC provider sign-in.
Package config loads llmbox's YAML configuration file for the hub (the llmbox-server).
Package config loads llmbox's YAML configuration file for the hub (the llmbox-server).
Package hooks runs external hook executables at box lifecycle events.
Package hooks runs external hook executables at box lifecycle events.
Package store persists llmbox's durable state — the box registry, the admin sign-in (identity) state, the cluster enrollment records, API keys, and hub-wide settings — behind a small set of interfaces.
Package store persists llmbox's durable state — the box registry, the admin sign-in (identity) state, the cluster enrollment records, API keys, and hub-wide settings — behind a small set of interfaces.
Package token implements the llmbox-server `token` command tree — creating, listing, and revoking the one-time join tokens a hub issues to enroll spokes.
Package token implements the llmbox-server `token` command tree — creating, listing, and revoking the one-time join tokens a hub issues to enroll spokes.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL