Heartbeat Service
The internal/agent package implements the HeartbeatService, which sends periodic heartbeat requests to the control plane and processes directive flags from the response.
Config
| Field | Type | Default | Description |
|---|---|---|---|
Interval | time.Duration | 30s | Heartbeat send interval |
NodeID | string | — | Node identifier (required) |
Heartbeat Loop
HeartbeatService.Run(ctx) operates as follows:
- Send one heartbeat immediately on start
- Start a ticker at the configured interval (default 30s)
- On each tick, build and send a
HeartbeatRequest - Process the
HeartbeatResponsedirective flags - Continue until context is cancelled
Run() always returns nil.
Request Payload
The heartbeat request is built by an optional buildRequest function. If not set, a zero-valued HeartbeatRequest is sent. The builder typically collects runtime state:
type HeartbeatRequest struct {
NodeID string `json:"node_id"`
Timestamp time.Time `json:"timestamp"`
Status string `json:"status"`
Uptime string `json:"uptime"`
BinaryChecksum string `json:"binary_checksum"`
Mesh *MeshInfo `json:"mesh,omitempty"`
NAT *NATInfo `json:"nat,omitempty"`
Bridge *BridgeInfo `json:"bridge,omitempty"`
UserAccess *UserAccessInfo `json:"user_access,omitempty"`
Ingress *IngressInfo `json:"ingress,omitempty"`
SiteToSite *SiteToSiteInfo `json:"site_to_site,omitempty"`
}Response Handling
The control plane returns a HeartbeatResponse with directive flags:
type HeartbeatResponse struct {
Reconcile bool `json:"reconcile"`
RotateKeys bool `json:"rotate_keys"`
}| Flag | Action |
|---|---|
reconcile | Call ReconcileTrigger.TriggerReconcile() |
rotate_keys | Call the onRotateKeys callback |
Error Handling
401 Unauthorized
When the heartbeat receives a 401 error (api.ErrUnauthorized), the onAuthFailure callback is invoked. In production (plexd up), this triggers re-registration:
- Call
registrar.Register(ctx)to obtain a new identity - Update the control plane client auth token via
client.SetAuthToken() - Log the re-registration result
If re-registration fails, the error is logged and the heartbeat continues retrying on the next tick.
Other Errors
Non-401 errors are logged at error level. The heartbeat loop continues on the next tick interval.
Callbacks
| Method | Signature | Description |
|---|---|---|
SetReconcileTrigger | ReconcileTrigger | Reconciler to trigger on reconcile=true |
SetOnAuthFailure | func() | Called on 401 Unauthorized |
SetOnRotateKeys | func() | Called on rotate_keys=true |
SetBuildRequest | func() HeartbeatRequest | Custom request builder |
Integration Wiring
In plexd up, the heartbeat service is wired as follows:
HeartbeatService
├── client: ControlPlane (sends heartbeat RPCs)
├── reconcileTrigger: Reconciler (triggers state reconciliation)
├── onAuthFailure: re-registers → updates auth token
└── onRotateKeys: triggers reconcile (fetches new signing keys)Interfaces
type HeartbeatClient interface {
Heartbeat(ctx context.Context, nodeID string, req HeartbeatRequest) (*HeartbeatResponse, error)
}
type ReconcileTrigger interface {
TriggerReconcile()
}Both interfaces are small and testable. The HeartbeatClient is satisfied by *api.ControlPlane, and ReconcileTrigger is satisfied by *reconcile.Reconciler.