docs+plugin: HTTP-only transport cleanup, install guide, review fixes
- docs/install.md: new end-to-end guide for non-technical users (gateway install, app install, LAN/TLS/remote connection, push, options, troubleshooting); docs/setup.md now points to it - README: new 'Install the gateway' section; pairing section updated for HTTP transport (8791, QR scan on Android) - rename IRIS_WS_HOST -> IRIS_HTTP_HOST (clean rename, no compat fallback); drop dead DEFAULT_PORT=8790 - setup.py: advertise https:// in the printed/QR server URL when IRIS_HTTP_CERT is set - ws_probe.py/e2e.py: default --url http://127.0.0.1:8791, env IRIS_WS_URL -> IRIS_HTTP_URL, honor explicit port + https scheme - plugin.yaml: IRIS_HTTP_* env names, description no longer says 'WebSocket server' - docs 03/09/12/19: fix stale WS-era refs (ws_server.py cites, 8790 smoke test, WSS->HTTPS, 'HTTP fallback' reframed as the only transport) - AGENTS.md: symlink name android -> iris (matches actual install) - test: adapter reads IRIS_HTTP_HOST/CERT/KEY from env; legacy IRIS_WS_* names are not consulted (95/95 pass)
This commit is contained in:
1 parent
a61b47a947
commit
b1c9bac7d8
18 files changed
+472
-317
No files matched your search
+36
-36
@@ -27,13 +27,13 @@ requires_env:
|
||||
prompt: "Iris pairing token"
|
||||
password: true
|
||||
optional_env:
|
||||
- name: IRIS_WS_HOST
|
||||
description: "WS bind host (default 127.0.0.1; use 0.0.0.0 for LAN)"
|
||||
prompt: "WS host"
|
||||
- name: IRIS_HTTP_HOST
|
||||
description: "HTTP bind host (default 127.0.0.1; use 0.0.0.0 for LAN)"
|
||||
prompt: "HTTP host"
|
||||
password: false
|
||||
- name: IRIS_WS_PORT
|
||||
description: "WS port (default 8790)"
|
||||
prompt: "WS port"
|
||||
- name: IRIS_HTTP_PORT
|
||||
description: "HTTP port (default 8791)"
|
||||
prompt: "HTTP port"
|
||||
password: false
|
||||
- name: IRIS_HOME_CHANNEL
|
||||
description: "Default chat id for cron/notification delivery (default default)"
|
||||
@@ -67,13 +67,13 @@ optional_env:
|
||||
description: "ntfy server URL (default https://ntfy.sh)"
|
||||
prompt: "ntfy server URL"
|
||||
password: false
|
||||
- name: IRIS_WS_CERT
|
||||
description: "TLS cert path for WSS (optional)"
|
||||
prompt: "WSS cert"
|
||||
- name: IRIS_HTTP_CERT
|
||||
description: "TLS cert path for HTTPS (optional)"
|
||||
prompt: "HTTPS cert"
|
||||
password: false
|
||||
- name: IRIS_WS_KEY
|
||||
description: "TLS key path for WSS (optional)"
|
||||
prompt: "WSS key"
|
||||
- name: IRIS_HTTP_KEY
|
||||
description: "TLS key path for HTTPS (optional)"
|
||||
prompt: "HTTPS key"
|
||||
password: false
|
||||
```
|
||||
|
||||
@@ -215,27 +215,26 @@ The adapter tracks per-chat **turn state** (in-turn, current streaming
|
||||
`message` vs `tool.*` vs `commentary`. The exact classification markers are
|
||||
verified empirically in M2 (see `13-testing.md`).
|
||||
|
||||
## 3.4 WebSocket server (`ws_server.py`)
|
||||
## 3.4 HTTP server (`http_server.py`)
|
||||
|
||||
- Library: **`websockets`** (core dep, v15). `websockets.serve(handler, host,
|
||||
port, ssl=ctx)`.
|
||||
- **Handler** per connection:
|
||||
1. Await first frame; must be `hello {token, device_id, device_name, caps,
|
||||
fcm_token?}`. Verify token (constant-time) + allowlist. On failure: send
|
||||
`error {code:"auth"}` and close.
|
||||
2. On success: register in connection registry
|
||||
(`device_id → {ws, caps, fcm_token}`), send
|
||||
`hello.ack {server_caps, sync_cursor, channels[]}`.
|
||||
3. Loop: decode frames, dispatch to adapter inbound handlers.
|
||||
4. On close: deregister; if no devices remain, ensure pending outbox
|
||||
frames have push fired.
|
||||
- Library: **stdlib `http.server`** (`ThreadingHTTPServer` +
|
||||
`BaseHTTPRequestHandler`) in a daemon thread; bridges into the gateway's
|
||||
asyncio loop via `asyncio.run_coroutine_threadsafe`. Optional TLS via
|
||||
`ssl.SSLContext` (`IRIS_HTTP_CERT`/`IRIS_HTTP_KEY`). Full design:
|
||||
`19-http-fallback-transport.md`.
|
||||
- **Auth:** `Authorization: Bearer <token>` (constant-time `verify_token`)
|
||||
- device allowlist via `X-Iris-Device`; `401` on failure.
|
||||
- **Endpoints:** `GET /v1/health` (unauthenticated liveness),
|
||||
`POST /v1/frame` (any JSON frame the protocol accepts),
|
||||
`GET /v1/events?cursor=N` (SSE: outbox catch-up + live frames),
|
||||
`GET /v1/poll?cursor=N` (long-poll fallback), `POST /v1/media` +
|
||||
`GET /v1/media/{id}` (media upload/pull).
|
||||
- **Routing:** `emit(chat_id, frame)` → broadcast to **all** connected
|
||||
devices (no per-chat subscribe; single-user model). Global frames
|
||||
(`channel.*`, `status`) also broadcast to all.
|
||||
- **Heartbeat:** WS ping/pong + app-level `ping`/`pong`; dead peers reaped.
|
||||
- **Backpressure:** per-connection send queue with a bounded buffer; drop
|
||||
`message.update` (coalesce to latest) under pressure, never drop
|
||||
`message`/`tool.end`/`notification`.
|
||||
- **Limits:** 64 KiB request body cap, per-device token-bucket rate limit
|
||||
(20/s, burst 40) → `429`; media uploads bounded by the per-upload total
|
||||
cap. No CORS (app clients only).
|
||||
|
||||
## 3.5 State & storage (all under `get_hermes_home()/"iris"`)
|
||||
|
||||
@@ -253,18 +252,19 @@ verified empirically in M2 (see `13-testing.md`).
|
||||
## 3.6 Config resolution
|
||||
|
||||
- **Secrets (`.env`):** `IRIS_TOKEN`, `IRIS_FCM_SERVICE_ACCOUNT`,
|
||||
`IRIS_FCM_SERVER_KEY`, `IRIS_WS_CERT/KEY`, `NTFY_TOPIC` (if secret).
|
||||
`IRIS_FCM_SERVER_KEY`, `IRIS_HTTP_CERT/KEY`, `NTFY_TOPIC` (if secret).
|
||||
- **Behavioral (`config.yaml` → `gateway.platforms.iris.extra`):** `host`,
|
||||
`port`, `home_channel`, `allowed_users`, `push_backend`, `outbox_retention_hours`,
|
||||
`max_upload_bytes`, `tls`.
|
||||
`http_port`, `home_channel`, `allowed_users`, `push_backend`, `outbox_retention_hours`,
|
||||
`max_upload_bytes`, `http_cert`/`http_key`.
|
||||
- Env vars override `config.yaml` (hermes convention). Read secrets with the
|
||||
scope-aware `_get_scoped_secret` pattern (see `plugins/platforms/irc/adapter.py:42`)
|
||||
so multiplexed profiles don't leak each other's tokens.
|
||||
|
||||
## 3.7 Failure & lifecycle safety
|
||||
|
||||
- WS server bind failure → `_set_fatal_error("bind_failed", …, retryable=True)`.
|
||||
- All outbound sends are best-effort; a dead socket latches and the frame falls
|
||||
to the outbox.
|
||||
- `disconnect()` cancels the server task and closes sockets cleanly.
|
||||
- HTTP server bind failure → non-fatal: log a warning, disable the HTTP leg,
|
||||
show it in the inspector (the plugin keeps working for other platforms).
|
||||
- All outbound sends are best-effort; a dead stream latches and the frame
|
||||
falls to the outbox.
|
||||
- `disconnect()` stops the HTTP server and closes streams cleanly.
|
||||
- Token/PII redaction in all logs (hermes PII policy).
|
||||
Reference in new issue
Block a user