217 lines
5.5 KiB
Markdown
217 lines
5.5 KiB
Markdown
# Operator Guide — pi-remote-control sidecar
|
||
|
||
> **Phase 1 state:** all sidecar modules implemented (T-1.0..T-1.10).
|
||
> The iOS app (Phase 2) is not yet built; use `wscat` or the legacy
|
||
> browser HTML UI to verify a running sidecar.
|
||
|
||
---
|
||
|
||
## Requirements
|
||
|
||
| Dependency | Minimum | Notes |
|
||
|---|---|---|
|
||
| Node.js | 18 | Must be on PATH (pi's runtime) |
|
||
| tmux | 2.5 | Control mode + `pane-died` events |
|
||
| openssl | any | Self-signed TLS cert generation |
|
||
| Python 3 | any | Smoke-test PTY workaround only |
|
||
|
||
---
|
||
|
||
## Quick start
|
||
|
||
```bash
|
||
# 1. Start pi with remote-control and auto-start the sidecar
|
||
pi -e extensions/remote-control --remote-control
|
||
|
||
# The sidecar prints its URL:
|
||
# Remote-control started: http://127.0.0.1:7777/?token=<TOKEN>
|
||
```
|
||
|
||
The server binds to `127.0.0.1:7777` by default (configurable — see below).
|
||
|
||
---
|
||
|
||
## Configuration
|
||
|
||
Create `~/.pi/remote-control/config.json` to override defaults:
|
||
|
||
```json
|
||
{
|
||
"bindAddress": "0.0.0.0:7777",
|
||
"publicBaseUrl": "https://pi.example.com"
|
||
}
|
||
```
|
||
|
||
IC-4 (config.toml, Phase 1.7 wiring) will migrate this to TOML. For now,
|
||
use the JSON format above.
|
||
|
||
**Key fields:**
|
||
|
||
| Field | Default | Description |
|
||
|---|---|---|
|
||
| `bindAddress` | `127.0.0.1:0` | Host:port to bind (`:0` = random free port) |
|
||
| `publicBaseUrl` | (none) | URL printed in QR / UI — use your tunnel URL |
|
||
|
||
---
|
||
|
||
## API reference (IC-2)
|
||
|
||
All endpoints require bearer token auth. Pass via:
|
||
- `?token=<TOKEN>` query param (same token as the startup URL)
|
||
- `Authorization: Bearer <TOKEN>` header
|
||
|
||
### REST endpoints
|
||
|
||
```
|
||
GET /health
|
||
→ { ok, uptime, sessions, sessionIds, bufferMb, diskFreeGb, warnings }
|
||
|
||
POST /sessions
|
||
body: { name?: string }
|
||
→ 201 { id, name }
|
||
|
||
GET /sessions
|
||
→ [{ id, name, description, state, lastOutputAt }]
|
||
|
||
PATCH /sessions/:id
|
||
body: { description?: string }
|
||
→ 200 { id, description }
|
||
|
||
DELETE /sessions/:id
|
||
→ 204 (also deletes session buffer)
|
||
|
||
GET /sessions/:id/commands
|
||
→ [{ name, description, args? }]
|
||
|
||
GET /sessions/:id/thumbnail
|
||
→ text/plain, 40×12 capture-pane snapshot
|
||
|
||
POST /sessions/:id/input
|
||
body: { type: "key"|"keys"|"paste", name?:string, data?:string }
|
||
→ 204
|
||
```
|
||
|
||
### WebSocket endpoints
|
||
|
||
```
|
||
WS /sessions/:id/stream
|
||
Client→Server (JSON text frames):
|
||
{ type:"resume"; lastSeq: number|null } — connect/reconnect
|
||
{ type:"key"; name: string } — single named key
|
||
{ type:"keys"; data: string } — literal text
|
||
{ type:"paste"; data: string } — bracketed-paste
|
||
{ type:"snapshot-request" } — request a snapshot
|
||
|
||
Server→Client (binary frames):
|
||
[seq: 8 bytes BE uint64][raw ANSI bytes]
|
||
|
||
Server→Client (JSON text frames):
|
||
{ type:"state"; value:"thinking"|"tool"|"idle"|"awaiting-input"; tool?:string; ts:number }
|
||
{ type:"snapshot"; seq:number; data:string } — base64 ANSI snapshot
|
||
{ type:"session-meta"; name:string; description?:string; createdAt:string }
|
||
{ type:"error"; code:string; message:string }
|
||
|
||
WS /sessions/:id/side
|
||
State-only side-channel (no binary output).
|
||
Server→Client: same JSON frames as /stream (state, session-meta, error).
|
||
Client→Server: (none expected)
|
||
```
|
||
|
||
---
|
||
|
||
## Pairing (iOS / CLI)
|
||
|
||
```bash
|
||
# Generate a QR code to pair the iOS app
|
||
node extensions/remote-control/cli/index.js pair
|
||
|
||
# List bearer tokens
|
||
node extensions/remote-control/cli/index.js auth list
|
||
|
||
# Create a named token
|
||
node extensions/remote-control/cli/index.js auth create "Jay's iPhone"
|
||
|
||
# Revoke a token
|
||
node extensions/remote-control/cli/index.js auth revoke <id>
|
||
```
|
||
|
||
The QR encodes a `pi-remote://` URL (IC-3) containing host, port, pairing
|
||
token, TLS fingerprint, and sidecar name. The iOS app scans it and calls
|
||
`POST /pair` to exchange a bearer token.
|
||
|
||
---
|
||
|
||
## TLS
|
||
|
||
Self-signed cert is generated on first run at:
|
||
```
|
||
~/.local/share/pi-remote/tls/cert.pem
|
||
~/.local/share/pi-remote/tls/key.pem
|
||
```
|
||
|
||
The SHA-256 fingerprint is included in the QR code. The iOS app pins to
|
||
this fingerprint — no CA needed.
|
||
|
||
> **Note:** T-1.3 implements cert generation. T-1.5 wiring TLS into the
|
||
> HTTP server is a Phase 2 task (server currently runs plain HTTP;
|
||
> terminate TLS at your reverse proxy for now).
|
||
|
||
---
|
||
|
||
## Disk buffer
|
||
|
||
Each session's output is written to:
|
||
```
|
||
~/.local/share/pi-remote/buffers/<session-id>.buf
|
||
```
|
||
|
||
Caps (defaults, configurable via IC-4 TOML in T-1.7):
|
||
- Per-session: 100 MB
|
||
- Global: 1 GB
|
||
- Minimum free disk: 1 GB (writes suspended below this)
|
||
- Idle cleanup: sessions inactive > 30 days are deleted on startup
|
||
|
||
---
|
||
|
||
## Smoke tests
|
||
|
||
```bash
|
||
# Basic smoke (server start, HTML, manifest, icon, WS)
|
||
npm run smoke
|
||
|
||
# Stream integration (session CRUD, send-keys, reconnect, thumbnail)
|
||
npm run smoke:stream
|
||
|
||
# Both
|
||
npm run smoke:all
|
||
```
|
||
|
||
Requires `python3` on PATH (PTY workaround for pi's TUI requirement).
|
||
|
||
---
|
||
|
||
## Troubleshooting
|
||
|
||
**Server doesn't start / port conflict**
|
||
Set `bindAddress` in config.json to a free port.
|
||
|
||
**`tmux >= 2.5 required`**
|
||
Upgrade tmux: `brew upgrade tmux` (macOS) or `apt upgrade tmux`.
|
||
|
||
**`can't find window`**
|
||
Older code bug: was using hardcoded `:0.0` pane targets, which fails if
|
||
tmux `base-index` is 1. Fixed in T-1.1 — target is now just the session name.
|
||
|
||
**Marker not appearing in stream**
|
||
The ControlClient uses `-C` (not `-CC`) for control mode. If you see no
|
||
`%output` events, check tmux version and that `-C` works:
|
||
```bash
|
||
tmux -C attach -t <session>
|
||
# Should print: %begin ... %end ... %output ...
|
||
```
|
||
|
||
**APNs push not working**
|
||
APNs is scaffolded (T-1.10) but device tokens are only available once the
|
||
iOS app pairs in Phase 2. Check `[apns]` config and that `.p8` key path
|
||
is correct.
|