forest

package
v0.1.47 Latest Latest
Warning

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

Go to latest
Published: Jul 1, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Package forest is the network of arbos nodes (ADR-0034): the head side — a self-hostable registry + relay that gives each joined node an ephemeral public URL — and the node side, a client that registers a device identity, leases a name, and serves its gateway back through an outbound tunnel.

The wire surface is the subset of docs/account-api-contract.md a forest head implements: device registration, signed-challenge token mint, the node heartbeat (which carries the lease), and the tunnel handshake. A node built against this package works against the hosted head and a self-hosted one identically — the head is part of the swappable backend, per ADR-0033.

Index

Constants

View Source
const DefaultHead = "https://arbos.life"

DefaultHead is the forest a bare `arbos web` joins when no --forest/--local override is given: the canonical "one command, get a URL" path. Nodes are auth-gated by their own login token; the head is transport, not a trust boundary, so defaulting in is safe — but the join is always printed loudly so nobody is surprised their node has a public name.

Variables

This section is empty.

Functions

func LoadOrCreateAgentKey

func LoadOrCreateAgentKey(dir string) (ed25519.PrivateKey, error)

LoadOrCreateAgentKey returns the per-directory agent key, generating one on first run at <dir>/.arbos/agent.key. Unlike the device key it is not a secret (ADR-0035): its public half is a binding input that, composed with the device key, derives this agent's stable URL — same directory on the same machine yields the same URL, with no login. It travels with the directory; the secret that gates the URL stays the device key, on the box.

func LoadOrCreateDeviceKey

func LoadOrCreateDeviceKey(path string) (ed25519.PrivateKey, error)

LoadOrCreateDeviceKey returns the device's ed25519 key, generating one on first run. The keypair is the device (ADR-0033): the only durable local secret, stored as the 32-byte seed at path (0600, dir 0700). The account subsystem (ADR-0033 P1) will lift this same file; the forest client is its first consumer.

Types

type Client

type Client struct {
	// Base is the forest head's URL, e.g. "http://204-12-163-231.sslip.io:8080".
	Base string
	// KeyPath is the device key seed (LoadOrCreateDeviceKey).
	KeyPath string
	// AgentDir is the workspace whose per-directory key (<dir>/.arbos/agent.key)
	// composes with the device to derive a stable URL (ADR-0035). Empty leaves
	// the name a function of the device alone.
	AgentDir string
	// Handler is served to tunneled requests — the same gateway handler the
	// local listener serves, auth gate included. Serving it directly off the
	// tunnel (no loopback hop) is deliberate: tunneled requests must never
	// look like local ones to the gate (ADR-0034).
	Handler http.Handler
	// OnJoin fires whenever a join (or rejoin) succeeds.
	OnJoin func(JoinInfo)
	Logf   func(format string, args ...any)
	// contains filtered or unexported fields
}

Client joins a node to a forest: registers the device, mints a token, heartbeats to hold the lease, and serves Handler back through an outbound tunnel. Run blocks and reconnects forever — a dropped tunnel or a restarted head is rejoined with backoff, because the lease model already assumes nodes come and go.

func (*Client) Run

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

Run joins and serves until ctx ends. Every failure path falls back to a fresh join attempt after backoff; the only fatal errors are local ones (an unreadable device key).

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

Head is the forest head: device registry, token mint, lease table, and the relay that routes "<name>.<domain>" requests down each node's tunnel. The relay is a dumb pipe — it never makes auth decisions for a node; the node's own gateway gate does (ADR-0034).

func NewHead

func NewHead(cfg HeadConfig) (*Head, error)

NewHead loads persisted devices and returns a serving-ready head.

func (*Head) Handler

func (h *Head) Handler() http.Handler

Handler routes by Host: the apex serves the API, "<name>.<apex>" serves that lease's tunnel, anything else 404s.

func (*Head) Sweep

func (h *Head) Sweep(ctx context.Context)

Sweep reaps expired leases (closing any lingering tunnel) until ctx ends. Run it as a goroutine next to the HTTP server.

type HeadConfig

type HeadConfig struct {
	// Domain is the public apex the head answers as, including a non-default
	// port if any (e.g. "204-12-163-231.sslip.io:8080"). Lease hosts are
	// "<name>.<Domain>"; anything else 404s.
	Domain string
	// Scheme ("http" | "https") for the URLs minted into heartbeat
	// responses. The head itself only listens plain; TLS termination in
	// front (or autocert, later) flips this to https.
	Scheme string
	// StatePath persists registered devices (a JSON map), so an anonymous
	// account survives head restarts. Leases are deliberately not persisted:
	// they are ephemeral by design and re-establish on the next heartbeat.
	StatePath string
	// LeaseTTL is how long a lease outlives its last heartbeat. Heartbeat is
	// the cadence handed to nodes. Head-controlled on purpose.
	LeaseTTL  time.Duration
	Heartbeat time.Duration
	Logf      func(format string, args ...any)
}

HeadConfig configures a forest head.

type JoinInfo

type JoinInfo struct {
	Name string
	URL  string
}

JoinInfo is what a successful join hands back: the leased name and the public base URL the node is now reachable at.

Jump to

Keyboard shortcuts

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