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
- Variables
- func DeleteAPIKey() error
- func Dir() (string, error)
- func DockerAvailable(ctx context.Context, docker string) error
- func InstallService() (string, error)
- func LoadAPIKey() (string, error)
- func OpenURL(raw string)
- func SaveAPIKey(key string) (source string, err error)
- func UninstallService() error
- func ValidMode(m string) bool
- func WorkspacesDir() (string, error)
- type Client
- func (c *Client) PollLink(ctx context.Context, deviceCode string) (*Credentials, error)
- func (c *Client) PollWorkspaceTasks(ctx context.Context) ([]WorkspaceTask, error)
- func (c *Client) SendHeartbeat(ctx context.Context, hb Heartbeat) error
- func (c *Client) StartLink(ctx context.Context, name, goos, arch, mode string) (*DeviceStart, error)
- func (c *Client) SubmitWorkspaceResult(ctx context.Context, r WorkspaceResult) error
- func (c *Client) UpdateWorkspaceStatus(ctx context.Context, workspaceID, status string) error
- type ContainerLauncher
- type Credentials
- type DeviceStart
- type Heartbeat
- type HostLauncher
- type HostServer
- type Launcher
- type Runner
- type State
- type Workspace
- type WorkspaceResult
- type WorkspaceTask
Constants ¶
const ( ModeContainer = "container" ModeNative = "native" ModeBareMetal = "bare-metal" )
Modes a runner executes workspaces in (SP-159 §159c).
const DefaultImage = "ghcr.io/sprout-foundry/sprout:latest"
DefaultImage is the workspace image container mode runs.
const DefaultListenAddr = "127.0.0.1:8935"
DefaultListenAddr is where the host server listens for platform calls.
Variables ¶
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 DockerAvailable ¶
DockerAvailable reports whether container mode can run here.
func InstallService ¶
InstallService registers `sprout runner start` as a systemd user unit that starts at login and restarts if it exits.
func LoadAPIKey ¶
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 ¶
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 WorkspacesDir ¶
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 (*Client) PollLink ¶
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 ¶
SendHeartbeat reports liveness and capability.
func (*Client) StartLink ¶
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.
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) Start ¶
func (l *ContainerLauncher) Start(ctx context.Context, task WorkspaceTask) (*Workspace, error)
type Credentials ¶
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) Start ¶
func (l *HostLauncher) Start(ctx context.Context, task WorkspaceTask) (*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 (*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.
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.
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.
Source Files
¶
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. |