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:
- styrened — Base layer daemon + library + TUI, installed on every device (edge, operator, hub). The TUI is bundled as
styrened.tuisubpackage, installed via[tui]extra. - styrene-edge — Upstream device support repo (NixOS configs, device profiles, polymerize scripts — fetched at runtime by TUI)
- 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

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-tuirepo is archived. The TUI now lives atstyrened.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 configurationssbc/common/— Shared NixOS modules (base.nix, styrened.nix, batman-mesh.nix)sbc/flake.nix— Nix flake producing SD card images vianix buildforge/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.nixproduces direct-boot SD images vianix 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_idin its response - Fire-and-forget: Use 16 zero bytes (
NO_CORRELATION) for one-way messages - Timeout: 30-second default, raises
RPCTimeoutErrorif 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
| Content | Size | Transport |
|---|---|---|
| Device specs | ~1 KB | Reticulum |
| NixOS configs | 5-50 KB | Reticulum |
| WiFi/key templates | <1 KB | Reticulum |
| Device identity seed | 32 bytes | Reticulum |
| LXMF messages | <1 KB | Reticulum |
| Status updates | <100 bytes each | Reticulum |
| NixOS ISO | ~800 MB | Internet (hash from Hub) |
| Nix packages | Variable | Internet (cache.nixos.org) |
| SD card images | ~1-2 GB | Internet (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
| Device | Architecture | Boot | Media | Notes |
|---|---|---|---|---|
| MiniGMK (x86-generic) | linux/amd64 | UEFI 64-bit | USB installer | GMKtec NucBox G3 Plus, primary x86 target |
| Q502L (x86-generic) | linux/amd64 | UEFI 64-bit | USB installer | ASUS Q502LA laptop |
| T100TA | linux/amd64 | UEFI 32-bit | USB installer | Bay Trail tablet, needs BOOTIA32.EFI, 2GB RAM |
| Raspberry Pi 4 | linux/arm64 | U-Boot | Direct SD | Nix flake SD image, Cachix cached |
| Raspberry Pi Zero 2W | linux/arm64 | U-Boot | Direct SD | Nix flake SD image, constrained (512MB RAM) |
| RG35XX H | linux/arm64 | U-Boot | Direct SD | Anbernic gaming handheld, Allwinner H700, WiFi 5 — primary handheld target |
| R36S | linux/arm64 | U-Boot | Direct SD | PowKiddy gaming handheld, RK3326, no WiFi (USB OTG) |
Future Targets
| Device | Architecture | Notes |
|---|---|---|
| Libre Board | linux/arm64 | Custom SBC |
| Generic ARM64 | linux/arm64 | Any 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?
| Layer | Protocol | Purpose |
|---|---|---|
| L2 Mesh | BATMAN-adv | Physical connectivity - makes multiple WiFi nodes act as one switched network |
| Overlay | Reticulum | Logical 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

Role Separation
| Device | BATMAN-adv Role | Reticulum Role | Notes |
|---|---|---|---|
| Hub Node (RPi4B) | Gateway | Transport Node + Hub | Bridges mesh to LAN, runs full RNS |
| Mesh Router (Opal) | Node | None (or lightweight) | Dedicated L2 mesh, low power |
| Fleet Device | Client | RNS + LXMF | Joins 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
-
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
-
Identity lifecycle: What happens when a device is decommissioned?
- Revocation list on Hub?
- Time-limited credentials?
-
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
- BATMAN-adv Documentation
- Reticulum Network Stack
- MeshChat - Pattern for integrated mesh + TUI tooling
- NomadNet - Decentralized pages over Reticulum
- LXMF - Messaging protocol for Reticulum
- Textual - Python TUI framework
Revision History
| Date | Change |
|---|---|
| 2025-01-19 | Initial vision document |
| 2025-01-19 | Added protobuf-over-LXMF wire protocol, RNS subprocess architecture |
| 2025-01-20 | Added BATMAN-adv Layer 2 mesh architecture section |
| 2025-01-20 | Clarified Polymerize = installer scripts (not agent), fleet ops via native Reticulum/LXMF |
| 2025-01-21 | Documented request_id correlation mechanism for async request/response over LXMF |
| 2025-01-21 | Marked Phase 2 partial completion (TUI-side RPC complete), clarified Hub as propagation node |
| 2026-02-01 | MAJOR 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-01 | v0.4.0+ updates: TerminalService now IMPLEMENTED; added ConversationService, |
| ReadReceiptProtocol, NodeStore, IdentityConfig (display_name/icon) | |
| 2026-02-13 | Added infrastructure-by-default design principle, Community Hub as first public release |
| 2026-02-18 | MAJOR 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-18 | Named NixOS edge flavor Styrix (Styrax + Nix); renamed “Bond” to “Polymerize” |
| 2026-02-19 | Distribution 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) |