4.5 KiB
Tronbyt Toes App — Implementation Plan
What This Is
A toes app that wraps the Tronbyt Go server (self-hosted Tidbyt replacement) as a managed subprocess. Bun proxies all HTTP and WebSocket traffic to the Go binary over a unix socket. The Tidbyt device talks to tronbyt.toes.local and everything chains through.
Tidbyt device
→ tronbyt.toes.local (toes proxy)
→ Bun app on PORT (this app)
→ Go binary on unix socket
File Structure
tronbyt/
package.json
index.tsx # re-exports src/server
src/
server/
index.tsx # Hype app: spawns Go binary, proxies all traffic
pages/
index.tsx # Hype page (not needed, Go serves its own UI)
bin/
.gitkeep
tronbyt-server # pre-built binary (gitignored, per-platform)
docs/
PLAN.md # this file
Implementation Steps
1. Binary setup
- Download the right binary from https://github.com/tronbyt/server/releases
- Mac dev:
tronbyt-server-darwin-arm64 - Pi production:
tronbyt-server-linux-arm64
- Mac dev:
- Place in
bin/tronbyt-server, make executable (chmod +x) - Add
bin/tronbyt-serverto.gitignore - On macOS: System Settings → Privacy & Security → Allow Anyway
2. Spawn the Go binary
On server startup, spawn bin/tronbyt-server with these env vars:
| Env Var | Value | Why |
|---|---|---|
TRONBYT_UNIX_SOCKET |
{DATA_DIR}/tronbyt.sock |
Avoids port conflicts, clean proxying |
DATA_DIR |
{DATA_DIR} |
Persists DB, apps, firmware across deploys |
DB_DSN |
{DATA_DIR}/tronbyt.db |
Explicit DB path |
PRODUCTION |
false |
Skip firmware downloads (set true on Pi later) |
SINGLE_USER_AUTO_LOGIN |
true |
Home network, no login needed |
SYSTEM_APPS_AUTO_REFRESH |
true |
Keep community apps updated |
Wait for health by polling the unix socket at /health until it returns 200.
3. HTTP proxy
Proxy all requests from Bun → Go over the unix socket:
app.all('*', async (c) => {
const resp = await fetch(socketUrl + c.req.path + search, {
method: c.req.method,
headers: c.req.raw.headers,
body: c.req.raw.body,
unix: socketPath,
})
return new Response(resp.body, resp)
})
Bun's fetch supports unix option natively — no extra libraries needed.
4. WebSocket proxy
Two WS endpoints to proxy:
GET /ws— dashboard real-time updatesGET /{deviceId}/ws— device push connection
Use Bun.serve's websocket support to accept the upgrade on the Bun side, then open a WebSocket to the Go server over the unix socket and relay messages bidirectionally.
5. Health check
app.get('/ok', async (c) => {
const resp = await fetch('http://localhost/health', { unix: socketPath })
return resp.ok ? c.text('ok') : c.text('unhealthy', 503)
})
6. Graceful shutdown
process.on('SIGTERM', () => {
goProcess.kill('SIGTERM')
})
The Go server handles SIGTERM cleanly. Toes gives 10s before SIGKILL.
7. Remove Hype pages
The scaffolded src/pages/index.tsx isn't needed — the Go server serves its own web UI. The Bun app is purely a proxy, no HTML rendering. Can simplify to just src/server/index.tsx or even a single index.ts.
Toes Environment Variables
These are set automatically by toes on every app:
| Var | Description |
|---|---|
PORT |
Port to listen on |
DATA_DIR |
Per-app persistent data directory |
APPS_DIR |
Shared apps directory |
TOES_URL |
Base URL of toes server |
APP_URL |
This app's subdomain URL |
Device Configuration
Flash the Tidbyt with Tronbyt firmware, then set:
Image URL: http://tronbyt.toes.local/<device-id>/next
The device ID is assigned by the Tronbyt server when you add the device in its web UI.
Gotchas
- IPv6 binding — Not an issue with unix socket approach (no TCP binding on Go side)
- First boot is slow — Community apps repo clones on first start (~15s). With
PRODUCTION=false, firmware download is skipped. SetPRODUCTION=trueon Pi if you want OTA firmware updates. - macOS unsigned binary — System Settings → Privacy & Security → Allow Anyway
- Device WiFi — Tidbyt ESP32 is 2.4GHz only
- Image URL must be full path —
http://tronbyt.toes.local/<device-id>/next, not just the base URL
Dependencies
No new dependencies needed beyond what's already in package.json. Bun's native fetch handles unix sockets. The Go binary is self-contained (Pixlet embedded).
Can remove @because/forge and @because/howl from dependencies since this app has no UI of its own.