daemon

package
v0.8.0 Latest Latest
Warning

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

Go to latest
Published: Oct 3, 2026 License: MIT Imports: 37 Imported by: 0

Documentation

Overview

Package daemon implements the proxy daemon's management API.

Backwards Compatibility

The daemon is a long-lived process that may run a different binary version than the CLI or the moat-run process. This happens during development (rebuilding moat while runs are active) and during upgrades.

To keep old and new versions interoperable, the daemon API follows these rules:

  • Additive only: new fields in request/response structs are fine (encoding/json ignores unknown fields). Never remove or rename fields.
  • New endpoints are fine: old clients won't call them. New clients must handle 404 gracefully when talking to an older daemon.
  • Never change the semantics of existing fields.

When adding new API surface, consider: "will a CLI built today still work if the daemon is an older binary?" and vice versa.

Package daemon implements the proxy daemon for multi-run credential injection.

Index

Constants

View Source
const (
	CapKeepPolicy        = "keep-policy"
	CapKeepBodyPolicy    = "keep-body-policy"
	CapHostGatewayV2     = "host-gateway-v2"
	CapCredentialRefs    = "credential-refs-v1"
	CapCredentialBundles = "credential-bundles-v1"
	CapSerialDevices     = "serial-devices"
)

Daemon capability identifiers advertised in HealthResponse.Capabilities and checked by the CLI before registering a run. These strings are part of the cross-version contract (see Backwards Compatibility above): the daemon advertises them and the CLI checks them, so both sides must use the same literal — keep them as shared constants to prevent an advertise/check typo from silently disabling a capability gate. Add, never rename or remove.

View Source
const DefaultProxyPort = 19080

DefaultProxyPort is the default port for the daemon's credential-injecting proxy. This is intentionally different from the routing proxy port (8080) to avoid conflicts on macOS where both 0.0.0.0:PORT and 127.0.0.1:PORT can coexist and Docker traffic hits the wrong listener.

View Source
const WorkspaceVolumePrefix = "moat-ws-"

WorkspaceVolumePrefix is the prefix moat uses for per-run workspace volume names (the name is WorkspaceVolumePrefix + runID). It is the single source of truth for the naming convention: run.WorkspaceVolumeName builds names from it, and the GC here strips it to recover run IDs. It lives in this package because internal/run imports internal/daemon (not the reverse), so the shared constant cannot live in run without an import cycle.

Variables

View Source
var BuildCommit string

BuildCommit is the git commit hash of the running binary. Set by the CLI at startup so the health endpoint can report the daemon's version. This allows diagnosing version skew between daemon and CLI.

View Source
var ErrRunNotFound = errors.New("run not found")

ErrRunNotFound is returned when a run is not registered with the daemon.

Functions

func GCOrphanWorkspaceVolumes added in v0.7.0

func GCOrphanWorkspaceVolumes(ctx context.Context)

GCOrphanWorkspaceVolumes reclaims moat-ws-<run-id> Docker named volumes whose run has been destroyed (its run directory no longer exists). A volume is normally removed on `moat destroy`, but a crashed run or SIGKILL leaks it. This pass enumerates moat-ws-* volumes and removes those whose run id has no corresponding run directory.

It is intentionally defensive: any failure (no Docker, Apple runtime, list error, run-dir enumeration error) logs at debug/warn and returns without disturbing the daemon. The caller can safely invoke it during idle cleanup.

func RaiseFileLimit added in v0.7.0

func RaiseFileLimit() (oldSoft, newSoft uint64, err error)

RaiseFileLimit raises this process's RLIMIT_NOFILE soft limit toward desiredNofile, capped at the hard limit. It returns the previous soft limit and the soft limit in effect afterward (always >= oldSoft). It is a no-op (equal values) when the soft limit is already at or above the target.

A limit that cannot be raised is not an error — the process keeps its existing, already-in-effect limit and err stays nil. err is non-nil only when the current limit cannot be read. Some systems, notably macOS, reject a soft limit above kern.maxfilesperproc even when the hard limit is nominally higher; when the full target is rejected, the fallback binary-searches the highest value the system will accept between the old soft limit and the target.

func RemoveLockFile

func RemoveLockFile(dir string)

RemoveLockFile removes the daemon lock file.

func RestoreRuns added in v0.3.2

func RestoreRuns(ctx context.Context, registry *Registry, runs []PersistedRun) int

RestoreRuns re-registers persisted runs by re-resolving credentials from the encrypted credential store. Returns the number of successfully restored runs.

func RestoreRunsWithSerial added in v0.8.0

func RestoreRunsWithSerial(ctx context.Context, registry *Registry, runs []PersistedRun, listen serialRestoreListener) int

RestoreRunsWithSerial is RestoreRuns with serial-device support: a restored run's listeners are re-opened on the ports its container already holds, via the supplied listener. A run whose port is now occupied fails loudly and is skipped — not silently rebound elsewhere, where the container could not reach it.

func StartTokenRefresh

func StartTokenRefresh(ctx context.Context, rc *RunContext, grants []string)

StartTokenRefresh begins a background goroutine that periodically refreshes credentials for the given run context.

func WriteLockFile

func WriteLockFile(dir string, info LockInfo) error

WriteLockFile writes the daemon lock file.

Types

type AWSConfig

type AWSConfig struct {
	RoleARN         string        `json:"role_arn"`
	Region          string        `json:"region"`
	SessionDuration time.Duration `json:"session_duration"`
	ExternalID      string        `json:"external_id,omitempty"`
	Profile         string        `json:"profile,omitempty"`
}

AWSConfig holds AWS credential provider configuration.

type Client

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

Client communicates with the daemon over a Unix socket.

func EnsureRunning

func EnsureRunning(dir string, proxyPort int) (*Client, error)

EnsureRunning checks if the daemon is already running and returns a client. If not running, it starts the daemon via self-exec and waits for it to be ready.

An advisory file lock serializes the check-and-spawn sequence so concurrent callers don't each spawn a separate daemon process.

func NewClient

func NewClient(sockPath string) *Client

NewClient creates a daemon client connected to the given socket path.

func Restart added in v0.6.0

func Restart(dir string, proxyPort int) (*Client, bool, error)

Restart atomically replaces the running daemon with a fresh one spawned from the current binary. It holds the daemon spawn lock across the entire stop→start sequence so concurrent EnsureRunning callers (e.g. health monitors) block until the new daemon is up, then observe it healthy and do nothing — closing the window where a stopped daemon could be resurrected by an old binary.

The returned bool reports whether an existing daemon was stopped (true) or nothing was running and a fresh daemon was simply started (false).

func (*Client) Health

func (c *Client) Health(ctx context.Context) (*HealthResponse, error)

Health returns the daemon's health status.

func (*Client) ListRuns

func (c *Client) ListRuns(ctx context.Context) ([]RunInfo, error)

ListRuns returns all registered runs.

func (*Client) RegisterRoutes

func (c *Client) RegisterRoutes(ctx context.Context, agent string, services map[string]string) error

RegisterRoutes registers service routes for an agent.

func (*Client) RegisterRun

func (c *Client) RegisterRun(ctx context.Context, regReq RegisterRequest) (*RegisterResponse, error)

RegisterRun registers a new run with the daemon.

func (*Client) Shutdown

func (c *Client) Shutdown(ctx context.Context) error

Shutdown requests the daemon to shut down gracefully.

func (*Client) UnregisterRoutes

func (c *Client) UnregisterRoutes(ctx context.Context, agent string) error

UnregisterRoutes removes service routes for an agent.

func (*Client) UnregisterRun

func (c *Client) UnregisterRun(ctx context.Context, token string) error

UnregisterRun removes a run from the daemon.

func (*Client) UpdateRun

func (c *Client) UpdateRun(ctx context.Context, token, containerID string) error

UpdateRun updates a run's container ID (phase 2 of registration).

type CommandContainerChecker

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

CommandContainerChecker checks container liveness by shelling out to the container runtime CLI. It tries Docker first, then Apple containers.

func NewCommandContainerChecker

func NewCommandContainerChecker() *CommandContainerChecker

NewCommandContainerChecker creates a new container checker that uses CLI commands.

func (*CommandContainerChecker) ForgetContainer added in v0.4.0

func (c *CommandContainerChecker) ForgetContainer(id string)

ForgetContainer removes the cached runtime for a container that no longer exists.

func (*CommandContainerChecker) IsContainerRunning

func (c *CommandContainerChecker) IsContainerRunning(ctx context.Context, id string) (bool, error)

IsContainerRunning checks if a container is still running. Returns (true, nil) when the container is confirmed alive, (false, nil) when confirmed dead, and (false, err) when the check itself failed (e.g. transient CLI error).

type ContainerChecker

type ContainerChecker interface {
	IsContainerRunning(ctx context.Context, id string) (alive bool, err error)
}

ContainerChecker checks if a container is still running. Returns (true, nil) when confirmed alive, (false, nil) when confirmed dead, and (false, err) when the check failed (transient error).

type CredentialEntry

type CredentialEntry struct {
	Name  string `json:"name"`  // Header name (e.g., "Authorization", "x-api-key")
	Value string `json:"value"` // Header value
	Grant string `json:"grant"` // Grant name for logging
}

CredentialEntry holds a credential header for proxy injection.

type CredentialSpec

type CredentialSpec struct {
	Host   string `json:"host"`
	Header string `json:"header"`
	Value  string `json:"value"`
	Grant  string `json:"grant,omitempty"`
}

CredentialSpec describes a credential to inject for a host.

type ExtraHeaderEntry

type ExtraHeaderEntry struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

ExtraHeaderEntry holds an additional header to inject.

type ExtraHeaderSpec

type ExtraHeaderSpec struct {
	Host       string `json:"host"`
	HeaderName string `json:"header_name"`
	Value      string `json:"value"`
}

ExtraHeaderSpec describes an additional header to inject.

type HealthResponse

type HealthResponse struct {
	PID          int      `json:"pid"`
	ProxyPort    int      `json:"proxy_port"`
	RunCount     int      `json:"run_count"`
	StartedAt    string   `json:"started_at"`
	Commit       string   `json:"commit,omitempty"`       // Git commit hash of the daemon binary
	Capabilities []string `json:"capabilities,omitempty"` // Feature capabilities supported by this daemon
}

HealthResponse is returned from GET /v1/health.

type IdleTimer

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

IdleTimer triggers a callback after a period of inactivity.

func NewIdleTimer

func NewIdleTimer(duration time.Duration, callback func()) *IdleTimer

NewIdleTimer creates an idle timer (does not start automatically).

func (*IdleTimer) Cancel

func (t *IdleTimer) Cancel()

Cancel stops the timer without firing.

func (*IdleTimer) Reset

func (t *IdleTimer) Reset()

Reset restarts the idle countdown.

type LivenessChecker

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

LivenessChecker periodically checks container liveness and cleans up dead runs.

func NewLivenessChecker

func NewLivenessChecker(registry *Registry, checker ContainerChecker) *LivenessChecker

NewLivenessChecker creates a new liveness checker with 30-second default interval.

func (*LivenessChecker) CheckOnce

func (lc *LivenessChecker) CheckOnce(ctx context.Context)

CheckOnce performs a single liveness check for all registered runs.

func (*LivenessChecker) Run

func (lc *LivenessChecker) Run(ctx context.Context)

Run starts the periodic liveness check loop. Blocks until ctx is canceled.

func (*LivenessChecker) SetOnCleanup

func (lc *LivenessChecker) SetOnCleanup(fn func(token, runID string))

SetOnCleanup sets a callback invoked when a run is cleaned up. The callback receives both the auth token and run ID.

func (*LivenessChecker) SetOnEmpty

func (lc *LivenessChecker) SetOnEmpty(fn func())

SetOnEmpty sets a callback invoked when the registry becomes empty after cleanup.

func (*LivenessChecker) SetPersister added in v0.3.2

func (lc *LivenessChecker) SetPersister(p *RunPersister)

SetPersister sets the run persister for saving registry state after cleanup.

type LockInfo

type LockInfo struct {
	PID       int       `json:"pid"`
	ProxyPort int       `json:"proxy_port"`
	SockPath  string    `json:"sock_path"`
	StartedAt time.Time `json:"started_at"`
	Commit    string    `json:"commit,omitempty"` // Git commit hash of the daemon binary
}

LockInfo holds information about a running daemon.

func ReadLockFile

func ReadLockFile(dir string) (*LockInfo, error)

ReadLockFile reads the daemon lock file. Returns nil, nil if not found.

func (*LockInfo) IsAlive

func (l *LockInfo) IsAlive() bool

IsAlive checks if the daemon process is still running.

type PersistedRun added in v0.3.2

type PersistedRun struct {
	AuthToken         string                   `json:"auth_token"`
	RunID             string                   `json:"run_id"`
	ContainerID       string                   `json:"container_id,omitempty"`
	Grants            []string                 `json:"grants,omitempty"`
	MCPServers        []config.MCPServerConfig `json:"mcp_servers,omitempty"`
	NetworkPolicy     string                   `json:"network_policy,omitempty"`
	NetworkAllow      []string                 `json:"network_allow,omitempty"`
	AWSConfig         *AWSConfig               `json:"aws_config,omitempty"`
	TransformerSpecs  []TransformerSpec        `json:"transformer_specs,omitempty"`
	CopilotGitHubAuth bool                     `json:"copilot_github_auth,omitempty"`
	CredProfile       string                   `json:"cred_profile,omitempty"`

	// Serial state. The container's MOAT_SERIAL_*_URL froze the device's
	// host:port at container create, so a daemon restart must re-open the
	// listeners on the same port numbers — a fresh ephemeral port would be
	// unreachable from inside the container for the rest of the run.
	SerialDevices  []SerialDeviceSpec `json:"serial_devices,omitempty"`
	SerialBindAddr string             `json:"serial_bind_addr,omitempty"`
	SerialAddrs    map[string]string  `json:"serial_addrs,omitempty"`

	// AllowedHostPorts are the run's serial-listener host ports, the only ones
	// the firewall lets the container reach directly — network.host and
	// base_url ports stay proxy-mediated, and a rule here would grant them raw
	// egress. Restore re-populates them so a restarted daemon still enforces
	// the run's network policy.
	AllowedHostPorts []int `json:"allowed_host_ports,omitempty"`
}

PersistedRun is the on-disk representation of a registered run. It stores grant names (not raw credentials) so provider tokens are never written to disk. The auth token (proxy-local random string) is stored in plaintext, protected by file permissions (0600). On restore, provider credentials are re-resolved from the encrypted credential store.

type PolicyRuleSetSpec added in v0.5.0

type PolicyRuleSetSpec struct {
	Scope string   `json:"scope"`
	Mode  string   `json:"mode"`
	Deny  []string `json:"deny"`
}

PolicyRuleSetSpec describes a programmatic policy using Keep's RuleSet builder. Used for inline deny-list policies from moat.yaml. The daemon compiles these with keep.NewRuleSet() instead of parsing YAML.

type RegisterRequest

type RegisterRequest struct {
	RunID       string           `json:"run_id"`
	AuthToken   string           `json:"auth_token,omitempty"` // Re-registration: use existing token
	Credentials []CredentialSpec `json:"credentials,omitempty"`
	// CredentialRefs are grant names the daemon must resolve from the encrypted
	// store. Unlike Credentials, they are safe for clients to retain and replay.
	CredentialRefs       []string                 `json:"credential_refs,omitempty"`
	ExtraHeaders         []ExtraHeaderSpec        `json:"extra_headers,omitempty"`
	RemoveHeaders        []RemoveHeaderSpec       `json:"remove_headers,omitempty"`
	TokenSubstitutions   []TokenSubstitutionSpec  `json:"token_substitutions,omitempty"`
	MCPServers           []config.MCPServerConfig `json:"mcp_servers,omitempty"`
	NetworkPolicy        string                   `json:"network_policy,omitempty"`
	NetworkAllow         []string                 `json:"network_allow,omitempty"`
	NetworkRules         []netrules.HostRules     `json:"network_rules,omitempty"`
	Grants               []string                 `json:"grants,omitempty"`
	CopilotGitHubAuth    bool                     `json:"copilot_github_auth,omitempty"`
	AWSConfig            *AWSConfig               `json:"aws_config,omitempty"`
	ResponseTransformers []TransformerSpec        `json:"response_transformers,omitempty"`
	// CredProfile is the credential profile the run was created under. The
	// daemon scopes token refresh to it. Additive/optional: an older CLI omits
	// it and the daemon falls back to the default profile (prior behavior).
	// Named to match RunContext/PersistedRun and avoid confusion with
	// AWSConfig.Profile (an AWS shared-config profile).
	CredProfile      string              `json:"cred_profile,omitempty"`
	PolicyYAML       map[string][]byte   `json:"policy_yaml,omitempty"`
	PolicyRuleSets   []PolicyRuleSetSpec `json:"policy_rule_sets,omitempty"`
	HostGateway      string              `json:"host_gateway,omitempty"`
	HostGatewayIP    string              `json:"host_gateway_ip,omitempty"`
	AllowedHostPorts []int               `json:"allowed_host_ports,omitempty"`

	// SerialDevices are host serial devices this run may use. The daemon opens
	// one RFC2217 listener per device and returns their addresses.
	SerialDevices []SerialDeviceSpec `json:"serial_devices,omitempty"`

	// SerialBindAddr is the address serial listeners bind to — the
	// container-facing address of this run's network (loopback for host-net,
	// the network gateway for bridge and Apple containers). RFC2217 has no
	// authentication, so scoping the bind is the reachability control: a
	// listener bound here is reachable from the container and from this
	// machine, not from the network at large. Additive/optional: an older CLI
	// omits it and the daemon falls back to the broker's configured BindAddr.
	SerialBindAddr string `json:"serial_bind_addr,omitempty"`

	// SerialPins names the exact port each device must come back on, keyed by
	// device name. Sent on re-registration and restore: the container's
	// MOAT_SERIAL_*_URL froze these ports at create, so the listeners must
	// re-bind the same numbers — a fresh ephemeral port would be unreachable
	// and the run's devices would silently die. A pinned port that is taken
	// fails the registration rather than moving the device. Additive/optional.
	SerialPins map[string]int `json:"serial_pins,omitempty"`
}

RegisterRequest is sent to POST /v1/runs.

func (*RegisterRequest) ToRunContext

func (req *RegisterRequest) ToRunContext() *RunContext

ToRunContext converts a RegisterRequest into a RunContext.

type RegisterResponse

type RegisterResponse struct {
	AuthToken string `json:"auth_token"`
	ProxyPort int    `json:"proxy_port"`
	Error     string `json:"error,omitempty"`

	// SerialAddrs maps each approved device name to the host:port of its
	// RFC2217 listener. Empty when the run requested no devices.
	SerialAddrs map[string]string `json:"serial_addrs,omitempty"`
}

RegisterResponse is returned from POST /v1/runs.

type Registry

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

Registry is a thread-safe mapping of auth tokens to RunContexts. It provides the central lookup mechanism for the daemon proxy to resolve incoming requests to their per-run configuration and credentials.

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates an empty Registry.

func (*Registry) Count

func (r *Registry) Count() int

Count returns the number of registered runs.

func (*Registry) List

func (r *Registry) List() []*RunContext

List returns all registered RunContexts.

func (*Registry) Lookup

func (r *Registry) Lookup(token string) (*RunContext, bool)

Lookup finds a RunContext by auth token.

func (*Registry) Register

func (r *Registry) Register(rc *RunContext) string

Register adds a RunContext and returns a generated auth token. The token is a 32-byte cryptographically random hex string.

func (*Registry) RegisterWithToken

func (r *Registry) RegisterWithToken(rc *RunContext, token string)

RegisterWithToken adds a RunContext with a specific auth token. This is used for re-registration after a daemon restart so the container can keep using the same proxy auth token it was configured with.

func (*Registry) Unregister

func (r *Registry) Unregister(token string)

Unregister removes a RunContext by its auth token.

func (*Registry) UnregisterAndGet added in v0.5.0

func (r *Registry) UnregisterAndGet(token string) (*RunContext, bool)

UnregisterAndGet atomically removes and returns a RunContext by its auth token. Returns nil, false if the token is not found.

func (*Registry) UnregisterAndGetWithCount added in v0.5.0

func (r *Registry) UnregisterAndGetWithCount(token string) (rc *RunContext, found bool, remaining int)

UnregisterAndGetWithCount atomically removes a RunContext and returns both the removed context and the remaining count. This avoids a TOCTOU race where a new registration could slip in between UnregisterAndGet and a separate Count() call.

func (*Registry) UpdateContainerID

func (r *Registry) UpdateContainerID(token, containerID string) bool

UpdateContainerID sets the container ID for a registered run. Returns false if the token is not found.

type RemoveHeaderSpec

type RemoveHeaderSpec struct {
	Host       string `json:"host"`
	HeaderName string `json:"header_name"`
}

RemoveHeaderSpec describes a header to remove from requests.

type RouteRegistration

type RouteRegistration struct {
	Services map[string]string `json:"services"`
}

RouteRegistration is sent to POST /v1/routes/{agent}.

type RunContext

type RunContext struct {
	RunID       string `json:"run_id"`
	ContainerID string `json:"container_id,omitempty"`
	AuthToken   string `json:"auth_token"`

	Credentials          map[string][]CredentialEntry                `json:"credentials"`
	CredentialBundles    map[string][]credential.Bundle              `json:"-"` // contains secrets; never serialize
	ExtraHeaders         map[string][]ExtraHeaderEntry               `json:"extra_headers"`
	RemoveHeaders        map[string][]string                         `json:"remove_headers"`
	TokenSubstitutions   map[string]TokenSubstitutionEntry           `json:"token_substitutions"`
	ResponseTransformers map[string][]credential.ResponseTransformer `json:"-"` // not serialized

	MCPServers    []config.MCPServerConfig `json:"mcp_servers,omitempty"`
	NetworkPolicy string                   `json:"network_policy,omitempty"`
	NetworkAllow  []string                 `json:"network_allow,omitempty"`
	NetworkRules  []netrules.HostRules     `json:"network_rules,omitempty"`

	AWSConfig         *AWSConfig        `json:"aws_config,omitempty"`
	TransformerSpecs  []TransformerSpec `json:"transformer_specs,omitempty"`
	Grants            []string          `json:"grants,omitempty"`
	CopilotGitHubAuth bool              `json:"copilot_github_auth,omitempty"`
	HostGateway       string            `json:"host_gateway,omitempty"`
	HostGatewayIP     string            `json:"host_gateway_ip,omitempty"` // actual IP for forwarding allowed host traffic
	AllowedHostPorts  []int             `json:"allowed_host_ports,omitempty"`

	// Serial state, for persistence across daemon restarts. SerialDevices and
	// SerialBindAddr are the registration request's; SerialAddrs is what the
	// container's frozen MOAT_SERIAL_*_URL points at, so a restore must
	// re-open each listener on the same port.
	SerialDevices  []SerialDeviceSpec `json:"serial_devices,omitempty"`
	SerialBindAddr string             `json:"serial_bind_addr,omitempty"`
	SerialAddrs    map[string]string  `json:"serial_addrs,omitempty"`

	// CredProfile is the credential profile this run was created under (from
	// the CLI's --profile/MOAT_PROFILE). The daemon is shared across profiles,
	// so token refresh must scope to this value rather than the daemon
	// process's own credential.ActiveProfile. Empty means the default profile.
	CredProfile string `json:"cred_profile,omitempty"`

	RegisteredAt time.Time `json:"registered_at"`

	KeepEngines map[string]*keeplib.Engine `json:"-"` // compiled Keep policy engines per scope
	// contains filtered or unexported fields
}

RunContext holds per-run proxy state. It implements credential.ProxyConfigurer so providers can configure it identically to how they configure proxy.Proxy.

func NewRunContext

func NewRunContext(runID string) *RunContext

NewRunContext creates a new RunContext for a run.

func (*RunContext) AddExtraHeader

func (rc *RunContext) AddExtraHeader(host, headerName, headerValue string)

AddExtraHeader implements credential.ProxyConfigurer.

func (*RunContext) AddResponseTransformer

func (rc *RunContext) AddResponseTransformer(host string, transformer credential.ResponseTransformer)

AddResponseTransformer implements credential.ProxyConfigurer.

func (*RunContext) CancelRefresh

func (rc *RunContext) CancelRefresh()

CancelRefresh cancels the token refresh goroutine, if any. Safe to call concurrently and multiple times.

func (*RunContext) Close added in v0.5.0

func (rc *RunContext) Close()

Close releases resources held by this RunContext, including all Keep engines. Safe to call concurrently and multiple times.

func (*RunContext) CredentialBundleGrants added in v0.8.0

func (rc *RunContext) CredentialBundleGrants() []string

CredentialBundleGrants returns the distinct grants that have a bundle installed, in a stable order.

Bundles carry real secrets and are deliberately not serialized, so a run holding one cannot hand its values to the daemon in a registration request. It sends these grant names instead and the daemon resolves the current credential from the encrypted store itself — see RegisterRequest.CredentialRefs.

func (*RunContext) GetContainerID

func (rc *RunContext) GetContainerID() string

GetContainerID returns the container ID safely.

func (*RunContext) GetCredential

func (rc *RunContext) GetCredential(host string) (CredentialEntry, bool)

GetCredential returns the first credential for a host, checking host:port fallback. Use GetCredentials to retrieve all credentials when multiple grants target the same host.

func (*RunContext) GetCredentials added in v0.5.0

func (rc *RunContext) GetCredentials(host string) []CredentialEntry

GetCredentials returns all credentials for a host, checking host:port fallback.

func (*RunContext) GetExtraHeaders

func (rc *RunContext) GetExtraHeaders(host string) []ExtraHeaderEntry

GetExtraHeaders returns extra headers for a host, checking host:port fallback.

func (*RunContext) GetRemoveHeaders

func (rc *RunContext) GetRemoveHeaders(host string) []string

GetRemoveHeaders returns headers to remove for a host, checking host:port fallback.

func (*RunContext) GetResponseTransformers

func (rc *RunContext) GetResponseTransformers(host string) []credential.ResponseTransformer

GetResponseTransformers returns response transformers for a host, checking host:port fallback.

func (*RunContext) GetTokenSubstitution

func (rc *RunContext) GetTokenSubstitution(host string) (TokenSubstitutionEntry, bool)

GetTokenSubstitution returns the token substitution for a host, checking host:port fallback.

func (*RunContext) RemoveRequestHeader

func (rc *RunContext) RemoveRequestHeader(host, headerName string)

RemoveRequestHeader implements credential.ProxyConfigurer.

func (*RunContext) SetAWSHandler

func (rc *RunContext) SetAWSHandler(h http.Handler)

SetAWSHandler stores the AWS credential endpoint handler for this run.

func (*RunContext) SetContainerID added in v0.5.0

func (rc *RunContext) SetContainerID(id string)

SetContainerID updates the container ID safely.

func (*RunContext) SetCredential

func (rc *RunContext) SetCredential(host, value string)

SetCredential implements credential.ProxyConfigurer.

func (*RunContext) SetCredentialBundle added in v0.8.0

func (rc *RunContext) SetCredentialBundle(host string, bundle credential.Bundle)

SetCredentialBundle implements credential.BundleConfigurer.

func (*RunContext) SetCredentialHeader

func (rc *RunContext) SetCredentialHeader(host, headerName, headerValue string)

SetCredentialHeader implements credential.ProxyConfigurer.

func (*RunContext) SetCredentialWithGrant

func (rc *RunContext) SetCredentialWithGrant(host, headerName, headerValue, grant string)

SetCredentialWithGrant implements credential.ProxyConfigurer. When multiple grants target the same host (e.g., "claude" and "anthropic" on api.anthropic.com), each is stored separately. If an entry with the same grant and header name already exists for the host (e.g., during token refresh), it is updated in place rather than duplicated.

func (*RunContext) SetRefreshCancel

func (rc *RunContext) SetRefreshCancel(cancel context.CancelFunc)

SetRefreshCancel stores the cancel function for the token refresh goroutine.

func (*RunContext) SetTokenSubstitution

func (rc *RunContext) SetTokenSubstitution(host, placeholder, realToken string)

SetTokenSubstitution implements credential.ProxyConfigurer.

func (*RunContext) ToProxyContextData

func (rc *RunContext) ToProxyContextData() *proxy.RunContextData

ToProxyContextData converts this RunContext into a proxy.RunContextData for use in per-request credential resolution.

type RunInfo

type RunInfo struct {
	RunID        string `json:"run_id"`
	ContainerID  string `json:"container_id,omitempty"`
	RegisteredAt string `json:"registered_at"`
}

RunInfo is an element of the list returned by GET /v1/runs.

type RunPersister added in v0.3.2

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

RunPersister manages saving and loading the run registry to disk.

func NewRunPersister added in v0.3.2

func NewRunPersister(path string, registry *Registry) *RunPersister

NewRunPersister creates a persister that saves to the given file path.

func (*RunPersister) Flush added in v0.3.2

func (p *RunPersister) Flush() error

Flush stops any pending debounce timer and performs a synchronous Save. Call during daemon shutdown to ensure the final state is persisted.

func (*RunPersister) Load added in v0.3.2

func (p *RunPersister) Load() ([]PersistedRun, error)

Load reads persisted runs from disk. Returns nil, nil if the file doesn't exist. Handles both the versioned format ({"version":1,"runs":[...]}) and the legacy bare-array format for backwards compatibility.

func (*RunPersister) Save added in v0.3.2

func (p *RunPersister) Save() error

Save serializes the current registry to disk atomically (temp file + rename). The file is written with 0600 permissions. Safe to call concurrently.

func (*RunPersister) SaveDebounced added in v0.3.2

func (p *RunPersister) SaveDebounced()

SaveDebounced coalesces rapid save calls with a short timer. Safe to call from multiple goroutines.

type SerialDeviceSpec added in v0.8.0

type SerialDeviceSpec struct {
	Name      string `json:"name"`                // config name, e.g. "esp32"
	Path      string `json:"path"`                // host device node
	VID       string `json:"vid,omitempty"`       // for audit entries
	PID       string `json:"pid,omitempty"`       // for audit entries
	Serial    string `json:"serial,omitempty"`    // for audit entries
	PortPath  string `json:"port_path,omitempty"` // for audit entries
	Interface string `json:"interface,omitempty"` // for audit entries
	Record    string `json:"record,omitempty"`    // "events" (default) or "full"
	// Baud is the initial line rate from moat.yaml's `baud:`. Zero means the
	// port opens at whatever rate the driver default is and the client's
	// SET-BAUDRATE governs. Applied at session open; an RFC2217 client that
	// sends its own SET-BAUDRATE overrides it.
	Baud int `json:"baud,omitempty"`
}

SerialDeviceSpec is one approved serial device, already resolved and pinned by the CLI. The daemon does not re-resolve it: enumeration and pin checking happen in pre-flight so a mismatch fails before the container is created.

type Server

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

Server is the daemon's HTTP API server over a Unix socket.

func NewServer

func NewServer(sockPath string, proxyPort int) *Server

NewServer creates a daemon API server that will listen on the given Unix socket path.

func (*Server) ListenSerialPinned added in v0.8.0

func (s *Server) ListenSerialPinned(rc *RunContext, specs []SerialDeviceSpec, bindAddr string, pinned map[string]string) (map[string]string, error)

ListenSerialPinned re-opens a restored run's serial listeners on the exact ports the container's frozen MOAT_SERIAL_*_URLs point at. A daemon restart must not move a device, because the container cannot learn the new address. A port that cannot be re-bound fails with an error naming the device, rather than silently leaving the device dead or rebinding it elsewhere.

func (*Server) Registry

func (s *Server) Registry() *Registry

Registry returns the server's run registry.

func (*Server) SetOnEmpty

func (s *Server) SetOnEmpty(fn func())

SetOnEmpty sets a callback that is invoked when the last run is unregistered.

func (*Server) SetOnRegister

func (s *Server) SetOnRegister(fn func())

SetOnRegister sets a callback invoked when a new run is registered.

func (*Server) SetOnShutdown

func (s *Server) SetOnShutdown(fn func())

SetOnShutdown sets a callback that is invoked when shutdown is requested via the API. This should signal the main daemon loop to exit (e.g., by sending SIGTERM to self).

func (*Server) SetOnUnregister

func (s *Server) SetOnUnregister(fn func(runID string))

SetOnUnregister sets a callback that is invoked when a run is unregistered. The callback receives the run ID for per-run resource cleanup.

func (*Server) SetPersister added in v0.3.2

func (s *Server) SetPersister(p *RunPersister)

SetPersister sets the run persister for saving registry state to disk.

func (*Server) SetProxyPort

func (s *Server) SetProxyPort(port int)

SetProxyPort updates the proxy port reported in API responses. Call after the credential proxy starts to set the actual port.

func (*Server) SetRoutes

func (s *Server) SetRoutes(rt *routing.RouteTable)

SetRoutes sets the route table used for route registration handlers.

func (*Server) SetSerialBroker added in v0.8.0

func (s *Server) SetSerialBroker(b *serialbroker.Broker)

SetSerialBroker attaches the serial device broker. Without one, a run that requests devices is rejected rather than silently starting with no access.

func (*Server) Start

func (s *Server) Start() error

Start begins listening on the Unix socket. Any stale socket file is removed first.

func (*Server) Stop

func (s *Server) Stop(ctx context.Context) error

Stop gracefully shuts down the server and removes the socket file.

type TokenSubstitutionEntry

type TokenSubstitutionEntry struct {
	Placeholder string `json:"placeholder"`
	RealToken   string `json:"real_token"`
}

TokenSubstitutionEntry holds a placeholder-to-real-token mapping.

type TokenSubstitutionSpec

type TokenSubstitutionSpec struct {
	Host        string `json:"host"`
	Placeholder string `json:"placeholder"`
	RealToken   string `json:"real_token"`
}

TokenSubstitutionSpec describes a token substitution.

type TransformerSpec

type TransformerSpec struct {
	Host string `json:"host"`
	Kind string `json:"kind"` // "oauth-endpoint-workaround" or "response-scrub"
}

TransformerSpec describes a response transformer to apply for a host. Since transformers are Go functions (not serializable), this spec allows the daemon to reconstruct them from well-known kinds.

type UpdateRunRequest

type UpdateRunRequest struct {
	ContainerID string `json:"container_id"`
}

UpdateRunRequest is sent to PATCH /v1/runs/{token}.

Jump to

Keyboard shortcuts

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