runner

package
v0.24.5 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2026 License: Apache-2.0 Imports: 28 Imported by: 0

Documentation

Overview

Package runner implements `sprout runner`: a user's own machine executing the browser build's escalated work for their platform account (SP-159). The platform side of the protocol is documented in the platform repo's docs/runners/PROTOCOL.md.

Index

Constants

View Source
const (
	ModeContainer = "container"
	ModeNative    = "native"
	ModeBareMetal = "bare-metal"
)

Modes a runner executes workspaces in (SP-159 §159c).

View Source
const DefaultImage = "ghcr.io/sprout-foundry/sprout:latest"

DefaultImage is the workspace image container mode runs.

View Source
const DefaultListenAddr = "127.0.0.1:8935"

DefaultListenAddr is where the host server listens for platform calls.

Variables

View Source
var (
	ErrAuthorizationPending = errors.New("authorization pending")
	ErrSlowDown             = errors.New("slow down")
	ErrAccessDenied         = errors.New("link request denied on the platform")
	ErrExpiredToken         = errors.New("link code expired")
)

Device-flow poll outcomes the caller acts on.

Functions

func DeleteAPIKey

func DeleteAPIKey() error

DeleteAPIKey removes the runner's API key from both stores.

func Dir

func Dir() (string, error)

Dir is where the runner keeps its state and workspaces.

func DockerAvailable

func DockerAvailable(ctx context.Context, docker string) error

DockerAvailable reports whether container mode can run here.

func InstallService

func InstallService() (string, error)

InstallService registers `sprout runner start` as a systemd user unit that starts at login and restarts if it exits.

func LoadAPIKey

func LoadAPIKey() (string, error)

LoadAPIKey reads the runner's API key from the credential store, or the file fallback SaveAPIKey may have used.

func OpenURL

func OpenURL(raw string)

OpenURL opens an http(s) URL in the user's browser, best effort: the link flow prints the URL too, so a failure here costs nothing. The URL comes from the platform's response, so anything else (file:, app launchers, a value that would parse as a flag) is not opened.

func SaveAPIKey

func SaveAPIKey(key string) (source string, err error)

SaveAPIKey stores the runner's API key in the credential store. The platform issues the key exactly once, so when the OS keyring refuses (no GUI session, locked keychain) it falls back to sprout's encrypted file store rather than lose it.

func UninstallService

func UninstallService() error

UninstallService disables and removes the systemd user unit.

func ValidMode

func ValidMode(m string) bool

ValidMode reports whether m is a known execution mode.

func WorkspacesDir

func WorkspacesDir() (string, error)

WorkspacesDir holds one clone per workspace; a runner never touches the user's own working copies. It lives in sprout's state dir, outside the config dir whose credential files native mode hides from workspaces.

Types

type Client

type Client struct {
	BaseURL    string
	Creds      Credentials
	HTTPClient *http.Client
}

Client speaks the runner protocol to one platform.

func NewClient

func NewClient(baseURL string, creds Credentials) *Client

NewClient returns a client for baseURL; creds may be empty before linking.

func (c *Client) PollLink(ctx context.Context, deviceCode string) (*Credentials, error)

PollLink checks whether the user approved the link. It returns the runner's credentials exactly once, on approval.

func (*Client) PollWorkspaceTasks

func (c *Client) PollWorkspaceTasks(ctx context.Context) ([]WorkspaceTask, error)

PollWorkspaceTasks fetches pending workspace instructions.

func (*Client) SendHeartbeat

func (c *Client) SendHeartbeat(ctx context.Context, hb Heartbeat) error

SendHeartbeat reports liveness and capability.

func (c *Client) StartLink(ctx context.Context, name, goos, arch, mode string) (*DeviceStart, error)

StartLink asks the platform for a device code for this machine. mode is shown on the approval screen; the platform never stores or sets it.

func (*Client) SubmitWorkspaceResult

func (c *Client) SubmitWorkspaceResult(ctx context.Context, r WorkspaceResult) error

SubmitWorkspaceResult reports a workspace start outcome.

func (*Client) UpdateWorkspaceStatus

func (c *Client) UpdateWorkspaceStatus(ctx context.Context, workspaceID, status string) error

UpdateWorkspaceStatus reports a workspace's status change.

type ContainerLauncher

type ContainerLauncher struct {
	Image string
	// Docker is the CLI to run; "docker" when empty.
	Docker string
}

ContainerLauncher runs each workspace in the sprout workspace image with the platform's hardening profile: non-root, read-only rootfs, every capability dropped, no-new-privileges, CPU and memory limits, the daemon published on loopback only. It drives the docker CLI, so Docker Desktop, OrbStack, Colima and Podman's docker shim all work.

func (*ContainerLauncher) Destroy

func (l *ContainerLauncher) Destroy(ctx context.Context, workspaceID string, _ *Workspace) error

func (*ContainerLauncher) Start

func (*ContainerLauncher) Stop

func (l *ContainerLauncher) Stop(ctx context.Context, ws *Workspace) error

type Credentials

type Credentials struct {
	RunnerID string `json:"runner_id"`
	APIKey   string `json:"api_key"`
}

Credentials identify a linked runner.

type DeviceStart

type DeviceStart struct {
	DeviceCode              string `json:"device_code"`
	UserCode                string `json:"user_code"`
	VerificationURI         string `json:"verification_uri"`
	VerificationURIComplete string `json:"verification_uri_complete"`
	Interval                int    `json:"interval"`
	ExpiresIn               int    `json:"expires_in"`
}

DeviceStart is the platform's answer to a link request.

type Heartbeat

type Heartbeat struct {
	Status        string   `json:"status"`
	RunningTasks  int      `json:"running_tasks"`
	Mode          string   `json:"mode"`
	Sandbox       string   `json:"sandbox"`
	RunnerVersion string   `json:"runner_version"`
	Toolchains    []string `json:"toolchains,omitempty"`
	// DirectURL is where the platform reaches the host server now; empty
	// for a relayed runner.
	DirectURL string `json:"direct_url,omitempty"`
	// Relayed reports that the runner serves the platform over its tunnel.
	Relayed bool `json:"relayed,omitempty"`
}

Heartbeat reports liveness plus what the runner can do. The platform only reflects Mode; it never sets it.

type HostLauncher

type HostLauncher struct {
	Sandboxed bool
	// Writable are extra paths native mode may write (toolchain caches).
	Writable []string
	// SproutBin is the binary that serves the workspace daemon.
	SproutBin string
	// contains filtered or unexported fields
}

HostLauncher runs a workspace's daemon directly on this machine, with its toolchains. Sandboxed is native mode (the OS sandbox confines it); unsandboxed is bare-metal mode (it runs with the user's full access).

func (*HostLauncher) Destroy

func (l *HostLauncher) Destroy(_ context.Context, workspaceID string, _ *Workspace) error

func (*HostLauncher) Start

func (l *HostLauncher) Start(ctx context.Context, task WorkspaceTask) (*Workspace, error)

func (*HostLauncher) Stop

func (l *HostLauncher) Stop(_ context.Context, ws *Workspace) error

type HostServer

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

HostServer is the only surface the platform reaches on a runner: /daemon/{workspaceID}/... proxied to that workspace's daemon on loopback, after checking the per-workspace bearer secret. The daemon checks the same secret again (its SPROUT_AUTH_TOKEN), so the runner is not the only gate.

func NewHostServer

func NewHostServer() *HostServer

NewHostServer returns an empty host server.

func (*HostServer) Bind

func (h *HostServer) Bind(workspaceID string, port int, secret string)

Bind routes a workspace to its daemon port, authenticated by secret.

func (*HostServer) Handler

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

Handler is the host server's HTTP surface.

func (*HostServer) Unbind

func (h *HostServer) Unbind(workspaceID string)

Unbind removes a workspace; later calls for it get 404.

type Launcher

type Launcher interface {
	Start(ctx context.Context, task WorkspaceTask) (*Workspace, error)
	Stop(ctx context.Context, ws *Workspace) error
	Destroy(ctx context.Context, workspaceID string, ws *Workspace) error
}

Launcher starts and stops workspaces in one execution mode.

type Runner

type Runner struct {
	State    *State
	Client   *Client
	Launcher Launcher
	Host     *HostServer
	// Sandbox and Version are reported in heartbeats.
	Sandbox string
	Version string
	Log     *slog.Logger
	// contains filtered or unexported fields
}

Runner serves workspaces for one platform account until its context ends.

func (*Runner) Run

func (r *Runner) Run(ctx context.Context) error

Run serves the host server, heartbeats and executes workspace tasks.

type State

type State struct {
	PlatformURL string `json:"platform_url"`
	RunnerID    string `json:"runner_id"`
	Name        string `json:"name"`
	Mode        string `json:"mode"`
	// PublicURL is the HTTPS base the platform reaches the host server at
	// (direct mode). Empty means the relay tunnel, once the platform offers it.
	PublicURL  string `json:"public_url,omitempty"`
	ListenAddr string `json:"listen_addr,omitempty"`
	Image      string `json:"image,omitempty"`
	// Writable are extra paths native mode may write (toolchain caches).
	Writable []string `json:"writable,omitempty"`
}

State is the runner's persistent configuration. The API key is not part of it: it lives in the credential store.

func LoadState

func LoadState() (*State, error)

LoadState returns the saved state, or defaults when the runner was never linked.

func (*State) Linked

func (s *State) Linked() bool

Linked reports whether the runner has completed `sprout runner link`.

func (*State) Save

func (s *State) Save() error

Save writes the state with owner-only permissions.

type Workspace

type Workspace struct {
	ID   string
	Port int
	// Handle identifies it to its launcher (a container name, or a pid).
	Handle string
}

Workspace is a running workspace's daemon, reachable on loopback.

type WorkspaceResult

type WorkspaceResult struct {
	WorkspaceID   string `json:"workspace_id"`
	ContainerID   string `json:"container_id"`
	Port          int    `json:"port"`
	ConnectionURL string `json:"connection_url,omitempty"`
	Status        string `json:"status"`
}

WorkspaceResult reports a started (or failed) workspace.

type WorkspaceTask

type WorkspaceTask struct {
	WorkspaceID    string            `json:"workspace_id"`
	UserID         string            `json:"user_id"`
	RepoURL        string            `json:"repo_url"`
	Action         string            `json:"action"`
	ContainerID    string            `json:"container_id"`
	TxnSecret      string            `json:"txn_secret,omitempty"`
	GitToken       string            `json:"git_token,omitempty"`
	LLMProvider    string            `json:"llm_provider,omitempty"`
	LLMKey         string            `json:"llm_key,omitempty"`
	UserEnv        map[string]string `json:"user_env,omitempty"`
	PlatformAPIURL string            `json:"platform_api_url,omitempty"`
}

WorkspaceTask is a start/stop/destroy instruction for one workspace. Secrets arrive over the runner-key-authenticated channel and are never written to disk by the runner.

Directories

Path Synopsis
Package relay carries HTTP exchanges over one WebSocket between the platform and a runner behind NAT (platform SP-BUILDER-13, sprout SP-159).
Package relay carries HTTP exchanges over one WebSocket between the platform and a runner behind NAT (platform SP-BUILDER-13, sprout SP-159).
Package sandbox confines a runner workspace's processes in native mode (SP-159 §159c): host toolchains under the OS sandbox — Seatbelt on macOS, bubblewrap on Linux.
Package sandbox confines a runner workspace's processes in native mode (SP-159 §159c): host toolchains under the OS sandbox — Seatbelt on macOS, bubblewrap on Linux.

Jump to

Keyboard shortcuts

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