agent

package
v0.4.0 Latest Latest
Warning

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

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

Documentation

Overview

Package agent implements agent-level runtime services.

Index

Constants

View Source
const (
	// DefaultMode is the default operating mode.
	DefaultMode = "node"

	// DefaultLogLevel is the default log level.
	DefaultLogLevel = "info"

	// DefaultDataDir is the default data directory.
	DefaultDataDir = "/var/lib/plexd"
)
View Source
const DefaultHeartbeatInterval = 30 * time.Second

DefaultHeartbeatInterval is the default heartbeat interval.

Variables

This section is empty.

Functions

This section is empty.

Types

type AgentConfig

type AgentConfig struct {
	// Mode is the operating mode: "node" or "bridge".
	// Default: "node"
	Mode string `yaml:"mode"`

	// LogLevel is the log level: "debug", "info", "warn", "error".
	// Default: "info"
	LogLevel string `yaml:"log_level"`

	// DataDir is the directory for persistent agent data.
	// Default: /var/lib/plexd
	DataDir string `yaml:"data_dir"`

	API          api.Config          `yaml:"api"`
	Registration registration.Config `yaml:"registration"`
	Reconcile    reconcile.Config    `yaml:"reconcile"`
	NodeAPI      nodeapi.Config      `yaml:"node_api"`
	Health       health.Config       `yaml:"health"`
	Actions      actions.Config      `yaml:"actions"`
	Policy       policy.Config       `yaml:"policy"`
	WireGuard    wireguard.Config    `yaml:"wireguard"`
	Metrics      metrics.Config      `yaml:"metrics"`
	LogFwd       logfwd.Config       `yaml:"log_fwd"`
	AuditFwd     auditfwd.Config     `yaml:"audit_fwd"`
	Integrity    integrity.Config    `yaml:"integrity"`
	Upgrade      upgrade.Config      `yaml:"upgrade"`
	Tunnel       tunnel.Config       `yaml:"tunnel"`
	NAT          nat.Config          `yaml:"nat"`
	PeerExchange peerexchange.Config `yaml:"peer_exchange"`
	Bridge       bridge.Config       `yaml:"bridge"`
	Heartbeat    HeartbeatConfig     `yaml:"heartbeat"`
}

AgentConfig is the top-level configuration for the plexd agent. It aggregates all subsystem configurations and is populated from a YAML configuration file via ParseConfig.

func ParseConfig

func ParseConfig(path string) (*AgentConfig, bool, error)

ParseConfig reads a YAML configuration file and returns an AgentConfig with defaults applied. An absent file is not an error: it is treated as an empty config, and the returned bool reports whether the file was found. A file that exists but cannot be read, that is empty, or that does not parse, is an error. Validating the result is the caller's responsibility, once the CLI flag and environment overrides have been merged in.

func (*AgentConfig) ApplyDefaults

func (c *AgentConfig) ApplyDefaults()

ApplyDefaults sets default values for zero-valued fields.

func (*AgentConfig) Validate

func (c *AgentConfig) Validate() error

Validate checks that required fields are set and values are acceptable.

type DeviceKeyUpdater added in v0.2.0

type DeviceKeyUpdater interface {
	UpdatePrivateKey(privateKey []byte) error
}

DeviceKeyUpdater installs a rotated private key on the WireGuard device.

type HeartbeatClient

type HeartbeatClient interface {
	Heartbeat(ctx context.Context, nodeID string, req api.HeartbeatRequest) (*api.HeartbeatResponse, error)
}

HeartbeatClient sends heartbeat requests to the control plane.

type HeartbeatConfig

type HeartbeatConfig struct {
	// Interval is the heartbeat send interval.
	// Default: 30s
	Interval time.Duration `yaml:"interval"`

	// NodeID is the node identifier (required).
	NodeID string `yaml:"node_id"`
}

HeartbeatConfig holds the configuration for the heartbeat service.

func (*HeartbeatConfig) ApplyDefaults

func (c *HeartbeatConfig) ApplyDefaults()

ApplyDefaults sets default values for zero-valued fields.

func (*HeartbeatConfig) Validate

func (c *HeartbeatConfig) Validate() error

Validate checks that required fields are set.

type HeartbeatService

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

HeartbeatService sends periodic heartbeats to the control plane and dispatches directive flags from the response.

func NewHeartbeatService

func NewHeartbeatService(cfg HeartbeatConfig, client HeartbeatClient, logger *slog.Logger) *HeartbeatService

NewHeartbeatService creates a new HeartbeatService with the given configuration and client. The config is validated; defaults are applied for any zero-valued optional fields.

func (*HeartbeatService) Run

func (s *HeartbeatService) Run(ctx context.Context) error

Run starts the heartbeat loop. It sends one heartbeat immediately and then continues at the configured interval until ctx is cancelled. Run always returns nil.

func (*HeartbeatService) SetBuildRequest

func (s *HeartbeatService) SetBuildRequest(fn func() api.HeartbeatRequest)

SetBuildRequest sets a custom heartbeat request builder. If not set, the service sends a zero-valued HeartbeatRequest.

func (*HeartbeatService) SetOnAuthFailure

func (s *HeartbeatService) SetOnAuthFailure(fn func())

SetOnAuthFailure sets a callback invoked when a heartbeat fails with a 401 Unauthorized error.

func (*HeartbeatService) SetOnRotateKeys

func (s *HeartbeatService) SetOnRotateKeys(fn func())

SetOnRotateKeys sets a callback invoked when the control plane signals that keys should be rotated.

func (*HeartbeatService) SetReconcileTrigger

func (s *HeartbeatService) SetReconcileTrigger(rt ReconcileTrigger)

SetReconcileTrigger sets the reconcile trigger invoked when the control plane requests reconciliation.

type KeyRotator added in v0.2.0

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

KeyRotator runs the mesh-key rotation state machine. Both rotation signals drive the same single-flight cycle: the repeated heartbeat rotate_keys flag through Rotate, which rate-limits fresh starts, and the one-shot SSE rotate_keys event through RotateNow, which does not. The control plane keeps a rotation pending until the node confirms it, so repeated signals, retries, and the RecoverPending sweep all converge on the same crash-safe outcome.

func NewKeyRotator added in v0.2.0

func NewKeyRotator(client RotateClient, id *registration.NodeIdentity, dataDir string, device DeviceKeyUpdater, rt ReconcileTrigger, logger *slog.Logger) *KeyRotator

NewKeyRotator builds a KeyRotator that stages fresh keys under dataDir, submits their public half via client, installs the confirmed private key on device (may be nil), and triggers a reconcile through rt once a rotation commits.

func (*KeyRotator) RecoverPending added in v0.2.0

func (r *KeyRotator) RecoverPending(ctx context.Context)

RecoverPending drives staged-but-uncommitted rotations for the lifetime of ctx. A staged key that the control plane already accepted strands the node unless something resubmits it: the rotation completed server-side, so no further signal arrives, every peer holds the new public key, and the node still runs the old one. That state outlives startup — a submit whose response is lost (proxy 5xx, read timeout, reset connection) leaves exactly the same staged key at any point during the process lifetime — so this loop keeps sweeping instead of returning once nothing is staged.

While a key is staged it resubmits with a capped exponential backoff; with nothing staged it re-checks at the cap and makes no HTTP call, so an idle node never starts a rotation on its own.

func (*KeyRotator) Rotate added in v0.2.0

func (r *KeyRotator) Rotate(ctx context.Context) error

Rotate runs one single-flight rotation cycle. It stages a fresh key (or reuses one staged by an earlier attempt), submits its public half, and commits the durable identity only after the control plane confirms. A concurrent Rotate that loses the TryLock returns nil without touching the staging file, and starting a fresh rotation within minInterval of the last commit is skipped; a key already staged is always resubmitted. The outcome taxonomy over the submit result is:

  • success with a complete receipt: commit with the returned receipt;
  • success without a receipt: a control plane that predates this contract, so keep the staged key for a resubmit and return the error;
  • 422 keys_rotate_public_key_unchanged: a previous submission already landed (a crash between submit and persist), so commit with a nil receipt to keep the existing LastRotation;
  • 409 keys_rotate_no_pending_rotation: a stale or cancelled signal whose staged key never landed, so discard it and return nil;
  • a permanent rejection (400, 403, 413, 404 keys_rotate_peer_not_found, 422 keys_rotate_public_key_invalid): no retry changes the outcome, so discard the staged key and return the error;
  • anything else (auth, 5xx, transport errors): keep the staged key so RecoverPending or the next signal retries, and return the error.

func (*KeyRotator) RotateNow added in v0.2.0

func (r *KeyRotator) RotateNow(ctx context.Context) error

RotateNow runs the same rotation cycle as Rotate but starts a fresh rotation even within minInterval of the last commit. It serves the SSE rotate_keys event: the control plane sends it once per rotation decision and never repeats it, so a cooldown skip here would strand the rotation until the heartbeat flag catches up — or forever, for a control plane that signals via SSE alone. A redelivered event is harmless: with no rotation pending the submit is answered 409 keys_rotate_no_pending_rotation and the staged key is discarded without touching the device.

type ReconcileTrigger

type ReconcileTrigger interface {
	TriggerReconcile()
}

ReconcileTrigger triggers an immediate reconciliation.

type RotateClient added in v0.2.0

type RotateClient interface {
	RotateKeys(ctx context.Context, req api.KeyRotateRequest) (*api.KeyRotateResponse, error)
}

RotateClient submits a completed rotation's public key to the control plane.

Jump to

Keyboard shortcuts

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