@firefly0621/dsh-remote-relay
v0.1.0-rc.14
Published
Standalone WebSocket relay for dsh remote control: device registry, pairing, session routing
Readme
@firefly0621/dsh-remote-relay
English | 中文
Standalone WebSocket relay for the dsh remote-control capability. Devices (dsh hosts running @firefly0621/dsh-remote-control) connect outbound with a long-lived secret; mobile apps pair with a short-lived code; the relay routes request/response messages between them. The PC behind NAT never needs an inbound port — it dials out, exactly like the OpenClaw/Claw mobile-control pattern.
The relay is a single-process Node service with no harness dependency. Device registrations and pairing codes live in memory and clear on restart; app sessions persist when DSH_RELAY_DATA_DIR is set, so a paired phone resumes with its stored token instead of re-pairing.
Configuration (environment variables)
| Variable | Default | Meaning |
|---|---|---|
| PORT | 8787 | Listening port |
| NODE_ENV | — | production requires TLS (refuses plaintext WS) |
| DSH_RELAY_DEVICE_SECRETS | — | Comma-separated deviceId:secret pairs; the device registry |
| DSH_RELAY_ALLOW_AUTO_REGISTER | — | 1 accepts the first hello for an unknown random deviceId and binds it (the plugin's zero-config mode) |
| DSH_RELAY_DATA_DIR | — | Directory for durable session storage; absent keeps sessions in memory |
| DSH_RELAY_TRUST_PROXY | — | 1 uses the leftmost X-Forwarded-For address for per-IP rate limits (only behind a trusted reverse proxy) |
| TLS_CERT / TLS_KEY | — | PEM cert/key paths; required when NODE_ENV=production |
Deployment (systemd on a VPS)
[Unit]
Description=dsh remote relay
After=network.target
[Service]
WorkingDirectory=/opt/dsh-relay
ExecStart=/usr/bin/node /opt/dsh-relay/lib/bin.js
Environment=NODE_ENV=production
Environment=PORT=8787
Environment=DSH_RELAY_DEVICE_SECRETS=my-pc:CHANGE_ME_LONG_RANDOM
Environment=TLS_CERT=/etc/letsencrypt/live/relay.example.com/fullchain.pem
Environment=TLS_KEY=/etc/letsencrypt/live/relay.example.com/privkey.pem
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.targetGenerate the device secret with openssl rand -hex 32. Front with a real certificate (Let's Encrypt) — the relay never serves plaintext WS in production.
Security notes
- Auth is two-layered: the device secret proves "this is the registered host"; the pairing code proves "the phone user is at the keyboard of that host". The relay forwards requests only between a paired app and its bound device.
DSH_RELAY_ALLOW_AUTO_REGISTERis first-seen-wins: an unknowndeviceIdbinds to whatever secret its first hello presents. Plugin-generated deviceIds are random 128-bit values, so claiming a vacant id gains nothing; keep the flag off on shared relays.- Session control is device-side: the bound device can list (
sessions.list) and revoke (sessions.revoke) its app sessions; a revoked token stops resuming. - The relay is a dumb pipe: it never inspects command payloads and never persists message content. Compromising the relay exposes routing metadata, not settings values — though settings reads still transit it, so TLS is mandatory.
- Pairing codes: 6 digits, 10-minute TTL, one-time use, max 5 wrong attempts. Rotated by the device on every relay (re)registration.
- Pairing brute-force is rate-limited: failed
pairattempts are budgeted per source IP (10 per minute), and an IP over the budget is blocked for 10 minutes. At most 32 concurrent sockets per source IP; device secrets compare in constant time. - Sessions are device-scoped:
sessions.revokedrops only sessions bound to the requesting device, never another device's tokens. - Requests fail fast: a request awaiting a device that disconnects is failed with
device.offline, and one unanswered past 30 seconds times out — no app socket hangs and no relay memory leaks. - Heartbeats: peers ping every 30s; a silent connection is dropped after 60s.
Protocol
Defined in @firefly0621/dsh-remote-protocol — this package only implements the relay side.
