hub

package
v0.2.0-alpha.1 Latest Latest
Warning

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

Go to latest
Published: Nov 13, 2026 License: Apache-2.0 Imports: 41 Imported by: 0

Documentation

Overview

Package hub implements the Regulus hub, the process that everything else talks to: it serves the dashboard and the HTTP API, accepts the connections of the agents, keeps the live state of the fleet and drives the updates.

What it does, and where to start reading:

  • hub.go: New builds a Hub from the configuration, Serve runs it. Serve starts the HTTP server (dashboard, API, the /api/stream WebSocket) and, when agent_listen is set, the gRPC server for remote agents.
  • api.go, openapi.go, layout.go, updates_api.go: the HTTP API and its OpenAPI description.
  • agents.go, agent_net.go, local.go: the server side of the agent-hub contract (proto/regulus/agent/v1): heartbeats, metrics, commands, the in-process connection of the built-in agent, and refusal of agents whose version is not accepted.
  • fleet_api.go: enrollment tokens and the list of enrolled agents, on top of package fleet.
  • metrics.go, events.go, stream.go: samples kept in memory and the live stream sent to the dashboard.
  • rollout.go: the update of the agents in waves, driven by an administrator.

The hub does not own the details of accounts and sessions (package auth), certificates (pki), the registry of agents (fleet), release download and verification (update) or the agent logic itself (agent). Redundancy and failover (#103) do not exist yet.

Index

Constants

View Source
const (

	// EventAgentsListenerOn is the audit event of turning remote agents on.
	EventAgentsListenerOn = "agents_listener_on"

	// DefaultAgentPort is the port proposed for remote agents.
	DefaultAgentPort = 8443
)

The listener for remote agents is turned on either by the configuration (agent_listen, which wins and cannot be changed from the dashboard) or from the dashboard, which keeps the address in the database so it survives a restart.

View Source
const (
	ListenerFromConfig    = "config"
	ListenerFromDashboard = "dashboard"
)

Where the listener address comes from.

View Source
const (
	EventRolloutStarted  = "update_rollout_started"
	EventRolloutFinished = "update_rollout_finished"
	EventAgentUpdated    = "update_agent_updated"
	EventAgentFailed     = "update_agent_failed"
)

Audit events of the rollout.

View Source
const (
	AgentPending    = "pending"
	AgentOffered    = "offered"    // the update was sent; the server is fetching it
	AgentInstalling = "installing" // handed to the installer; waiting for it to come back
	AgentUpdated    = "updated"
	AgentFailed     = "failed"
	AgentSkipped    = "skipped" // an earlier failure stopped the rollout
)

Status of one server in a rollout.

View Source
const (
	RolloutIdle    = "idle"
	RolloutRunning = "running"
	RolloutDone    = "done"
	RolloutFailed  = "failed" // stopped by a failure
	RolloutStopped = "stopped"
)

State of a rollout.

View Source
const BuiltinAgentID = "local"

BuiltinAgentID is the id of the agent that runs inside the hub.

View Source
const ReleaseRepo = "CodexCircle/Regulus"

ReleaseRepo is the repository the hub reads releases from.

Variables

View Source
var (
	ErrAgentNotFound     = errors.New("agent is not connected")
	ErrAgentDisconnected = errors.New("agent disconnected before answering")
)

Errors returned by SendCommand.

View Source
var (
	ErrAgentsOn       = errors.New("remote agents are already on")
	ErrAgentsInConfig = errors.New("agent_listen is set in the configuration: change it there")
	ErrInvalidPort    = errors.New("the port must be a number from 1024 to 65535")
	ErrHubNotServing  = errors.New("the hub is not serving yet")
)

Errors of EnableAgents.

View Source
var (
	ErrRolloutRunning = errors.New("an update of the servers is already running")
	ErrNothingToRoll  = errors.New("no connected server runs an older version than this hub")
	ErrNotRelease     = errors.New("this hub is not a release build, so it has no release to offer the servers")
)

Errors of the rollout.

View Source
var ErrAgentsOff = errors.New("remote agents are off: turn them on in Servers, or set agent_listen in the configuration")

ErrAgentsOff is returned when remote agents are not enabled.

Functions

func NewHandler

func NewHandler(d Deps) http.Handler

NewHandler returns the hub HTTP handler. The API (under /api/) requires a login, except for the endpoints of the auth package. The dashboard files, /healthz and /api/database (one boolean) are public: they hold no data, and the page asks for a login before it shows anything.

Types

type AgentInfo

type AgentInfo struct {
	ID            string
	Version       string
	System        *agentv1.SystemInfo
	ConnectedAt   time.Time
	LastHeartbeat time.Time // zero until the first heartbeat
	LastMetrics   *agentv1.MetricsReport
	// ProtocolVersion is the version negotiated for the connection.
	ProtocolVersion uint32
	// Compat tells how the agent's release relates to the hub's.
	Compat update.Compat
}

AgentInfo is a snapshot of a connected agent.

type AgentListener

type AgentListener struct {
	// Enabled is true while remote agents can connect.
	Enabled bool `json:"enabled"`
	// Port is where they connect, when enabled.
	Port int `json:"port,omitempty"`
	// Source is ListenerFromConfig or ListenerFromDashboard, when enabled.
	Source string `json:"source,omitempty"`
	// CAFingerprint is what an agent pins to trust this hub.
	CAFingerprint string `json:"ca_fingerprint"`
	// Editable is true when the dashboard may turn the listener on.
	Editable bool `json:"editable"`
}

AgentListener is the state of the listener for remote agents.

type AgentSource

type AgentSource interface {
	Agents() []AgentInfo
	// StaleAfter is how long an agent may stay silent before it is reported as
	// stale.
	StaleAfter() time.Duration
	// Metrics returns the recent metrics of an agent, oldest first; known is
	// false for an agent the hub never saw.
	Metrics(agentID string) (samples []MetricsSample, known bool)
	// Subscribe returns a channel of JSON events (see events.go) and a function
	// that ends the subscription. The channel is closed if the subscriber falls
	// too far behind.
	Subscribe() (events <-chan []byte, cancel func())
}

AgentSource provides the agent data served by the HTTP API. *Hub implements it.

type Deps

type Deps struct {
	// UI holds the compiled dashboard.
	UI fs.FS
	// Agents, Layouts and Log may be nil in tests; the agent list is then empty
	// and the layout endpoints answer 404.
	Agents  AgentSource
	Layouts LayoutStore
	Log     *slog.Logger
	// Auth protects the API. A nil Auth leaves it open, which is only meant for
	// tests.
	Auth *auth.Service
	// Fleet enrolls and revokes remote agents. May be nil in tests; the
	// endpoints then answer 404.
	Fleet FleetControl
	// Updates reports and steers the updates of the hub. May be nil in tests;
	// the endpoints then answer 404.
	Updates UpdateControl
	// Rollout updates the managed servers to the version of the hub. May be nil
	// in tests; the endpoints then answer 404.
	Rollout RolloutControl
	// Version is the version the hub was built with. The enrollment answer
	// carries it when it is a release, so a new agent installs the same one.
	Version string
	// DatabaseProblem returns why the database is not answering, or "" when it
	// is. Nil means "no problem", as for SQLite and in tests. Only whether
	// there is a problem is ever sent to a client, never the text.
	DatabaseProblem func() string
}

Deps are what the HTTP handler serves.

type Enrollment

type Enrollment struct {
	Token         string
	Name          string
	ExpiresAt     time.Time
	CAFingerprint string
	AgentPort     int
}

Enrollment is what an admin needs to add a server: the token, valid once, and what the agent must trust.

type FleetControl

type FleetControl interface {
	CreateEnrollment(ctx context.Context, by, name string) (Enrollment, error)
	RevokeAgent(ctx context.Context, by, id string) error
	RenameAgent(ctx context.Context, by, id, name string) error
	AgentListener(ctx context.Context) (AgentListener, error)
	EnableAgents(ctx context.Context, by string, port int) (AgentListener, error)
}

FleetControl is what the HTTP API needs to enroll and revoke remote agents. *Hub implements it.

type Hub

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

Hub is a running-capable hub instance: its configuration, database, UI and the agents connected to it.

func New

func New(ctx context.Context, cfg config.Config, log *slog.Logger, ui fs.FS, opts ...Option) (*Hub, error)

New opens the database (applying migrations) and returns a Hub that serves the dashboard from ui. Call Close when done.

func (*Hub) AgentAddr

func (h *Hub) AgentAddr() string

AgentAddr is where the listener for remote agents is bound, or "" while it is off.

func (*Hub) AgentConn

func (h *Hub) AgentConn() (*grpc.ClientConn, error)

AgentConn returns a client connection to the hub's agent service over the in-process transport. The built-in agent uses it. Close it when done.

func (*Hub) AgentListener

func (h *Hub) AgentListener(context.Context) (AgentListener, error)

AgentListener reports whether remote agents can connect, and where.

func (*Hub) Agents

func (h *Hub) Agents() []AgentInfo

Agents returns a snapshot of the connected agents, ordered by id.

func (*Hub) Close

func (h *Hub) Close() error

Close releases the hub resources.

func (*Hub) CreateEnrollment

func (h *Hub) CreateEnrollment(ctx context.Context, by, name string) (Enrollment, error)

CreateEnrollment makes a one-time enrollment token.

func (*Hub) EnableAgents

func (h *Hub) EnableAgents(ctx context.Context, by string, port int) (AgentListener, error)

EnableAgents turns remote agents on at port, on every address of the server, and keeps the choice for the next start. It is refused when the configuration sets agent_listen, which always wins.

func (*Hub) Layout

func (h *Hub) Layout(ctx context.Context, userID int64) (json.RawMessage, time.Time, bool, error)

Layout implements LayoutStore.

func (*Hub) Metrics

func (h *Hub) Metrics(agentID string) (samples []MetricsSample, known bool)

Metrics returns the recent metrics reported by an agent, oldest first. known is false when the hub has never seen the agent.

func (*Hub) Registry

func (h *Hub) Registry(ctx context.Context) ([]fleet.Agent, error)

Registry lists the enrolled agents, connected or not.

func (*Hub) Rejection

func (h *Hub) Rejection(id string) (Rejection, bool)

Rejection returns why an enrolled agent was last refused because of its version, if it has not connected since.

func (*Hub) RenameAgent

func (h *Hub) RenameAgent(ctx context.Context, by, id, name string) error

RenameAgent sets the name shown for an enrolled agent.

func (*Hub) RevokeAgent

func (h *Hub) RevokeAgent(ctx context.Context, by, id string) error

RevokeAgent stops an agent from connecting again and drops its connection.

func (*Hub) RolloutStatus

func (h *Hub) RolloutStatus(_ context.Context) (RolloutStatus, error)

RolloutStatus reports the servers that could be updated and what the last rollout did.

func (*Hub) SendCommand

func (h *Hub) SendCommand(ctx context.Context, agentID string, cmd *agentv1.Command) (*agentv1.CommandResult, error)

SendCommand sends a command to a connected agent and waits for its result. It returns ErrAgentNotFound if the agent is not connected and ErrAgentDisconnected if it goes away before answering.

func (*Hub) Serve

func (h *Hub) Serve(ctx context.Context, ln net.Listener) error

Serve serves HTTP on ln until ctx is cancelled, then shuts down gracefully.

func (*Hub) SetLayout

func (h *Hub) SetLayout(ctx context.Context, userID int64, layout json.RawMessage) error

SetLayout implements LayoutStore.

func (*Hub) StaleAfter

func (h *Hub) StaleAfter() time.Duration

StaleAfter is how long an agent may stay silent before it is reported as stale.

func (*Hub) StartRollout

func (h *Hub) StartRollout(ctx context.Context, by string, opts RolloutOptions) (RolloutStatus, error)

StartRollout updates the connected servers that run an older version to the version of this hub, a wave at a time, and stops at the first failure.

func (*Hub) StopRollout

func (h *Hub) StopRollout(ctx context.Context, by string) (RolloutStatus, error)

StopRollout lets the servers already handed an update finish and offers it to no other.

func (*Hub) Subscribe

func (h *Hub) Subscribe() (<-chan []byte, func())

Subscribe returns a channel of the JSON events pushed on GET /api/stream and a function that ends the subscription.

func (*Hub) Updates

func (h *Hub) Updates() *update.Manager

Updates returns the manager that looks for new releases.

type LayoutStore

type LayoutStore interface {
	// Layout returns the stored layout; found is false if the user has none.
	Layout(ctx context.Context, userID int64) (layout json.RawMessage, updatedAt time.Time, found bool, err error)
	SetLayout(ctx context.Context, userID int64, layout json.RawMessage) error
}

LayoutStore keeps the dashboard layout of each user. *Hub implements it.

type MetricsSample

type MetricsSample struct {
	At     time.Time
	Report *agentv1.MetricsReport
}

MetricsSample is one metrics report with the time the hub received it.

type Option

type Option func(*Hub)

Option customizes a Hub.

func WithAgentIntervals

func WithAgentIntervals(heartbeat, metrics time.Duration) Option

WithAgentIntervals sets the heartbeat and metrics intervals the hub asks agents to use. Mostly useful in tests.

func WithUpdateSource

func WithUpdateSource(src update.Source) Option

WithUpdateSource replaces the place the hub looks for new releases. Mostly useful in tests.

func WithUpdateVerifier

func WithUpdateVerifier(v update.Verifier) Option

WithUpdateVerifier replaces the check of the signature of downloaded releases. Only for tests: a hub without it never trusts an unsigned release.

func WithVersion

func WithVersion(version string) Option

WithVersion sets the release version of the hub, reported by the built-in agent.

type Rejection

type Rejection struct {
	At      time.Time
	Version string
	Message string
}

Rejection is the last time the hub refused an enrolled agent because its version cannot work with this hub. The dashboard shows it until the agent connects.

type RolloutAgent

type RolloutAgent struct {
	ID       string `json:"id"`
	Hostname string `json:"hostname,omitempty"`
	From     string `json:"from"`
	Status   string `json:"status"`
	Message  string `json:"message,omitempty"`
}

RolloutAgent is a server in a rollout, or one that could be in the next.

type RolloutControl

type RolloutControl interface {
	RolloutStatus(ctx context.Context) (RolloutStatus, error)
	StartRollout(ctx context.Context, by string, opts RolloutOptions) (RolloutStatus, error)
	StopRollout(ctx context.Context, by string) (RolloutStatus, error)
}

RolloutControl is what the HTTP API needs to update the servers. *Hub implements it.

type RolloutOptions

type RolloutOptions struct {
	// Agents limits the rollout to these servers; empty means every candidate.
	Agents []string `json:"agents"`
	// WaveSize is how many servers are updated at the same time (default 1).
	// The rollout stops at the first failure.
	WaveSize int `json:"wave_size"`
}

RolloutOptions say what a rollout does.

type RolloutStatus

type RolloutStatus struct {
	// Target is the version the servers are updated to: the one this hub runs.
	Target string `json:"target"`
	State  string `json:"state"`
	// Candidates are the connected servers that run an older version and would
	// be updated by a new rollout.
	Candidates []RolloutAgent `json:"candidates"`
	// Agents is what the last rollout did.
	Agents     []RolloutAgent `json:"agents"`
	WaveSize   int            `json:"wave_size,omitempty"`
	StartedBy  string         `json:"started_by,omitempty"`
	StartedAt  *time.Time     `json:"started_at"`
	FinishedAt *time.Time     `json:"finished_at"`
}

RolloutStatus is the state of updating the servers to the version of this hub.

type UpdateControl

type UpdateControl interface {
	Status(ctx context.Context) (update.Status, error)
	SetSettings(ctx context.Context, by string, s update.Settings) (update.Status, error)
	Check(ctx context.Context) (update.Status, error)
	Download(ctx context.Context, by string) (update.Status, error)
	Apply(ctx context.Context, by string) (update.Status, error)
}

UpdateControl is what the HTTP API needs to report and steer the updates of the hub. *update.Manager implements it.

Jump to

Keyboard shortcuts

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