agent

package
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Sep 17, 2026 License: MIT Imports: 30 Imported by: 0

Documentation

Overview

Package agent is the device side of cloop's remote executor: the process that runs on an edge device, dials out to a control plane, and executes the workloads it is sent.

It exists because the interesting devices are unreachable. A build box in an office, a Jetson on a factory floor, a laptop on hotel wifi — all can make outbound HTTPS and none can accept an inbound connection. So the agent inverts the usual direction: it dials the control plane, holds one multiplexed WebSocket open, and receives work over it.

Three properties shape the implementation:

  • Reconnection is normal. Networks on edge devices drop constantly, so the agent reconnects with capped exponential backoff plus jitter and resumes log streaming from the offset the control plane actually received.
  • Memory is finite. A device with 512 MiB of RAM cannot buffer a chatty harness's output while the link is down, so retention is bounded and overflow is reported rather than silently absorbed.
  • The control plane is not trusted with the filesystem. Every workdir is confined beneath a configured root, enforced on the device, because the device is the only party that knows its own filesystem.

Index

Constants

View Source
const CredentialFileMode os.FileMode = 0o600

CredentialFileMode is the required permission for the credential file.

View Source
const DefaultRetainBytes = 1 << 20

DefaultRetainBytes is how much unacknowledged output one workload may hold. 1 MiB comfortably covers a multi-second reconnect for a chatty harness while staying negligible against even a small device's memory.

Variables

View Source
var AgentVersion = version.String()

AgentVersion identifies this agent build to the control plane, so version skew across a fleet is visible from the Executors panel rather than being inferred from behaviour.

It was a hardcoded "1" for as long as this file existed, and was never once incremented — so the field meant to diagnose version skew was itself the reason skew could not be diagnosed. Every device in every fleet reported the same value, whatever it was actually running.

The real build version lives in pkg/version, below both this package and the CLI, which is what makes it askable from here at all. A var rather than a const so a test can stage a specific build without rebuilding, and because its value is no longer a compile-time constant.

Functions

func CheckCredentialPermissions

func CheckCredentialPermissions(path string) string

CheckCredentialPermissions reports a warning when the credential file is readable by anyone but its owner. Returns "" when the permissions are fine.

func DefaultCredentialPath

func DefaultCredentialPath() (string, error)

DefaultCredentialPath returns ~/.cloop/agent.json.

It is under the user's home rather than the project's .cloop directory because the agent's identity belongs to the *device*, not to any project: a device runs workloads for many projects, and several of them may not exist on it yet.

func Detect

Detect gathers this device's capabilities.

func HasContainerRuntime

func HasContainerRuntime(caps remote.AgentCapabilities) bool

HasContainerRuntime reports whether caps advertises any container runtime.

func HasHarness

func HasHarness(caps remote.AgentCapabilities, name string) bool

HasHarness reports whether caps advertises a given harness, for schedulers matching a project's provider against a device.

func SaveCredential

func SaveCredential(path string, c Credential) error

SaveCredential writes the credential atomically with 0600 permissions.

Atomic because a device that loses power mid-write must not come back with a half-written identity it can neither use nor diagnose — the rename either happened or it did not.

The temp file is created with 0600 from the outset rather than chmod'ed afterwards: a create-then-chmod sequence leaves a window in which the secret exists on disk world-readable, and on a shared device that window is enough.

Types

type Agent

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

Agent is the device-side executor client.

func New

func New(cfg Config) (*Agent, error)

New builds an agent. It does not connect; call Run.

func (*Agent) AgentID

func (a *Agent) AgentID() string

AgentID returns the enrolled identity, or "" before enrollment.

func (*Agent) Capabilities

func (a *Agent) Capabilities() remote.AgentCapabilities

Capabilities reports what this device advertises. MaxConcurrent is normalised in New, so this is exactly the ceiling admission control applies.

func (*Agent) Run

func (a *Agent) Run(ctx context.Context) error

Run connects and serves until ctx is cancelled or the control plane refuses this agent permanently.

func (*Agent) TransportSummary

func (a *Agent) TransportSummary() string

TransportSummary describes the agent's transport for startup output.

func (*Agent) WorkDirRoot

func (a *Agent) WorkDirRoot() string

WorkDirRoot returns the confinement root.

type Config

type Config struct {
	// Server is the control-plane WebSocket URL,
	// e.g. wss://cloop.example.com/api/executors/connect.
	Server string
	// Token is an enrollment token, used only until a credential is issued.
	Token string
	// TokenFile is the file Token was read from, when it came from one.
	//
	// The agent deletes it once the token has been redeemed. An enrollment
	// token is single-use, so after redemption the file holds a dead secret
	// that still looks live: it would be carried into every backup and disk
	// image of the device, and an operator finding it later cannot tell
	// whether it is spent. Removing it makes the credential's lifetime match
	// its usefulness. Set by `cloop executor agent --token-file`, which is
	// how the installed service receives its token without it appearing in
	// ExecStart.
	TokenFile string
	// CredentialPath is where the long-lived credential is persisted.
	// Empty uses DefaultCredentialPath().
	CredentialPath string
	// WorkDirRoot confines every workload. Empty uses ~/.cloop/work.
	WorkDirRoot string
	// MaxConcurrent bounds simultaneous workloads. Zero means NumCPU.
	MaxConcurrent int
	// Labels are operator-supplied scheduler selectors.
	Labels map[string]string
	// RetainBytes overrides DefaultRetainBytes per workload.
	RetainBytes int
	// Pin is the control plane's expected SPKI fingerprint,
	// "sha256:<base64>". Several may be given comma-separated so a key
	// rotation can be staged. Empty means ordinary PKI verification only.
	// Taken from the stored credential when the flag is not passed.
	Pin string
	// RootCAFile adds a PEM bundle to the trusted roots, for a control plane
	// whose certificate the system store does not know — a private CA, or a
	// `cloop hub tls-init` development certificate. This is the supported way
	// to reach such a server; there is deliberately no way to disable
	// verification instead.
	RootCAFile string
	// InsecureTransport permits plaintext ws:// to a non-loopback host.
	// It exists for links already protected some other way (an established
	// mTLS tunnel, a lab with no network). Every connection attempt logs a
	// warning while it is on.
	InsecureTransport bool
	// Logf receives operational messages. Nil discards them.
	Logf func(format string, args ...any)
	// Now overrides the clock for tests.
	Now func() time.Time
	// Dial overrides connection establishment, so tests can drive the agent
	// over an in-memory pipe instead of a real socket.
	Dial func(ctx context.Context, server, token string) (remote.Conn, error)
}

Config parameterises an agent.

type Credential

type Credential struct {
	// Server is the control-plane WebSocket URL this credential is valid for.
	// Stored alongside the secret so `cloop executor agent` can be re-run with
	// no flags at all once enrolled.
	Server string `json:"server"`
	// AgentID is the control plane's identifier for this device. It is also
	// the executor ID projects bind to.
	AgentID string `json:"agent_id"`
	// Name is the operator-facing label chosen at enrollment.
	Name string `json:"name,omitempty"`
	// Credential is the long-lived secret. The control plane stores only its
	// hash, so this file is the only copy in existence.
	Credential string `json:"credential"`
	// WorkDirRoot is the filesystem root every workload is confined beneath.
	WorkDirRoot string `json:"workdir_root,omitempty"`
	// Pin is the control plane's SPKI fingerprint ("sha256:<base64>"), or
	// "" when the device was enrolled without one.
	//
	// It is persisted alongside the secret because pinning that only applied
	// to the enrollment connection would protect the least valuable moment
	// and leave every subsequent one — thousands of reconnects carrying a
	// long-lived credential, over years, from a device nobody visits —
	// trusting whatever answers DNS. Storing it makes the pin a property of
	// the device's identity rather than of one command line.
	Pin string `json:"pin,omitempty"`
	// EnrolledAt records when the credential was issued.
	EnrolledAt time.Time `json:"enrolled_at"`
}

Credential is the durable identity the agent authenticates with.

func LoadCredential

func LoadCredential(path string) (Credential, error)

LoadCredential reads the credential file.

A missing file is reported as os.ErrNotExist so callers can distinguish "not enrolled yet" (prompt for a token) from "enrolled but broken" (a real error the operator must fix).

func (Credential) Valid

func (c Credential) Valid() bool

Valid reports whether the credential has the fields needed to connect.

type DetectOptions

type DetectOptions struct {
	// WorkDirRoot is the confinement root to advertise.
	WorkDirRoot string
	// MaxConcurrent is the agent's workload ceiling.
	MaxConcurrent int
	// Labels are operator-supplied scheduler selectors.
	Labels map[string]string
	// LookPath overrides exec.LookPath for tests.
	LookPath func(string) (string, error)
	// MemoryMB overrides memory detection for tests; -1 forces "unknown".
	MemoryMB int
}

DetectOptions tunes capability detection.

Jump to

Keyboard shortcuts

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