Styrene Provisioning Vision

A Reticulum-native fleet provisioning system for edge devices.

Status: Active — Last aligned with implementation 2026-02-19

Architecture Reference: See architecture-decisions.md for high-level decisions and document hierarchy.

Overview

Styrene Provisioning provides robust, mesh-connected provisioning and fleet management for edge devices. The system consists of:

  1. styrened — Base layer daemon + library + TUI, installed on every device (edge, operator, hub). The TUI is bundled as styrened.tui subpackage, installed via [tui] extra.
  2. styrene-edge — Upstream device support repo (NixOS configs, device profiles, polymerize scripts — fetched at runtime by TUI)
  3. Reticulum Mesh — LXMF communication with custom Styrene wire protocol

Architecture Note: styrened is the base layer on every device. The TUI is an optional UI layer that some devices get. On operator workstations, the TUI manages styrened as a subprocess (no permanent systemd service required). On edge devices, styrened runs as a permanent systemd service. The IPC contract (Unix socket) is the same in both cases.

Distribution Note: Packages are kept as isolated dependencies with coupling at the package boundary (pip dependency + IPC/wire protocol). This enables future rewrites of individual components in other languages (e.g., Rust) — a new implementation only needs to honor the wire protocol and socket contract, not maintain Python API compatibility.

Architecture

System overview

Components

styrened (Unified Library + Daemon)

The core Styrene package providing both library functionality and headless daemon.

Design principle — infrastructure-by-default: Every styrened instance is a full LXMF peer on the mesh, acting as a transport participant and propagation point simply by running. Most mesh users connect transiently; styrened devices are persistent infrastructure, growing mesh reliability as a side effect of fleet operations. Standard LXMF clients (Sideband, MeshChat, NomadNet) see styrened as a normal messaging peer — the Styrene wire protocol rides inside LXMF FIELD_CUSTOM_TYPE, invisible to non-Styrene participants.

Package: styrened (GitHub)

As Library (used by styrened.tui):

  • RPC client/server implementations
  • Wire protocol encoding/decoding
  • Message models and data structures
  • Service lifecycle management
  • Config management

As Daemon (runs on edge devices):

  • RPCServer — Handles incoming fleet management requests
  • AutoReplyHandler — Responds to NomadNet/MeshChat users
  • Device Discovery + NodeStore — Tracks mesh topology via RNS announces (persistent)
  • ConversationService — Chat history, message delivery tracking, threading
  • ReadReceiptProtocol — Ecosystem compatibility with Sideband/MeshChat
  • IPC Control Server — Unix socket for local CLI/TUI communication
  • TerminalService — Remote shell access via PTY over RNS Link
  • HTTP API — Optional REST endpoints for status/control

Installation:

pipx install styrene                    # Recommended (daemon + TUI, isolated env)
pip install styrened                    # Daemon only (no TUI, for edge devices)
nix run github:styrene-lab/styrened     # Nix flake

styrened.tui (Operator Interface)

The single operator interface for Styrene — provisioning, fleet management, and mesh communication in one TUI. Bundled as a subpackage of styrened, installed via pipx install styrene.

Note: The standalone styrene-tui repo is archived. The TUI now lives at styrened.tui.

Daemon Lifecycle: On operator workstations, the TUI manages styrened as a subprocess — starts it on launch, tears it down on exit. No permanent systemd service required. On edge devices where styrened runs as a systemd service, the TUI connects to the existing daemon via IPC. The Unix socket contract is the same in both cases.

Capabilities:

  • Forge provisioning (absorbed) — media writer, disk detection, bundle builder, device catalog
  • LXMF inbox/chat — replaces NomadNet client and MeshChat for operators; full LXMF ecosystem compatibility
  • Fleet management — dashboard with live device status, RPC commands (status, exec, reboot, config)
  • Device detection (USB, SD, SATA, NVMe)
  • Device profiles fetched from styrene-edge at runtime (GitHub, release artifacts, or Cachix)
  • [TODO] Browse Styrene Hub via NomadNet protocol

styrene-edge (Device Support Repository)

styrene-edge is an upstream device support repo — the equivalent of a firmware tree or board support package. Contributors work here to add new device patterns; operators never clone it.

Repository: styrene-edge (GitHub)

Components:

  • sbc/<device>/configuration.nix — Per-device NixOS configurations
  • sbc/common/ — Shared NixOS modules (base.nix, styrened.nix, batman-mesh.nix)
  • sbc/flake.nix — Nix flake producing SD card images via nix build
  • forge/data/devices.yaml — Device catalog (specs, boot types, config paths)
  • sbc/<device>/polymerize.sh — Per-device installer scripts (x86)

Runtime consumption: The TUI fetches device profiles and NixOS configs from styrene-edge at runtime, the same way nix build github:styrene-lab/styrene-edge#rpi4-sd resolves the flake reference remotely. When a contributor merges a new device PR, it appears in the TUI’s catalog on next fetch.

Polymerize scripts (polymerize.sh):

  • Per-device-type installer scripts in sbc/<device>/polymerize.sh
  • Semi-attended NixOS installation: partition, format, nixos-install
  • Reads injected config from USB media bundle
  • Supports WiFi auto-config, SSH key injection, hostname baking

SD card images (ARM):

  • sbc/flake.nix produces direct-boot SD images via nix build
  • Cachix binary cache (styrene.cachix.org) serves pre-built aarch64 images
  • No installer script needed — SD card IS the root filesystem

Fleet Communication (Styrene Wire Protocol)

Devices communicate using a custom Styrene wire protocol over LXMF:

Wire Format v2:

[PREFIX:11][VERSION:1][TYPE:1][REQUEST_ID:16][PAYLOAD:N]
PREFIX: b"styrene.io:"
VERSION: 2
TYPE: StyreneMessageType enum (0x00-0xFF)
REQUEST_ID: 16 bytes for correlation (random or NO_CORRELATION)
PAYLOAD: msgpack-encoded data

Message Type Ranges:

  • 0x01-0x0F: Control (PING, PONG, HEARTBEAT)
  • 0x10-0x1F: Status (STATUS_REQUEST/RESPONSE)
  • 0x20-0x2F: Content (CHAT, FILE_*)
  • 0x30-0x3F: Network (ANNOUNCE, PEER_*)
  • 0x40-0x5F: RPC Commands (EXEC, REBOOT, CONFIG_UPDATE)
  • 0x60-0x7F: RPC Responses
  • 0x80-0x9F: Hub Services (REGISTRY_, FLEET_STATUS_)
  • 0xA0-0xBF: Pub/Sub (SUBSCRIBE, PUBLISH)
  • 0xC0-0xCF: Terminal Sessions (TERMINAL_REQUEST/ACCEPT/REJECT)
  • 0xD8-0xDE: Tunnel Negotiation (TUNNEL_OFFER/ACCEPT/REKEY) — see mesh-vpn-architecture

Why Custom Protocol (not “native Reticulum”):

  • Request/response correlation via 16-byte request_id
  • Typed messages with structured payloads
  • Security limits (MAX_PAYLOAD_SIZE, strict msgpack parsing)
  • Terminal sessions with PTY over RNS Link (implemented)

Styrene Hub (NomadNet Node)

The fleet’s configuration and identity authority, hosted on Reticulum. Acts as a propagation node for mesh communications and device discovery.

The Styrene Community Hub is the planned first public release — an LXMF propagation node, RNS transport node, NomadNet page server, and styrened fleet manager bundled into a single deployable unit. No existing project packages all of these together; the Hub establishes Styrene’s presence in the Reticulum ecosystem before fleet management features are complete.

Hosted Content:

  • Device specifications (partition layouts, bootloader requirements)
  • NixOS configuration templates
  • Fleet manifests (which devices belong, their roles)
  • Identity registry (authorized devices and operators)
  • ISO hashes for integrity verification

Hub Role in RPC:

  • Propagation node for LXMF messages between TUI and devices
  • Discovery endpoint for finding fleet devices on the mesh
  • Does NOT intercept or proxy RPC calls (direct TUI ↔ Device communication)
  • Provides authoritative device registry for fleet membership

Why NomadNet:

  • Content replicates across the mesh (resilient)
  • Accessible over any Reticulum transport (WiFi, LoRa, packet radio)
  • Pages are simple to author and update
  • MeshChat already demonstrates the pattern

Fleet Communication Protocol

Fleet communication uses the Styrene wire protocol over LXMF. Messages are binary-encoded with msgpack payloads.

Wire Format v2

LXMF Message:
  FIELD_CUSTOM_TYPE (0xFB): b"styrene.io"
  FIELD_CUSTOM_DATA (0xFC): [PREFIX][VERSION][TYPE][REQUEST_ID][PAYLOAD]

Wire Data Layout:
  PREFIX:     b"styrene.io:" (11 bytes)
  VERSION:    0x02 (1 byte)
  TYPE:       StyreneMessageType enum (1 byte)
  REQUEST_ID: 16 random bytes (for correlation)
  PAYLOAD:    msgpack-encoded data (variable)

Request/Response Correlation

  • Request ID Generation: os.urandom(16) — 16 random bytes (NOT UUID)
  • Response Echoing: Device echoes the request_id in its response
  • Fire-and-forget: Use 16 zero bytes (NO_CORRELATION) for one-way messages
  • Timeout: 30-second default, raises RPCTimeoutError if exceeded
# TUI side (RPCClient)
request_id = os.urandom(16)  # 16 random bytes
envelope = StyreneEnvelope(
    version=2,
    message_type=StyreneMessageType.STATUS_REQUEST,
    payload=b"",
    request_id=request_id,
)
await protocol.send_typed_message(device_hash, envelope)
response = await asyncio.wait_for(pending[request_id], timeout=30.0)

# Device side (RPCServer)
# Receives envelope, echoes request_id in response
response_envelope = create_status_response(status_data, request_id=envelope.request_id)
await protocol.send_typed_message(source_hash, response_envelope)

Message Type Examples

STATUS_REQUEST (0x10) / STATUS_RESPONSE (0x11):

Request:  [styrene.io:][0x02][0x10][<16-byte-id>][<empty>]
Response: [styrene.io:][0x02][0x11][<16-byte-id>][msgpack({
            "uptime": 123456,
            "ip": "192.168.0.101",
            "services": ["reticulum", "styrened"],
            "disk_used": 4200000000,
            "disk_total": 28000000000
          })]

EXEC (0x40) / EXEC_RESULT (0x60):

Request:  [styrene.io:][0x02][0x40][<16-byte-id>][msgpack({
            "command": "systemctl",
            "args": ["status", "reticulum"]
          })]
Response: [styrene.io:][0x02][0x60][<16-byte-id>][msgpack({
            "exit_code": 0,
            "stdout": "● reticulum.service...",
            "stderr": ""
          })]

Security Model

  • All messages encrypted end-to-end (Reticulum identity keys)
  • Devices only accept commands from authorized operator identities
  • Identity authorization configured at provision time
  • Optional: Hub as identity authority for fleet-wide policies

Data Flow

Provisioning a New Device (x86 — USB Installer)

1. Operator: Launch styrene (TUI)
2. TUI: Fetch device catalog from styrene-edge (remote), detect USB drives
3. Operator: Select target device (e.g., "minigmk", "t100ta")
4. Operator: Configure hostname, WiFi, SSH keys
5. TUI (Forge): Download NixOS ISO, write bootable USB, inject configs + polymerize.sh
6. Operator: Boot target from USB, run polymerize.sh
7. polymerize.sh: Partition, format, nixos-install (semi-attended)
8. Device: Reboot into installed NixOS with styrened
9. Device: Reticulum service starts, announces identity
10. TUI: Device appears in fleet dashboard

Provisioning a New Device (ARM — Direct SD)

1. Operator: Launch styrene (TUI), select ARM device from catalog
2. TUI (Forge): Build SD image via nix build github:styrene-lab/styrene-edge#rpi4-sd
   (fetches from Cachix if available — no cross-compilation or local clone needed)
3. TUI (Forge): Write to SD card with media writer
4. Insert SD, power on — NixOS boots directly
5. Device: styrened starts, announces on mesh
6. TUI: Device appears in fleet dashboard

Managing Existing Device

1. TUI: Show fleet dashboard (devices from local inventory + live status)
2. Operator: Select device
3. TUI: Send status_request via LXMF
4. Device: Respond with status_response
5. Operator: View status, optionally send commands
6. TUI → Device: LXMF message with command
7. Device: Execute, respond with result

Bandwidth Considerations

ContentSizeTransport
Device specs~1 KBReticulum
NixOS configs5-50 KBReticulum
WiFi/key templates<1 KBReticulum
Device identity seed32 bytesReticulum
LXMF messages<1 KBReticulum
Status updates<100 bytes eachReticulum
NixOS ISO~800 MBInternet (hash from Hub)
Nix packagesVariableInternet (cache.nixos.org)
SD card images~1-2 GBInternet (styrene.cachix.org)

The mesh handles control plane traffic. Data plane (large downloads) uses internet. SD card images for ARM SBCs are served from the Cachix binary cache (styrene.cachix.org) so any machine can fetch them without cross-compilation.

Implementation Phases

Phase 1: Local Provisioner — LARGELY COMPLETE

TUI-driven workflow with integrated Forge for media preparation and polymerize scripts for installation.

Forge components (absorbed into styrened.tui):

  • ✓ Device catalog with specs (minigmk, t100ta, q502l, rpi4, rpi-zero2w, rg35xx-h, r36s)
  • ✓ USB media writer (ISO download, partition, config injection)
  • ✓ Bundle builder (configs + polymerize.sh + WiFi + SSH keys)
  • ✓ Per-device polymerize.sh scripts (x86-generic, t100ta)
  • ✓ NixOS SD card images via Nix flake (rpi4, rpi-zero2w, rg35xx-h, r36s)
  • ✓ Cachix binary cache for pre-built aarch64 images
  • ✓ Install runbook tracking (versioned runbooks + result records)

styrened.tui:

  • ✓ Textual TUI with device selection and fleet dashboard
  • ✓ Fleet inventory display
  • ⚠️ Device catalog fetch from styrene-edge at runtime (not yet implemented — currently bundled)

Deliverable: End-to-end provisioning from a single TUI — LARGELY COMPLETE

Phase 2: Reticulum Integration ✅ COMPLETE

Mesh connectivity for fleet communication is implemented.

Completed:

  • ✓ LXMF message layer (send/receive via LXMFService)
  • ✓ RPC protocol (request/response correlation, timeouts, errors)
  • ✓ Wire protocol v2 (16-byte request_id, msgpack payloads)
  • ✓ Message models (StatusRequest/Response, ExecCommand/Result, etc.)
  • RPCServer (handles STATUS_REQUEST, EXEC, REBOOT, CONFIG_UPDATE, PING)
  • RPCClient (send requests, await correlated responses)
  • StyreneProtocol (typed message dispatch, handler registration)
  • AutoReplyHandler (respond to NomadNet/MeshChat users)
  • Device Discovery (RNS announce parsing, mesh topology tracking)
  • IPC Control Server (Unix socket for local CLI/daemon communication)
  • ConversationService (chat history, delivery tracking, threading, attachments)
  • ReadReceiptProtocol (ecosystem compatibility with Sideband/MeshChat)
  • NodeStore (persistent device discovery, identity→hash mapping)
  • TerminalService (remote shell via PTY over RNS Link)
  • IdentityConfig (display_name, icon for ecosystem compatibility)
  • ⚠️ Pub/Sub messages (defined in wire protocol, not yet implemented)

Still TODO:

  • Hub integration (browse/download device specs from NomadNet)
  • Inject device Reticulum identity at provision time
  • Authorization checking (verify operator identity on devices)

Deliverable: Live fleet status over mesh. ✅ ACHIEVED

Phase 3: Full Fleet Management

Complete fleet operations through the mesh.

TUI:

  • Fleet dashboard with live status
  • Batch operations (update all, reboot all)
  • Configuration management and rollback
  • Audit logging

Hub:

  • Device registry with status tracking
  • Config versioning
  • Fleet policies

Deliverable: Complete fleet management without SSH.

Supported Devices

Active Devices

DeviceArchitectureBootMediaNotes
MiniGMK (x86-generic)linux/amd64UEFI 64-bitUSB installerGMKtec NucBox G3 Plus, primary x86 target
Q502L (x86-generic)linux/amd64UEFI 64-bitUSB installerASUS Q502LA laptop
T100TAlinux/amd64UEFI 32-bitUSB installerBay Trail tablet, needs BOOTIA32.EFI, 2GB RAM
Raspberry Pi 4linux/arm64U-BootDirect SDNix flake SD image, Cachix cached
Raspberry Pi Zero 2Wlinux/arm64U-BootDirect SDNix flake SD image, constrained (512MB RAM)
RG35XX Hlinux/arm64U-BootDirect SDAnbernic gaming handheld, Allwinner H700, WiFi 5 — primary handheld target
R36Slinux/arm64U-BootDirect SDPowKiddy gaming handheld, RK3326, no WiFi (USB OTG)

Future Targets

DeviceArchitectureNotes
Libre Boardlinux/arm64Custom SBC
Generic ARM64linux/arm64Any UEFI ARM64

Layer 2 Mesh: BATMAN-adv

Reticulum operates as an overlay network, agnostic to the underlying transport. For local fleet connectivity, we use BATMAN-adv (Better Approach to Mobile Ad-hoc Networking - Advanced) to provide Layer 2 mesh networking.

Why Two Layers?

LayerProtocolPurpose
L2 MeshBATMAN-advPhysical connectivity - makes multiple WiFi nodes act as one switched network
OverlayReticulumLogical connectivity - encrypted, identity-based routing independent of IP

They are complementary, not competing:

  • BATMAN-adv handles: “How do I get an Ethernet frame from radio A to radio B across 3 hops?”
  • Reticulum handles: “How do I send an encrypted message to cryptographic identity X?”

Reticulum sees BATMAN-adv’s bat0 interface as just another network interface.

Network Architecture

Fleet network topology

Role Separation

DeviceBATMAN-adv RoleReticulum RoleNotes
Hub Node (RPi4B)GatewayTransport Node + HubBridges mesh to LAN, runs full RNS
Mesh Router (Opal)NodeNone (or lightweight)Dedicated L2 mesh, low power
Fleet DeviceClientRNS + LXMFJoins mesh, standard Reticulum

Styrix Configuration

Styrix (NixOS edge flavor) fleet devices use the batman-mesh.nix module from styrene-edge/sbc/common/:

{ config, lib, ... }:

{
  imports = [ ../common/batman-mesh.nix ];

  styrene.mesh = {
    enable = true;
    meshId = "styrene";
    meshKey = lib.mkDefault "changeme-styrene-fleet";
    gwMode = "client";  # or "server" for hub, "off" for offline
  };
}

The module handles kernel module loading, batctl installation, NetworkManager 802.11s mesh profile creation, and a systemd service to attach the interface to bat0.

Implementation Notes

Phase 1: Devices connect via standard WiFi (no BATMAN-adv yet). Reticulum uses AutoInterface on wlan0.

Phase 2: Add BATMAN-adv to fleet devices. Reticulum’s AutoInterface switches to bat0. Mesh router (Opal) provides L2 backbone.

Phase 3: Add LoRa gateways for long-range/offline scenarios. Reticulum automatically discovers and uses multiple transports.

Open Questions

  1. Hub hosting: Run on existing infrastructure (brutus K8s) or dedicated device?

    • K8s: Easy to deploy, but tied to LAN
    • Dedicated: Can have LoRa interface for true off-grid
  2. Identity lifecycle: What happens when a device is decommissioned?

    • Revocation list on Hub?
    • Time-limited credentials?
  3. Offline provisioning: How to handle fully air-gapped scenarios?

    • Pre-cache everything on USB?
    • See SOPS-NIX-ADOPTION.md for secrets handling in airgapped environments

References

Revision History

DateChange
2025-01-19Initial vision document
2025-01-19Added protobuf-over-LXMF wire protocol, RNS subprocess architecture
2025-01-20Added BATMAN-adv Layer 2 mesh architecture section
2025-01-20Clarified Polymerize = installer scripts (not agent), fleet ops via native Reticulum/LXMF
2025-01-21Documented request_id correlation mechanism for async request/response over LXMF
2025-01-21Marked Phase 2 partial completion (TUI-side RPC complete), clarified Hub as propagation node
2026-02-01MAJOR UPDATE: Aligned document with actual implementation
— Architecture: 2-package model (styrened + styrene-tui), not 3-component
— styrened IS a daemon (contradicts original “no agent” stance) — correct decision
— Phase 2 marked COMPLETE (RPCServer, RPCClient, wire protocol v2, IPC, discovery)
— Wire protocol: custom Styrene format over LXMF, not “native Reticulum”
— Polymerize scripts: marked as NOT YET IMPLEMENTED
— Added: Terminal session messages, Pub/Sub (defined but not implemented)
— Removed duplicate revision entries
2026-02-01v0.4.0+ updates: TerminalService now IMPLEMENTED; added ConversationService,
ReadReceiptProtocol, NodeStore, IdentityConfig (display_name/icon)
2026-02-13Added infrastructure-by-default design principle, Community Hub as first public release
2026-02-18MAJOR UPDATE: Aligned with styrene-edge implementation
— Phase 1 updated: Styrene Forge replaces planned shell scripts
— Added Forge components (media_writer, bundle_builder, device catalog)
— Added Cachix binary cache for aarch64 SD images
— Updated data flow: separate x86 (USB installer) and ARM (direct SD) paths
— Device table updated: added minigmk, q502l as active devices
— BATMAN NixOS snippet updated to use batman-mesh.nix module
— polymerize.sh scripts documented (renamed from styrene-bond.sh)
— styrened version references updated to v0.4.0+
2026-02-18Named NixOS edge flavor Styrix (Styrax + Nix); renamed “Bond” to “Polymerize”
2026-02-19Distribution architecture overhaul: TUI absorbs Forge, manages styrened lifecycle
— styrene-tui is single operator interface (provisioning + chat + fleet management)
— styrene-edge becomes upstream device support repo (fetched at runtime)
— Revised data flow: TUI fetches device profiles remotely, no local clone needed
— Added gaming handhelds (RG35XX H, R36S) to device table
— Reticulum Hub uses styrened as dependency (not standalone NomadNet)

Graph