feat(trios-server): UART endpoint at /api/uart for remote serial-port access - #1082
Draft
gHashTag wants to merge 1 commit into
Draft
feat(trios-server): UART endpoint at /api/uart for remote serial-port access#1082gHashTag wants to merge 1 commit into
gHashTag wants to merge 1 commit into
Conversation
β¦ access Adds three HTTP endpoints under /api/uart, fail-closed on TRIOS_UART_TOKEN: GET /api/uart/ports list serial ports (metadata; no port opened) GET /api/uart/stream SSE stream of bytes read from a port (base64-encoded) POST /api/uart/write one-shot write of base64-encoded bytes Motivation ========== See docs/UART_ENDPOINT.md and docs/LOCAL_FLASH.md sec 1.4. Two of three P201Mini boards ship with an identical MAC/hostname/IP image, and their boot state cannot be diagnosed without a serial console. This endpoint, paired with tri-tunnel (Tailscale Funnel), lets a remote agent read the UART on the host Mac without SSH. Security ======== - Uses a separate bearer token (TRIOS_UART_TOKEN) from TRIOS_API_KEY. Git access should not automatically grant serial-port access. - The router is not mounted at all when TRIOS_UART_TOKEN is unset (fail-closed default). Startup logs the enabled/disabled state. - Global auth middleware bypasses /api/uart/* so clients only need one credential; UART's own middleware then enforces the UART token. Design notes ============ - Read = SSE with a bounded broadcast channel (capacity 1024). Slow clients see explicit `lag` events instead of silent drops. - Write = one-shot to keep server state minimal; compose with stream on the client side. - All wire bytes are base64 so control chars (Ctrl-C, Ctrl-A, ANSI escapes) survive JSON transport without shell-escaping. - Idle timeout 30s per stream; SSE emits `idle` heartbeat. - Reader lives in a dedicated OS thread (serialport is sync) and stops when all SSE subscribers disconnect. Tests ===== - router_disabled_without_token (fail-closed) - router_enabled_with_token - base64_write_body_roundtrip - control_chars_survive_base64 - default_baud_is_115200 - env_var_name_is_stable - test_uart_paths_bypass_global_auth_prefix (in security.rs) 39/39 unit tests pass. Deliberately NOT included ========================= - No convenience write-then-read endpoint (compose on client). - No multi-viewer port sharing (serialport opens exclusive). - No RTS/CTS/DTR flow-control knobs (defaults match P201Mini console). Anchor: phi^2 + phi^-2 = 3.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds three HTTP endpoints under
/api/uartontrios-serverto give a remote agent controlled access to the host's USB-serial adapters. Motivation is direct: two of three P201Mini boards ship with an identical MAC/hostname/IP image (seedocs/LOCAL_FLASH.mdΒ§1.4), and their boot state can only be diagnosed through a serial console. Paired with tri-tunnel (Tailscale Funnel), this endpoint lets a cloud agent read the UART on the host Mac without opening SSH.Endpoints
/api/uart/ports/api/uart/stream/api/uart/writeFull contract, event schema, and shell examples:
docs/UART_ENDPOINT.md.Security
TRIOS_UART_TOKEN), notTRIOS_API_KEY. Git access must not automatically grant serial-port access.TRIOS_UART_TOKENis unset, the router is not mounted at all. Startup logsUART endpoint DISABLED β set TRIOS_UART_TOKEN to enable /api/uart/*./api/uart/*(seesecurity.rschange), so clients only present one credential; UART's own middleware then enforces the UART token./writeβ control characters (Ctrl-C, Ctrl-A, ANSI escapes) survive JSON without shell-escaping games.Design decisions
lagevents instead of silent drops.serialportreader. Auto-stops when all SSE subscribers disconnect (no fd leaks).idleevents so clients can distinguish "port silent" from "connection dead".router<S>()so it can be nested intoRouter<AppState>without UART handlers knowing aboutAppState. UART handlers are stateless.Tests
39/39 unit tests pass locally (
cargo test -p trios-server --bin trios-server):router_disabled_without_tokenβ fail-closed guardrouter_enabled_with_tokenbase64_write_body_roundtripcontrol_chars_survive_base64β Ctrl-C, Ctrl-A, ESC[B survivedefault_baud_is_115200env_var_name_is_stableβ regression guard onTRIOS_UART_TOKENnametest_uart_paths_bypass_global_auth_prefixinsecurity.rsExisting 32 tests untouched.
Deliberately NOT included
write-then-readendpoint β compose on client.serialportopens exclusive; if needed, put fan-out in front locally.How to try locally
Files changed
crates/trios-server/src/uart.rsβ new module, 414 linescrates/trios-server/src/main.rsβ mount/api/uartrouter if token setcrates/trios-server/src/security.rsβ bypass/api/uart/*in global authcrates/trios-server/Cargo.tomlβ addserialport 4.7(no default features) +base64 0.22; enabletokio-stream/timedocs/UART_ENDPOINT.mdβ new, 173 linesAnchor:
phi^2 + phi^-2 = 3.