v0.3.0
LatestNew: Revamped Dev TUI
The strux dev terminal interface has been rebuilt from the ground up. The old single-file ui.tsx has been replaced with a modular component tree under src/commands/dev/ui/ (App, ResourceList, DetailPanel, ConfigPanel, StatusBar, CommandBar, LogView, TerminalView, FileTreeView) backed by a dedicated store.ts for reactive state.
Improved SSH Console:
- The remote terminal is now a full terminal emulator powered by
@xterm/headless. It renders directly to stdout via ANSI cursor positioning, bypassing Ink's renderer entirely so ncurses apps likehtop,vim, andlessrender cleanly without flicker. - Sessions can be detached with
Ctrl-\and reattached later withs. Raw PTY output is buffered to a 512 KB capped scrollback per session so reattaching replays missed output. - Sessions survive TUI navigation — you can detach from SSH, browse logs, open the config panel, and reattach to the same shell without losing state.
- Host-terminal resizes are forwarded to the remote PTY so full-screen apps reflow correctly.
Better Navigation:
- The resource list now includes nested device log streams (App, Cage, System Logs, Early Logs, Screen Logs, Client) alongside Vite, QEMU, Watcher, and Screen.
- New filter mode (
/) for log views, pause/resume watcher (p), and context-sensitive keybinds shown in the bottom command bar. - Config panel is accessible from any pane via
c, withEscto return. - The Config panel can now run host-side BSP flashing workflows and stream output into a dedicated Flash log view.
New: Host Flash Scripts
strux flash [bsp]runs BSPflash_script_toolscripts followed byflash_scriptscripts on the host, outside of the Docker builder.- Flash scripts run with
dist/flash/<bsp>as their working directory so BSPs can prepare and reuse host flashing tools in a scoped workspace. - Flash scripts receive the standard BSP script environment plus
PROJECT_DIST_FLASH_FOLDERandFLASH_DIR.
New: Dev Protocol Refactor
strux dev underwent a complete refactor of its device communication layer. The old monolithic server.ts has been split into focused modules: socket-manager.ts (typed WebSocket handling), protocol.ts (wire ↔ canonical translation), handlers/ (client, screen, update), ssh.ts, vite.ts, qemu.ts, watcher.ts, and mdns.ts.
Versioned Protocol with Backwards Compatibility:
- Clients now declare their protocol version via a
vquery param on connect. The dev server looks up aProtocolMappingfor that version and translates every incoming message from wire format to a canonical v0.3.0 shape, and every outgoing message from canonical back to wire.. - v0.2.0 clients are fully supported as a first-class protocol entry — the mapping translates old names like
new-binary↔binary-new,exec-start↔ssh-start,new-component↔component,reboot↔system-restart, andscreen-screenshot↔screen-picture, along with payload shape differences (e.g.,sessionId↔sessionID, the v0.2.0binary-ackmessagefield ↔ canonicalbinaryfield). Clients that connect without a version string fall back to the v0.2.0 protocol automatically. - This means existing devices flashed with v0.2.x images continue to work against a v0.3.0 dev server without reflashing, and adding a new protocol version is just a new entry in the
PROTOCOLSmap. - A typed
Socket<TSend, TReceive>wrapper enforces message type safety at the handler layer, making it impossible to send or dispatch a message that isn't in the protocol union.
New: Runtime Dev Mode Control
The built-in runtime API now exposes strux.dev, allowing a running production image to inspect and control its dev-mode configuration without rebuilding or reflashing first.
Runtime API:
strux.dev.GetConfig()returns whether dev mode is currently enabled and the stored dev configuration.strux.dev.SetConfig(config)writes the dev server configuration without changing the enabled state.strux.dev.SetEnabled(enabled)toggles whether the stored dev configuration is active.strux.dev.RestartService()restarts the Strux systemd service so the new mode takes effect.strux.dev.Apply(config, enabled)andstrux.dev.ApplyAndRestart(config, enabled)provide one-call helpers for writing config, toggling dev mode, and optionally restarting.
This enables production UIs to offer controlled developer access, for example a settings screen that enables dev mode on a deployed device and reconnects it to a local strux dev session.
Security warning:
- Dev mode is powerful and should be treated as a privileged maintenance feature. Once enabled, a connected dev server can replace application binaries, transfer Strux components, reboot the device, and perform workflows that can effectively reflash or take over the device.
- Do not expose
strux.devcontrols to untrusted users or unauthenticated production screens. Gate this behind your own authentication, physical access requirements, signed/admin-only flows, or device-management policy. - Be especially careful on public kiosks, customer-owned hardware, or remotely accessible networks. Leaving dev mode available without a lock can turn a production device into a writable development target.
New: Pre-built Docker Builder Image
The build system now pulls a pre-built strux-builder Docker image from GHCR (ghcr.io/strux-sh/strux-builder:<version>) instead of building it locally from a Dockerfile on every machine. This dramatically speeds up first-time setup and CLI upgrades — a docker pull replaces what was previously a full image build with cross-compilers, WebKit dev libraries, and dozens of packages.
The published image also includes the strux CLI and strux-introspect binaries, enabling direct use as a CI runner image. In CI environments like GitLab CI, you can build Strux OS images without Docker-in-Docker:
build:
image: ghcr.io/strux-sh/strux-builder:0.3.0
script:
- strux build qemu
How it works:
- When
strux buildruns, it first tries to pullghcr.io/strux-sh/strux-builder:<version>and tags it locally asstrux-builder - If the pull fails (offline, registry down, pre-release version), it falls back to building from the embedded Dockerfile — so offline development always works
- When strux detects it's running inside the builder container (via
STRUX_IN_CONTAINER=1), it runs build scripts directly instead of spawning nested Docker containers - Verbose output is auto-enabled in container/CI environments when no TTY is detected
New CLI flag:
--local-builder— Forces a local Dockerfile build instead of pulling from GHCR. Useful for offline work, custom Dockerfile modifications, or development on strux itself
New: USB Debug Ethernet for strux dev
Strux dev images can now expose a USB Ethernet gadget from the device to the development machine. This gives strux dev a direct point-to-point network path over USB while preserving the existing Vite/WebSocket/browser workflow instead of inventing a custom USB transport.
How it works:
- The Strux client configures a Linux configfs USB gadget at boot with a fixed identity: vendor ID
0x1209, product ID0x5358, manufacturerStrux, and product nameStrux USB Debug. - The gadget exposes a USB Ethernet interface (
usb0) and starts an embedded DHCP responder usinggithub.com/insomniacslk/dhcp. dev.usb.subnetcontrols the point-to-point subnet. The device uses the second usable address and leases the first usable address to the host. For example,192.168.7.0/24gives the host192.168.7.1and the device192.168.7.2.- When USB setup succeeds, the client prioritizes the USB host address for dev server discovery and disables mDNS for that session. If USB setup fails, the client logs the reason and falls back to the existing network discovery path.
Configuration:
dev:
usb:
enabled: true
subnet: 192.168.7.0/24
BSP requirements:
- USB debug requires a USB peripheral/OTG-capable port. Host-only USB ports cannot expose the device as a USB Ethernet adapter.
- The BSP kernel must enable USB gadget/configfs support and at least one USB Ethernet function. For macOS/Linux hosts, enable ECM or NCM; for Windows hosts, enable RNDIS.
- The default BSP template now documents the required kernel fragment options:
CONFIG_USB_GADGET,CONFIG_USB_LIBCOMPOSITE,CONFIG_USB_CONFIGFS,CONFIG_USB_CONFIGFS_ECM,CONFIG_USB_CONFIGFS_NCM, andCONFIG_USB_CONFIGFS_RNDIS.
New: Project Build Scripts
Projects can now run their own build scripts against the assembled root filesystem, without modifying the shared BSP. This is the home for app-specific image customization — installing a tool that isn't packaged, dropping in a binary, or running a one-off chroot step — that doesn't belong in a board support package shared across projects.
Scripts are declared in strux.yaml and run at the new rootfs_post step, after the built-in rootfs post-processing and before image bundling:
scripts:
- location: ./scripts/install-yt-dlp.sh
step: rootfs_post
description: "Install latest yt-dlp from GitHub releases"
# Optional caching, same rules as BSP scripts:
# depends_on: [./scripts/install-yt-dlp.sh]
# cached_generated_artifacts: [...] # omit to always run
Managed rootfs context:
- The harness extracts
rootfs-post.tar.gz, mounts it forchroot(including the cross-arch QEMU static binary), runs the script, then repacksrootfs-post.tar.gzin place — so the BSP'sbefore_bundle/make_imagestages and the image transparently pick up the changes with no BSP edits. - The repack only happens on success: a failing project script aborts the build and leaves the rootfs untouched.
- Because the repack is in-place,
rootfs_postscripts must be idempotent (overwrite, don't append) — when therootfs-postcache is warm the script may run against a rootfs that already contains its own previous output.
Helpers and environment:
- Scripts get the full build environment (
TARGET_ARCH,HOST_ARCH,BSP_NAME,PROJECT_NAME,PROJECT_VERSION,STRUX_VERSION, splash/display vars) plus path variables (PROJECT_DIR,BSP_CACHE_DIR, …). $ROOTFS_DIRpoints at the extracted rootfs, and these helper functions are provided:run_in_chroot/strux_chroot(run a command inside the image),strux_install_file <src> <abs-dest> [mode](copy a host file into the image, creating parent dirs), andstrux_progress/strux_progress_bar(drive the CLI progress display, withprogresskept as an alias).
Caching:
- Project scripts reuse the existing hash-based script cache. Declaring
cached_generated_artifactslets a script be skipped when its outputs exist and inputs are unchanged; omitting it (the default) runs the script every build — ideal for "always fetch the latest" steps.
rootfs_post is the only step available today. More project lifecycle steps will be added in future releases.
Minor Changes
- Added
hostas a BSP architecture option. Whenarch: hostis set inbsp.yaml, the build targets the host machine's native architecture instead of a hardcoded value. New projects created withstrux initnow default toarch: hostinstead of baking in the specific host architecture at init time. - Local builds (
bun run build) now read the version frompackage.jsoninstead of falling back to0.0.1. CI builds are unaffected — the--defineflag still takes precedence. - Cog browser is now compiled from source during the build process. The Debian-packaged Cog 0.18.5 lacks support for configuring the WebKit autoplay policy, which was added in Cog 0.19.1 (not available in any Debian repository). The build now clones Cog 0.18.5 from source, applies a backported patch that adds the
--autoplay-policyCLI flag, and cross-compiles it alongside the WPE extension. The patched binary is installed over the Debian package version. The Cog launch script (strux-run-cog.sh) now passes--autoplay-policy=allow, permitting unmuted media autoplay without requiring a user gesture. - Cage output orientation can now be configured per monitor with
display.monitors[].transforminstrux.yaml, or board-wide withSTRUX_OUTPUT_TRANSFORMinbsp.yamlcage.env. Supported transforms arenormal,90,180,270,flipped,flipped-90,flipped-180, andflipped-270. The transform is applied when Cage enables the wlroots output, and the early framebuffer splash also honors90,180, and270so the splash and browser UI share the same orientation. - Added
STRUX_PROGRESS_BAR: <message> (<percent>%)script output markers. BSP and host scripts can emit these markers to render colored CLI progress bars while keeping regular tool output raw. - Verbose Docker build output now still consumes
STRUX_PROGRESS:andSTRUX_PROGRESS_BAR:markers, replacing them with the same formatted Strux progress messages used in non-verbose mode instead of printing the raw marker lines. - Kernel and bootloader
source:refs inbsp.yamlcan now be pinned to exact commit hashes (e.g.https://github.com/rockchip-linux/kernel.git#<sha>), not just branches and tags. The fetch scripts previously relied ongit clone --depth 1 --branch <ref>, which fails for commit hashes; they now fall back to a shallow fetch of the specific commit (supported by GitHub viauploadpack.allowReachableSHA1InWant), and finally to a full clone + checkout for servers that don't allow fetching by SHA. This keeps BSP builds reproducible against vendor-validated upstream commits without tracking a moving branch.
Cage touch input rotation fix:
-
Touch devices mapped to a rotated Cage output now have their normalized coordinates transformed with the output's wlroots transform before Cage performs surface hit-testing. This keeps touchscreen input aligned with the visible browser UI when
display.monitors[].transformorSTRUX_OUTPUT_TRANSFORMrotates the output, while preserving the previous coordinate path for unrotated outputs. -
Fixed cached
strux build <bsp> --devbuilds not enabling dev mode in the generated image. The build now refreshes the BSP-specific.dev-env.jsoneven when the client binary is cached, removes stale dev config for cached production builds, and makesrootfs-postdepend on the dev config file so switching between dev and production rebuilds the image contents correctly. -
Fixed BSP lifecycle scripts running before
dist/artifacts/logo.pngexists on fresh builds. Initial artifacts are now prepared before BSP hooks run, so scripts such asbefore_rootfscan safely read the copied splash logo and Plymouth files. -
Fixed changes to
project_version(andname) instrux.yamlnot taking effect in cached builds. These fields are written into/etc/strux/project.jsonduring therootfs-poststep but weren't tracked as cache dependencies, so editing only the version left the step cached and the image kept the staleproject.json— causing the runtimestrux.project.Info()value (and any UI reading it) to show the old version. Therootfs-poststep now depends on both keys.