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
+8
-178
@@ -1,180 +1,10 @@
|
||||
# Setup — Pairing a Device
|
||||
|
||||
User-facing guide: get a phone or desktop talking to your hermes gateway in
|
||||
under 10 minutes. Design rationale lives in the numbered docs
|
||||
([`09-pairing-security.md`](09-pairing-security.md),
|
||||
[`08-push.md`](08-push.md), [`12-toolchain.md`](12-toolchain.md)); this page is
|
||||
just the steps.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
| Where | You need |
|
||||
| --- | --- |
|
||||
| Gateway host | hermes installed with its venv (`cd hermes-agent && uv sync`, see [`12-toolchain.md` §12.4](12-toolchain.md)) |
|
||||
| Android build machine | JDK 17, Android SDK with `ANDROID_HOME` set (or `app/local.properties`), ADB with a connected device |
|
||||
| Desktop build machine | JDK 17 only |
|
||||
|
||||
Gradle needs no system install — both apps use the project wrapper
|
||||
(`./gradlew`). First-time machine setup: [`12-toolchain.md`](12-toolchain.md).
|
||||
|
||||
## 1. Gateway setup (on the gateway host)
|
||||
|
||||
Install the plugin into the live hermes home (dev: a symlink from the monorepo
|
||||
root):
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.hermes/plugins
|
||||
ln -s "$PWD/gateway-plugin" ~/.hermes/plugins/iris
|
||||
hermes gateway status # should list "iris"
|
||||
```
|
||||
|
||||
Run the interactive setup:
|
||||
|
||||
```bash
|
||||
hermes gateway setup
|
||||
```
|
||||
|
||||
What it does:
|
||||
|
||||
- Generates `IRIS_TOKEN` (64 hex chars) if none exists and stores it in
|
||||
`~/.hermes/.env` (it prints the token once, at generation).
|
||||
- Prompts for the WS bind host (default `127.0.0.1`), port (default `8790`),
|
||||
and push backend (`ntfy` or `fcm`, default `ntfy`); warns when `fcm` is
|
||||
chosen (push metadata via Google's servers).
|
||||
- Prints the pairing payload (a QR-encodable `iris://pair?host=…&port=…&token=…`
|
||||
string), a scannable QR of that payload, and the server URL
|
||||
(`ws://<host>:8790/ws`).
|
||||
|
||||
Then start the gateway:
|
||||
|
||||
```bash
|
||||
hermes gateway # or: hermes gateway restart after config changes
|
||||
```
|
||||
|
||||
> **Note:** the default bind host `127.0.0.1` only accepts connections from the
|
||||
> gateway host itself (e.g. a desktop app on the same machine). For a phone on
|
||||
> the LAN, re-run `hermes gateway setup` (or edit `~/.hermes/.env`) and set
|
||||
> `IRIS_WS_HOST` to the host's LAN IP (e.g. `192.168.1.10`).
|
||||
|
||||
## 2. Iris app (Android)
|
||||
|
||||
Build and install (ADB device connected):
|
||||
|
||||
```bash
|
||||
cd app
|
||||
./gradlew :androidApp:installDebug
|
||||
```
|
||||
|
||||
First run opens the **Connect** screen:
|
||||
|
||||
1. **Server URL** — `ws://<gateway-ip>:8790/ws` (the URL printed by
|
||||
`hermes gateway setup`; use the LAN IP, not `127.0.0.1`, from a phone).
|
||||
2. **Pairing token** — from the `hermes gateway setup` output, or
|
||||
`grep IRIS_TOKEN ~/.hermes/.env` on the gateway host.
|
||||
3. **Test & Connect** — performs a real `hello` (the auth leg), then saves the
|
||||
pairing and connects.
|
||||
|
||||
> **Scan QR (Android):** the Connect screen has a **Scan QR** button (CameraX +
|
||||
> ML Kit) that reads the QR printed by `hermes gateway setup` and pre-fills the
|
||||
> URL + token. Desktop has no camera, so it uses manual entry. An `iris://pair`
|
||||
> deep link (from any scanner) pre-fills the same way.
|
||||
|
||||
## 3. Desktop app
|
||||
|
||||
```bash
|
||||
cd app
|
||||
./gradlew :desktopApp:run # dev run
|
||||
./gradlew :desktopApp:jpackage # native app-image (bundles the JRE)
|
||||
```
|
||||
|
||||
Pairing is the same Connect screen (URL + token); the token is stored in the OS
|
||||
keyring (with an encrypted-file fallback). Desktop push is tray icon + OS
|
||||
notifications (no FCM).
|
||||
|
||||
> **Known issue:** on Linux with JDK 17 the jpackage launcher prints a
|
||||
> non-fatal `pure virtual method called` warning (JDK-8348560, a
|
||||
> jpackage/Linux launcher bug). The app runs and connects regardless.
|
||||
|
||||
## 4. Push notifications
|
||||
|
||||
Push wakes a backgrounded/offline device; on reconnect the app syncs the
|
||||
outbox, so nothing is lost. Push fires when the device is offline, plus for
|
||||
high-priority events (approvals, clarifies, cron) even when a device is live.
|
||||
|
||||
### ntfy (default; zero-config)
|
||||
|
||||
```
|
||||
IRIS_PUSH_BACKEND=ntfy # the default — can be left unset
|
||||
```
|
||||
|
||||
- The device **generates its own topic** automatically (no `NTFY_TOPIC` needed);
|
||||
the server publishes to it.
|
||||
- `NTFY_SERVER_URL` defaults to `https://ntfy.sh`. **Self-hosted ntfy is
|
||||
recommended** — the public ntfy.sh SSE endpoint is flaky (it has served its
|
||||
web UI instead of the stream), while a self-hosted instance gives reliable
|
||||
SSE. For a real trust boundary use a private topic + `NTFY_AUTH_TOKEN`.
|
||||
- **Privacy:** ntfy keeps push metadata (title, topic) on your own
|
||||
infrastructure — this is the backend for truly private communication.
|
||||
|
||||
**What you see:** a low-priority foreground "ntfy listener" notification while
|
||||
the app is off; incoming pushes trigger a silent sync.
|
||||
|
||||
### FCM (opt-in; needs a Firebase project)
|
||||
|
||||
> **Privacy note:** FCM push metadata (notification title, device token) is
|
||||
> routed through **Google's servers**. If you want truly private
|
||||
> communication, use ntfy (self-hosted) instead — it is the default.
|
||||
|
||||
1. Create a Firebase project (console.firebase.google.com) and add an Android
|
||||
app with the app's applicationId; download `google-services.json` into
|
||||
`app/androidApp/`.
|
||||
2. Create a service account (Project settings → Service accounts → Generate new
|
||||
private key) and store the JSON path in `~/.hermes/.env`:
|
||||
`IRIS_FCM_SERVICE_ACCOUNT=/path/to/service-account.json`.
|
||||
3. Set `IRIS_PUSH_BACKEND=fcm`.
|
||||
|
||||
Without a Firebase project the FCM path is **inert** (the app's FCM service
|
||||
does nothing) — use ntfy (the default), or add Firebase later.
|
||||
|
||||
## 5. Remote access
|
||||
|
||||
- **Tailscale / WireGuard (recommended):** the gateway gets a stable tailnet IP;
|
||||
the app connects to `ws://<tailnet-ip>:8790/ws`. No public exposure.
|
||||
- **Reverse proxy / tunnel** (Caddy, Cloudflare Tunnel, ngrok): terminate TLS at
|
||||
the edge, forward the WebSocket to `127.0.0.1:8790`.
|
||||
- **WSS:** set `IRIS_WS_CERT` / `IRIS_WS_KEY` (paths, in
|
||||
`~/.hermes/.env`) and the server serves `wss://` instead of `ws://`.
|
||||
|
||||
> **Self-signed certs:** the app has a fingerprint-confirm flow (docs/09
|
||||
> §9.4): on first pair it shows the gateway cert's SHA-256 fingerprint; once
|
||||
> you confirm it, the cert is pinned in secure storage (like an SSH host
|
||||
> key). The cert needs a SAN for the URL host. CA-signed certs work out of
|
||||
> the box. Plain `ws://` on a trusted LAN (or inside Tailscale) stays the
|
||||
> default.
|
||||
|
||||
## 6. Troubleshooting
|
||||
|
||||
| Symptom | Likely cause / fix |
|
||||
| --- | --- |
|
||||
| `auth failed` / `error {code:"auth"}` on connect | Wrong token. Check `IRIS_TOKEN` in `~/.hermes/.env` on the gateway host (setup prints it only when it generates it). |
|
||||
| Connection refused | Gateway not running (`hermes gateway status`); wrong URL (port `8790`, path `/ws`, LAN IP instead of `127.0.0.1` from a phone); firewall blocking the port. |
|
||||
| Push not arriving | Backend not configured (gateway log: `push backend … not configured`); app backgrounded with no working backend; ntfy.sh SSE flakiness — use a self-hosted ntfy. |
|
||||
| Desktop jpackage launcher warning | Non-fatal (JDK-8348560 on Linux JDK 17); the app runs and connects regardless. |
|
||||
|
||||
Smoke test without the app (from the gateway host):
|
||||
|
||||
```bash
|
||||
python - <<'PY'
|
||||
import asyncio, json, websockets
|
||||
async def main():
|
||||
async with websockets.connect("ws://127.0.0.1:8790/ws") as ws:
|
||||
await ws.send(json.dumps({"v":1,"type":"hello","payload":{
|
||||
"token":"<IRIS_TOKEN>","device_id":"test","device_name":"probe",
|
||||
"caps":{"min_protocol":1}}}))
|
||||
print("recv:", await ws.recv())
|
||||
asyncio.run(main())
|
||||
PY
|
||||
```
|
||||
|
||||
Expect a `hello.ack`. If you get `error {code:"auth"}`, the token/host/port is
|
||||
wrong.
|
||||
> **Moved.** The user-facing setup guide now lives in
|
||||
> [`install.md`](install.md) — gateway install, all options, app install,
|
||||
> and connecting (LAN / TLS / remote). This file is kept so old links keep
|
||||
> working.
|
||||
>
|
||||
> - Push details: [`08-push.md`](08-push.md)
|
||||
> - Security model: [`09-pairing-security.md`](09-pairing-security.md)
|
||||
> - Toolchain (first-time machine setup): [`12-toolchain.md`](12-toolchain.md)
|
||||
Reference in new issue
Block a user