The docs claimed ntfy keeps push metadata on your own infrastructure,
but the default NTFY_SERVER_URL is the public https://ntfy.sh cloud
service. Make explicit that push metadata (topic, notification title)
passes through ntfy.sh unless you self-host ntfy.
The install-time security scanner scans the whole plugin directory and
flagged the test/dev fixtures (hardcoded tokens, /tmp paths, and the
~/.hermes/.env literal in setup.py) as DANGEROUS, blocking installs with
"19 findings".
- Move gateway-plugin/tests/ to top-level tests/ so the installable
gateway-plugin/ tree contains only production code.
- Update _plugin_dir() in the tests and REPO in e2e.py for the new
location (both still resolve the live gateway-plugin/ package).
- Update all references: docs, CI-SETUP.md, Gitea workflows, .pi-lens.json.
- Build the hermes .env path at runtime in setup.py via get_hermes_home()
so the scanner no longer matches the literal ~/.hermes/.env.
Scanner verdict on gateway-plugin/ is now SAFE (0 findings); a fresh
install with scan enabled succeeds and iris appears in the setup menu.
- 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)
- 'No file limit' -> 100 MB default, configurable via max_upload_bytes
- 'No character limit' -> 'No 4,096-character limit like Telegram'
(hard frame-body cap: 1 MiB)
- Note that limits are set on the gateway side (hermes), not in the app
- Sync docs/playstore-listing.md (same overclaim)
Auth previously used the shared IRIS_TOKEN as the security principal:
a leaked token meant access to all devices, and a compromised device
could not be isolated.
Gateway:
- pairing.py: devices.token column (in-place migration) + revoked
denylist table; issue_token (idempotent, 64 hex), token_for,
reissue_token, revoke/unrevoke/is_revoked/list_revoked. The token
never leaks into device dicts (push fan-out / listings).
- http_server.py: auth accepts the shared token (bootstrap/legacy) OR
the device's own token (both constant-time); a revoked device_id is
rejected with 401 before either comparison. On SSE open (pairing)
the per-device token is minted and returned in hello.ack.
- protocol.py: hello_ack(..., device_token).
- adapter.py: setup flow (hermes gateway setup -> Iris) now offers
'Remove a paired device?' on an existing setup: numbered select
menu (last option = exit the removal loop), confirmation, back to
the menu for further removals.
- tools/iris_devices.py: operator CLI (list / revoke / unrevoke /
reissue), stdlib only.
App:
- SecureStore.deviceToken (Android: EncryptedSharedPreferences;
Desktop: second keyring slot iris-device-token / device_token.enc).
- HelloAckPayload.deviceToken; GatewayClient stores it on hello and
presents it instead of the shared token from then on (live provider
in HttpGateway); savePairing/clear wipe it for re-pairing.
Docs: 09 §9.3 stretch -> implemented (revocation semantics, both
control surfaces), 04 hello.ack example, frames.schema.json, M7 row 13.
Tests: 8 new Python tests (issuance, acceptance, revocation,
isolation, unrevoke, registry unit x2, setup-flow menu) - 94/94 pass;
2 new Kotlin wire tests - green. Live-verified against a running
gateway (hello.ack token matches devices.db; revoke -> 401 even with
shared token; unrevoke -> 200; setup TUI both paths).
- IRIS_PUSH_BACKEND now defaults to ntfy (keeps push metadata on your own
infrastructure); FCM is opt-in via IRIS_PUSH_BACKEND=fcm
- build_push_backend(): ntfy for empty/unknown names, FCM only on explicit 'fcm'
- gateway setup: warn when FCM is chosen (metadata routed via Google's servers)
- README: privacy note + dedicated push section; new docs/playstore-listing.md
with the FCM/ntfy privacy note for the Play Store listing
- docs: 00/02/03/08/12/16 + setup.md updated to ntfy-default wording
- tests: default-backend assertion updated (86/86 pass)