Strux OS Documentation
Home
Guide
Concepts
BSP Development
Reference
GitHub
Home
Guide
Concepts
BSP Development
Reference
GitHub
  • Reference

    • CLI Reference
    • strux.yaml Reference
    • Go Runtime Reference
    • Frontend API Reference

Go Runtime Reference

Every exported function, method, and type in the Strux Go runtime library (github.com/strux-dev/strux/pkg/runtime). This is the package your main.go imports: it exposes your app to the frontend, serves your built frontend over HTTP, carries events in both directions, and provides the built-in system services. For a task-oriented introduction, see the Backend guide; for the JavaScript side of everything on this page, see the Frontend API reference.

Creating and starting the runtime

// Start creates the runtime, starts the IPC bridge, and blocks on the HTTP server.
func Start(app interface{}) error

// Init creates the runtime and starts the IPC bridge without blocking.
// Use this instead of Start when you need the Runtime for events or services.
func Init(app interface{}) (*Runtime, error)

// Serve starts the HTTP server and blocks until it exits.
func (rt *Runtime) Serve() error

// Stop shuts down the IPC server and removes the socket.
func (rt *Runtime) Stop()

The minimal pattern (no events) is runtime.Start(app). The template project generated by strux init uses Init so it can register event handlers before serving:

package main

import (
	"log"

	"github.com/strux-dev/strux/pkg/runtime"
)

// App is the main application struct.
// All public fields and methods are exposed to the frontend.
type App struct {
	Title   string
	Counter int
}

// Greet returns a greeting message.
func (a *App) Greet(name string) string {
	return "Hello, " + name + "!"
}

func main() {
	app := &App{Title: "my-kiosk", Counter: 0}

	rt, err := runtime.Init(app)
	if err != nil {
		log.Fatal(err)
	}
	defer rt.Stop()

	rt.On("hello", func(data interface{}) {
		rt.Emit("hello-reply", map[string]string{"message": "Hello from Go!"})
	})

	if err := rt.Serve(); err != nil {
		log.Fatal(err)
	}
}

Init returns an error if the IPC socket cannot be created. Start additionally returns the HTTP server's error when it exits.

What gets exposed

When the runtime is created, it walks your app struct with reflection and binds:

  • Exported methods (pointer and value receivers) — callable from the frontend, with JSON-encoded parameters and return values.
  • Exported primitive fields — readable and writable from the frontend.
  • Exported struct fields — become nested namespaces; their exported methods and fields are bound recursively under a dotted path (e.g. Settings.Audio.SetMasterVolume). Pointer fields are dereferenced; nil pointer fields are skipped, so initialize nested structs before calling Init/Start.
  • Unexported fields and methods are ignored entirely.

IPC bridge and HTTP server

  • The IPC bridge listens on the Unix socket /tmp/strux-ipc.sock. The WPE WebKit extension on the device connects to it and injects the JavaScript bindings — you never talk to this socket yourself.
  • Serve listens on 127.0.0.1:8080 by default; set the STRUX_HTTP_ADDR environment variable to override.
  • Static files are served from /strux/frontend when that directory exists (the location in a built image), otherwise from ./frontend.
  • Any path that doesn't match a real file falls back to index.html, so client-side routers (Vue Router, React Router) work.
  • GET /__strux/health returns 204 No Content — a health check endpoint.

Method call semantics

Methods bound from your app struct (and from extensions) follow these rules when called from the frontend:

  • Parameters are positional and decoded from JSON into the Go parameter types. A wrong parameter count or an undecodable value returns an error to the caller.
  • If the method's last return value is an error and it is non-nil, the call fails and the frontend promise rejects with the error message.
  • Zero non-error return values resolve to nothing, one resolves to that value, and multiple resolve to an array.

Services

The Runtime exposes built-in system services through accessor methods. Each accessor returns a stateless service value, so call it wherever you need it:

rt.Boot().Reboot()
level, err := rt.Display().GetBacklight("HDMI-A-1")

Every service is also exposed to the frontend under window.strux.<namespace> — see the Frontend API reference.

Three services — Display backlight, Network, and Wi-Fi — are backed by BSP capability providers. If the active BSP has not registered a provider, those methods return api.UnsupportedError (message: capability <name> is not supported by the active BSP). Check support at runtime with the Capabilities service before relying on them.

Boot

rt.Boot() *api.BootService — namespace boot. Boot and power management.

func (b *BootService) HideSplash() error
func (b *BootService) Reboot() error
func (b *BootService) Shutdown() error
MethodDescription
HideSplashTells the Cage compositor (via its control socket /tmp/strux-cage-control.sock) to hide the boot splash and reveal your app. Call this when your frontend is ready to be seen. If the socket doesn't exist or refuses the connection (e.g. in dev mode), it returns nil instead of an error.
RebootReboots the device (runs reboot).
ShutdownPowers the device off (runs poweroff).

Capabilities

rt.Capabilities() *api.CapabilitiesService — namespace capabilities. Tells you which BSP-backed capabilities are available on the running device.

func (s *CapabilitiesService) List() []CapabilityInfo
func (s *CapabilitiesService) Supports(name string) bool
MethodDescription
ListReturns every defined capability with its name, namespace, description, declared methods, whether a provider is registered, and the provider's Go type name.
SupportsReturns true when the named capability has a registered provider. Unknown names return false.

The capability names defined today are "display" (backlight control), "network", and "wifi".

type CapabilityInfo struct {
	Name        string       `json:"name"`
	Namespace   string       `json:"namespace"`
	Description string       `json:"description,omitempty"`
	Supported   bool         `json:"supported"`
	Provider    string       `json:"provider,omitempty"`
	Methods     []MethodSpec `json:"methods"`
}

Dev

rt.Dev() *api.DevService — namespace dev. Reads and writes the on-device dev-mode configuration, so a kiosk can expose a settings screen that enables dev mode without reflashing. The active config lives at /strux/.dev-env.json; a stored-but-disabled config lives at /strux/.dev-env.json.disabled.

func (d *DevService) GetConfig() (DevState, error)
func (d *DevService) SetConfig(config DevConfig) error
func (d *DevService) SetEnabled(enabled bool) error
func (d *DevService) Apply(config DevConfig, enabled bool) error
func (d *DevService) RestartService() error
func (d *DevService) ApplyAndRestart(config DevConfig, enabled bool) error
MethodDescription
GetConfigReturns the stored config plus whether dev mode is currently enabled. If no config file exists, returns defaults (mDNS on, inspector off on port 9223, USB networking on with subnet 192.168.7.0/24).
SetConfigValidates and writes the config without changing whether dev mode is enabled.
SetEnabledEnables or disables dev mode by renaming the config file between its active and disabled paths. Enabling fails if no config was stored (no stored dev config found; call SetConfig or Apply first) or if clientKey is empty.
ApplyValidates, stores, and toggles in one call.
RestartServiceRestarts the strux systemd service ~500 ms after returning, so the caller's IPC response is delivered first.
ApplyAndRestartApply followed by RestartService.
type DevConfig struct {
	ClientKey     string             `json:"clientKey"`
	UseMDNS       bool               `json:"useMDNS"`
	FallbackHosts []DevHost          `json:"fallbackHosts"`
	Inspector     DevInspectorConfig `json:"inspector"`
	USB           DevUSBConfig       `json:"usb"`
}

type DevHost struct {
	Host string `json:"host"`
	Port int    `json:"port"`
}

type DevInspectorConfig struct {
	Enabled bool `json:"enabled"`
	Port    int  `json:"port"`
}

type DevUSBConfig struct {
	Enabled bool   `json:"enabled"`
	Subnet  string `json:"subnet"`
}

type DevState struct {
	Enabled bool      `json:"enabled"`
	Config  DevConfig `json:"config"`
}

Validation errors: inspector.port must be greater than 0; usb.subnet must be an IPv4 CIDR with at least two usable addresses (prefix length ≤ 30); every fallback host needs a non-empty host and a port greater than 0; clientKey is required only to enable dev mode.

Display

rt.Display() *api.DisplayService — namespace display. Lists and configures display outputs, and controls backlight.

Output listing and configuration are implemented by the runtime itself by running the wlr-randr command-line tool against the Cage compositor (with a 30-second timeout per invocation). Only GetBacklight/SetBacklight go through the BSP's display provider, because backlight hardware is board-specific.

What is wlr-randr?

wlr-randr is a small utility that queries and configures display outputs on wlroots-based Wayland compositors like Cage — resolution, refresh rate, rotation, position, and scale. The runtime shells out to it; if the binary is not on PATH, display methods return an error (wlr-randr not found in PATH).

func (DisplayService) List() ([]DisplayOutput, error)
func (DisplayService) Get(name string) (DisplayOutput, error)
func (DisplayService) Apply(changes []DisplayOutputChange, opts DisplayApplyOptions) error
func (DisplayService) SetListedMode(output string, mode ListedModeSelection, opts DisplayApplyOptions) error
func (DisplayService) SetCustomMode(output string, mode CustomModeSelection, opts DisplayApplyOptions) error
func (DisplayService) SetPreferredMode(output string, opts DisplayApplyOptions) error
func (DisplayService) SetOutputEnabled(output string, on bool, opts DisplayApplyOptions) error
func (DisplayService) SetLayout(output string, x int32, y int32, opts DisplayApplyOptions) error
func (DisplayService) SetScale(output string, scale float64, opts DisplayApplyOptions) error
func (DisplayService) SetTransform(output string, transform OutputTransform, opts DisplayApplyOptions) error
func (DisplayService) GetBacklight(outputName string) (int, error)
func (DisplayService) SetBacklight(outputName string, value int) error
MethodDescription
ListReturns every connected logical display with its advertised timings, current mode, physical size, layout position, scale, and transform.
GetReturns the same snapshot for one output name. Returns ErrUnknownDisplayOutput if the name doesn't match a connected output.
ApplyApplies several output changes in one batch, so multi-display kiosks stay consistent. Returns ErrNoDisplayOutputChanges for an empty batch.
SetListedModeSwitches one output to an advertised width × height (and optional refresh rate in millihertz; 0 matches the first entry for that size).
SetCustomModeDrives an output with a timing that may not appear in its advertised list.
SetPreferredModeSelects the output's preferred timing when the driver exposes one.
SetOutputEnabledTurns the compositor's video output to that display on or off.
SetLayoutSets the output's position in the global compositor layout.
SetScaleSets fractional UI scaling for that output.
SetTransformSets rotation or mirroring. Valid OutputTransform values: normal, 90, 180, 270, flipped, flipped-90, flipped-180, flipped-270.
GetBacklightReturns the backlight level (typically 0–100). Requires the display capability provider; otherwise returns UnsupportedError.
SetBacklightSets the backlight level. The value must be between 0 and 100; requires the display capability provider.

Pass DisplayApplyOptions{DryRun: true} to any setter to validate a change without committing it.

type DisplayOutput struct {
	Name             string          `json:"name"`
	Description      string          `json:"description"`
	PhysicalWidthMM  int32           `json:"physicalWidthMm"`
	PhysicalHeightMM int32           `json:"physicalHeightMm"`
	Enabled          bool            `json:"enabled"`
	Modes            []DisplayMode   `json:"modes"`
	Current          *DisplayMode    `json:"current,omitempty"`
	PositionX        int32           `json:"positionX"`
	PositionY        int32           `json:"positionY"`
	Transform        OutputTransform `json:"transform"`
	Scale            float64         `json:"scale"`
}

type DisplayMode struct {
	WidthPX   int     `json:"widthPx"`
	HeightPX  int     `json:"heightPx"`
	RefreshHz float64 `json:"refreshHz,omitempty"`
	Preferred bool    `json:"preferred"`
	IsCurrent bool    `json:"current"`
}

type DisplayOutputChange struct {
	Name         string               `json:"name"`
	On           *bool                `json:"on,omitempty"`
	ListedMode   *ListedModeSelection `json:"listedMode,omitempty"`
	CustomMode   *CustomModeSelection `json:"customMode,omitempty"`
	UsePreferred bool                 `json:"usePreferred,omitempty"`
	PositionX    *int32               `json:"positionX,omitempty"`
	PositionY    *int32               `json:"positionY,omitempty"`
	Scale        *float64             `json:"scale,omitempty"`
	Transform    *OutputTransform     `json:"transform,omitempty"`
}

Network

rt.Network() *api.NetworkService — namespace network. Generic (wired-first) network interface management. Every method requires the network capability provider from the active BSP; otherwise it returns UnsupportedError. See Runtime Extensions for how a BSP supplies one.

func (NetworkService) ListInterfaces() ([]NetworkInterface, error)
func (NetworkService) GetDefaultInterface(kind string) (NetworkDefaultInterface, error)
func (NetworkService) GetStatus(interfaceName string) (NetworkStatus, error)
func (NetworkService) ConfigureIP(req NetworkIPConfigRequest) error
func (NetworkService) SetEnabled(interfaceName string, enabled bool) error
func (NetworkService) RenewDHCP(interfaceName string) error
MethodDescription
ListInterfacesReturns every managed network interface exposed by the BSP.
GetDefaultInterfaceReturns the preferred interface for a kind. Valid kinds: ethernet, wifi, cellular, usb, loopback, unknown, or "" for any. Other values return an error.
GetStatusReturns link and IPv4 state for one interface.
ConfigureIPApplies dhcp, static, or disabled IPv4 settings. For static, the address must be a valid IPv4 address; gateway, subnet, and DNS entries are validated as IPv4 when present.
SetEnabledEnables or disables one interface.
RenewDHCPRenews the DHCP lease on one interface.

Interface names are validated against ^[A-Za-z0-9][A-Za-z0-9_.-]{0,14}$; invalid or empty names return an error before the provider is consulted.

type NetworkIPConfig struct {
	Mode    string   `json:"mode"` // "dhcp", "static", or "disabled"
	Address string   `json:"address"`
	Gateway string   `json:"gateway"`
	Subnet  string   `json:"subnet"`
	DNS     []string `json:"dns"`
}

type NetworkStatus struct {
	InterfaceName   string          `json:"interfaceName"`
	Kind            string          `json:"kind"`
	Connected       bool            `json:"connected"`
	LinkDetected    bool            `json:"linkDetected"`
	HardwareAddress string          `json:"hardwareAddress"`
	SpeedMbps       int             `json:"speedMbps"`
	Duplex          string          `json:"duplex"`
	IP              NetworkIPConfig `json:"ip"`
}

(NetworkInterface, NetworkDefaultInterface, and NetworkIPConfigRequest are exported with matching JSON field names — see pkg/runtime/api/network.go.)

Project

rt.Project() *api.ProjectService — namespace project. Metadata about the image the device is running.

func (p *ProjectService) Info() (ProjectInfo, error)

Info reads /etc/strux/project.json from the image and returns it. It returns an error if the file is missing or malformed.

type ProjectInfo struct {
	Name           string `json:"name"`
	ProjectVersion string `json:"projectVersion"`
	StruxVersion   string `json:"struxVersion"`
	BSP            string `json:"bsp"`
	Arch           string `json:"arch"`
	BuiltAt        string `json:"builtAt"`
}

Update

rt.Update() *api.UpdateService — namespace update. Read-only view of system update progress and state, written by the on-device strux-client. See Updates and the update system concept page.

Experimental

A/B (dual-rootfs) updates are experimental. The UpdateState fields describing slots and boot tries may change. See Dual Rootfs.

func (u *UpdateService) Progress() (*UpdateProgress, error)
func (u *UpdateService) State() (*UpdateState, error)
MethodDescription
ProgressReturns the latest progress from /run/strux/update-progress.json, or nil (no error) when no update has reported progress.
StateReturns persisted A/B boot state from /strux-data/strux/update-state.json, or nil (no error) when the file doesn't exist.
type UpdateProgress struct {
	Status       string `json:"status"`
	Progress     int    `json:"progress"`
	Message      string `json:"message,omitempty"`
	BytesWritten int64  `json:"bytesWritten,omitempty"`
	TotalBytes   int64  `json:"totalBytes,omitempty"`
	Slot         string `json:"slot,omitempty"`
	Version      string `json:"version,omitempty"`
	UpdatedAt    string `json:"updatedAt,omitempty"`
}

type UpdateState struct {
	Version        int    `json:"version"`
	ActiveSlot     string `json:"activeSlot"`
	PendingSlot    string `json:"pendingSlot"`
	TriesRemaining int    `json:"triesRemaining"`
	Generation     int    `json:"generation"`
	LastGoodAt     string `json:"lastGoodAt,omitempty"`
	LastError      string `json:"lastError"`
}

WiFi

rt.WiFi() *api.WiFiService — namespace wifi. Wi-Fi scanning, connection management, saved profiles, and IP configuration. Every method requires the wifi capability provider from the active BSP; otherwise it returns UnsupportedError.

func (WiFiService) ListInterfaces() ([]WiFiInterface, error)
func (WiFiService) GetDefaultInterface() (WiFiDefaultInterface, error)
func (WiFiService) GetStatus(interfaceName string) (WiFiStatus, error)
func (WiFiService) Scan(interfaceName string) ([]WiFiNetwork, error)
func (WiFiService) Connect(req WiFiConnectRequest) error
func (WiFiService) ConnectKnown(req WiFiKnownNetworkRequest) error
func (WiFiService) Disconnect(interfaceName string) error
func (WiFiService) ListKnownNetworks() ([]WiFiKnownNetwork, error)
func (WiFiService) Forget(id string) error
func (WiFiService) SetKnownNetworkPriority(id string, priority int) error
func (WiFiService) ConfigureIP(req WiFiIPConfigRequest) error
MethodDescription
ListInterfacesReturns every Wi-Fi-capable interface, with capability flags (supportsScan, supportsAPMode, supports5GHz, supports6GHz).
GetDefaultInterfaceReturns the preferred Wi-Fi adapter for apps that don't need explicit adapter selection.
GetStatusReturns connection and IPv4 state for one interface.
ScanScans for nearby access points on one interface.
ConnectConnects to an SSID, optionally with a password and BSSID. The SSID must be non-empty; SSID, password, and BSSID must not contain NUL bytes.
ConnectKnownActivates a saved profile by its stable ID on one interface.
DisconnectDisconnects one interface.
ListKnownNetworksReturns saved connection profiles.
ForgetDeletes one saved profile by ID.
SetKnownNetworkPrioritySets a saved profile's autoconnect priority.
ConfigureIPApplies dhcp or static IPv4 settings to the active connection. static requires a non-empty address.

Interface names follow the same validation as the Network service. Profile IDs must be non-empty and free of NUL bytes. All request and result structs (WiFiInterface, WiFiNetwork, WiFiStatus, WiFiKnownNetwork, WiFiConnectRequest, WiFiKnownNetworkRequest, WiFiIPConfig, WiFiIPConfigRequest, WiFiDefaultInterface) are exported from the runtime package — see pkg/runtime/api/wifi.go for their JSON field names.

Events

The runtime carries named events in both directions between Go and the frontend over a dedicated channel.

// Emit sends an event to all connected frontends.
func (rt *Runtime) Emit(event string, data interface{})

// On registers a handler for events sent from the frontend.
// It returns a handler ID for Off.
func (rt *Runtime) On(event string, handler func(data interface{})) uint64

// Off removes a previously registered handler by its ID.
func (rt *Runtime) Off(id uint64)
  • Emit JSON-encodes the payload and broadcasts it to every connected frontend. Broken connections are dropped silently. If encoding fails, the event is logged and dropped.
  • On handlers run in their own goroutine per event, so a slow handler doesn't block the event loop — synchronize shared state yourself.
  • The frontend counterpart is strux.ipc.on() / strux.ipc.off() / strux.ipc.send() — see Events in the Frontend API reference.

A common pattern from a real project (the same OS image rendering two browser views) is relaying events between frontends:

for _, evt := range []string{"video:play", "video:pause", "video:stop"} {
	e := evt
	rt.On(e, func(data any) {
		rt.Emit(e, data) // rebroadcast to all connected frontends
	})
}

The exported event types:

type EventMessage struct {
	Type  string      `json:"type"`
	Event string      `json:"event"`
	Data  interface{} `json:"data,omitempty"`
}

type EventHandler struct {
	ID       uint64
	Callback func(data interface{})
}

Provider registration (BSP extensions)

These functions let a BSP package supply the hardware-specific implementation behind the Display backlight, Network, and WiFi services. They are meant to be called from a BSP runtime extension's init() function — see Runtime Extensions for the full workflow and the extension system concept page for how extensions are wired into the build.

func RegisterDisplayProvider(provider DisplayProvider)
func RegisterNetworkProvider(provider NetworkProvider)
func RegisterWiFiProvider(provider WiFiProvider)

Each function panics if the provider is nil or if a provider for that capability is already registered — a misconfigured BSP should fail loudly at startup, not at first use. Once registered, the matching capability reports Supported: true and the service methods route to the provider.

The provider interfaces (re-exported from pkg/runtime/api):

type DisplayProvider interface {
	GetBacklight(displayName string) (int, error)
	SetBacklight(displayName string, value int) error
}

type NetworkProvider interface {
	ListInterfaces() ([]NetworkInterface, error)
	GetDefaultInterface(kind string) (NetworkDefaultInterface, error)
	GetStatus(interfaceName string) (NetworkStatus, error)
	ConfigureIP(req NetworkIPConfigRequest) error
	SetEnabled(interfaceName string, enabled bool) error
	RenewDHCP(interfaceName string) error
}

type WiFiProvider interface {
	ListInterfaces() ([]WiFiInterface, error)
	GetDefaultInterface() (WiFiDefaultInterface, error)
	GetStatus(interfaceName string) (WiFiStatus, error)
	Scan(interfaceName string) ([]WiFiNetwork, error)
	Connect(req WiFiConnectRequest) error
	ConnectKnown(req WiFiKnownNetworkRequest) error
	Disconnect(interfaceName string) error
	ListKnownNetworks() ([]WiFiKnownNetwork, error)
	Forget(id string) error
	SetKnownNetworkPriority(id string, priority int) error
	ConfigureIP(req WiFiIPConfigRequest) error
}

The capability name constants are also re-exported: runtime.CapabilityDisplay, runtime.CapabilityNetwork, runtime.CapabilityWiFi.

Custom extensions

Beyond the standard capabilities, a BSP (or your own code) can expose an arbitrary Go object's exported methods to the frontend under a namespace:

// Process-wide: every Runtime created afterwards exposes the methods.
// Intended for BSP package init() functions. Panics on invalid or duplicate registration.
func RegisterCustomExtension(name string, instance interface{})

// Process-wide, with an explicit namespace. Returns an error instead of panicking.
func RegisterExtension(namespace, subNamespace string, instance interface{}) error

// Instance-scoped: registers on one Runtime only.
func (rt *Runtime) RegisterExtension(namespace, subNamespace string, instance interface{}) error

RegisterCustomExtension("gpio", &GPIO{}) makes the methods of GPIO callable as window.strux.gpio.<Method>() in the frontend. Registration fails (error or panic) when the namespace or sub-namespace is empty, the instance is nil, or the pair is already registered. Method call semantics are the same as for app methods. See Runtime Extensions for how strux types picks these up and generates frontend types for them.

Lower-level exports

You will rarely need these, but they are part of the public API:

ExportDescription
New(app interface{}) *RuntimeCreates a Runtime (builds the binding tree, registers built-in and process-wide extensions) without starting the IPC listener. Init is New + (rt) Start.
(rt) Start() errorStarts the IPC listener on /tmp/strux-ipc.sock. Called for you by Init/Start.
(rt) GetMethodInfo() []MethodInfoMetadata (name, parameter count, parameter kinds) for the app struct's top-level bound methods.
(rt) GetFieldInfo() []FieldInfoMetadata (name, kind) for the app struct's top-level bound primitive fields.
(rt) GenerateTypeScript(outputPath string) errorWrites a TypeScript declaration file for the current bindings. The strux types command (which uses static analysis and produces richer types) is the recommended way to generate frontend types — see the Frontend API reference.
Message, Response, MethodInfo, FieldInfo, ChannelHandshakeWire-format types for the JSON-RPC style IPC protocol.
RegistryThe extension registry used internally by the Runtime.
Type aliasesAll capability data types (NetworkInterface, WiFiStatus, CapabilityInfo, …) are aliased from pkg/runtime/api into the runtime package, so user code only needs the one import.
Last Updated:: 6/13/26, 2:20 AM
Contributors: Miguel Medeiros
Prev
strux.yaml Reference
Next
Frontend API Reference