Default push backend to ntfy; FCM opt-in with privacy warning (issue #10)
- 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)
This commit is contained in:
1 parent
70282dfb65
commit
746d809d48
14 files changed
+136
-56
No files matched your search
+1
-1
@@ -57,7 +57,7 @@ Everything in the feature checklist below.
|
||||
| Decision | Choice |
|
||||
|---|---|
|
||||
| Desktop app tech | **Compose Multiplatform** (shares Android code; "tweaked" for big screen) |
|
||||
| Push backend | **Both** — FCM primary, ntfy fallback (`IRIS_PUSH_BACKEND`) |
|
||||
| Push backend | **Both** — ntfy default, FCM optional (`IRIS_PUSH_BACKEND`) |
|
||||
| Media transport | **Over the WebSocket** (chunked binary frames; no extra Python deps) |
|
||||
| Phone default layout | **User-toggleable, single-pane default** (auto two-pane on large screens) |
|
||||
|
||||
|
||||
+2
-2
@@ -61,8 +61,8 @@ iris_x_hermes/
|
||||
- **`media.py`** — inbound chunked upload → `cache_*_from_bytes`; outbound
|
||||
`media.offer`/`media.pull` chunked streaming.
|
||||
- **`outbox.py`** — SQLite outbox per `chat_id` + monotonic sync cursor.
|
||||
- **`push.py`** — `PushBackend` interface; `FcmBackend` (httpx, FCM HTTP v1) and
|
||||
`NtfyBackend` (reuses hermes ntfy publish). Selected by `IRIS_PUSH_BACKEND`.
|
||||
- **`push.py`** — `PushBackend` interface; `NtfyBackend` (default) and
|
||||
`FcmBackend` (httpx, FCM HTTP v1). Selected by `IRIS_PUSH_BACKEND`.
|
||||
- **`pairing.py`** — token generation/verification (constant-time), device
|
||||
registry (SQLite), QR payload.
|
||||
- **`search.py`** — FTS5 query bridge over the hermes session store.
|
||||
|
||||
@@ -48,7 +48,7 @@ optional_env:
|
||||
prompt: "Allow all devices? (true/false)"
|
||||
password: false
|
||||
- name: IRIS_PUSH_BACKEND
|
||||
description: "Push backend: fcm (default) or ntfy"
|
||||
description: "Push backend: ntfy (default, keeps metadata off Google) or fcm"
|
||||
prompt: "Push backend"
|
||||
password: false
|
||||
- name: IRIS_FCM_SERVICE_ACCOUNT
|
||||
|
||||
+7
-4
@@ -1,7 +1,10 @@
|
||||
# 08 — Push Notifications, Outbox & Sync
|
||||
|
||||
The gateway can't reach a sleeping phone directly. Push goes through a cloud
|
||||
relay. **Decision: FCM primary, ntfy fallback** (`IRIS_PUSH_BACKEND`).
|
||||
relay. **Decision: ntfy default, FCM optional** (`IRIS_PUSH_BACKEND`).
|
||||
Privacy: FCM push metadata (notification title, device token) is routed
|
||||
through Google's servers — for truly private communication use ntfy
|
||||
(self-hosted), which keeps everything on your own infrastructure.
|
||||
|
||||
## 8.1 When push fires
|
||||
|
||||
@@ -54,9 +57,9 @@ class PushBackend(Protocol):
|
||||
def configured(self) -> bool: ...
|
||||
```
|
||||
|
||||
Selected at adapter init by `IRIS_PUSH_BACKEND` (`fcm` default, `ntfy`).
|
||||
Selected at adapter init by `IRIS_PUSH_BACKEND` (`ntfy` default, `fcm`).
|
||||
|
||||
### 8.2.1 `FcmBackend` (primary)
|
||||
### 8.2.1 `FcmBackend` (optional; metadata via Google)
|
||||
|
||||
- **FCM HTTP v1 API** via `httpx` (core dep). Auth = Firebase **service
|
||||
account** (`IRIS_FCM_SERVICE_ACCOUNT` JSON path) → mint a short-lived
|
||||
@@ -74,7 +77,7 @@ Selected at adapter init by `IRIS_PUSH_BACKEND` (`fcm` default, `ntfy`).
|
||||
- Batch: FCM allows up to 500 tokens/message; we send per-device (1 user, few
|
||||
devices).
|
||||
|
||||
### 8.2.2 `NtfyBackend` (fallback, self-host friendly)
|
||||
### 8.2.2 `NtfyBackend` (default, self-host friendly)
|
||||
|
||||
- Reuses hermes's existing ntfy publish path (hermes ships an ntfy adapter).
|
||||
- Publish to `NTFY_TOPIC` on `NTFY_SERVER_URL` (default `https://ntfy.sh`) via
|
||||
|
||||
@@ -77,15 +77,16 @@ hermes --version # sanity
|
||||
4. The app's `FirebaseMessagingService` obtains the FCM token at runtime and
|
||||
registers it via `hello` / `fcm.register`.
|
||||
|
||||
> Skip Firebase → set `IRIS_PUSH_BACKEND=ntfy` and configure `NTFY_TOPIC` /
|
||||
> `NTFY_SERVER_URL` (self-host ntfy or use ntfy.sh). See `08-push.md`.
|
||||
> Skip Firebase → the default is already ntfy: leave `IRIS_PUSH_BACKEND` unset
|
||||
> (or set it to `ntfy`) and configure `NTFY_TOPIC` / `NTFY_SERVER_URL`
|
||||
> (self-host ntfy or use ntfy.sh). See `08-push.md`.
|
||||
|
||||
## 12.6 Environment variables (summary)
|
||||
|
||||
**Secrets (`~/.hermes/.env`):**
|
||||
```
|
||||
IRIS_TOKEN=<64-hex>
|
||||
IRIS_PUSH_BACKEND=fcm # or ntfy
|
||||
IRIS_PUSH_BACKEND=ntfy # default; fcm = opt-in (metadata via Google)
|
||||
IRIS_FCM_SERVICE_ACCOUNT=/path/to/service-account.json
|
||||
# IRIS_FCM_SERVER_KEY=<legacy key> # fallback if no service account
|
||||
# NTFY_TOPIC=iris-push # when ntfy
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
| # | Decision | Choice | Rationale |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | Desktop app tech | **Compose Multiplatform** | Desktop = "the Android app, tweaked"; share protocol/state/UI. |
|
||||
| 2 | Push backend | **Both — FCM primary, ntfy fallback** | FCM is standard/reliable; ntfy for self-hosters with no Firebase. `IRIS_PUSH_BACKEND`. |
|
||||
| 2 | Push backend | **Both — ntfy default, FCM optional** (issue #10: FCM metadata — title, device token — is routed via Google's servers, contradicting the privacy claim; ntfy keeps it on your own infrastructure) | `IRIS_PUSH_BACKEND`. |
|
||||
| 3 | Media transport | **Over the WebSocket** | One transport, zero new Python deps; chunked binary frames. |
|
||||
| 4 | Phone default layout | **User-toggleable, single-pane default** | App-like on phones; auto two-pane on large screens; desktop defaults two-pane. |
|
||||
|
||||
|
||||
+2
-1
@@ -30,7 +30,7 @@ top-to-bottom once, then use the numbered docs as a lookup while implementing.
|
||||
| 5 | [`05-streaming.md`](05-streaming.md) | Streaming / reasoning / tools / intermediate. |
|
||||
| 6 | [`06-channels-cron-search.md`](06-channels-cron-search.md) | Channels, threads, cron delivery, search. |
|
||||
| 7 | [`07-media.md`](07-media.md) | Media upload/download + playback. |
|
||||
| 8 | [`08-push.md`](08-push.md) | Push (FCM + ntfy), outbox, sync. |
|
||||
| 8 | [`08-push.md`](08-push.md) | Push (ntfy default + FCM optional), outbox, sync. |
|
||||
| 9 | [`09-pairing-security.md`](09-pairing-security.md) | Pairing, auth, security model. |
|
||||
| 10 | [`10-android-app.md`](10-android-app.md) | When building the Android app. |
|
||||
| 11 | [`11-desktop-app.md`](11-desktop-app.md) | When building the Desktop app. |
|
||||
@@ -47,6 +47,7 @@ Machine-readable / diagrams:
|
||||
|
||||
- [`protocol/frames.schema.json`](protocol/frames.schema.json) — wire-frame schema.
|
||||
- [`diagrams/architecture.mmd`](diagrams/architecture.mmd) — mermaid architecture.
|
||||
- [`playstore-listing.md`](playstore-listing.md) — Play Store listing text (incl. the FCM/ntfy privacy note).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# Play Store Listing
|
||||
|
||||
Copy-paste text for the Play Console (and the F-Droid / sideload pages).
|
||||
Keep this file in sync with the README whenever the privacy story changes.
|
||||
|
||||
## Short description (≤ 80 chars)
|
||||
|
||||
Private chat for your personal AI agent — Android & desktop.
|
||||
|
||||
## Full description
|
||||
|
||||
Iris is a native chat app for your personal AI agent (hermes-agent):
|
||||
streaming replies, visible reasoning, structured tool activity, channels,
|
||||
threads, media, search — Telegram-quality, on your own infrastructure.
|
||||
|
||||
**Absolute Privacy!** — everything stays on your own infrastructure:
|
||||
|
||||
- Your gateway, your machine, your data. No cloud middleman for chat.
|
||||
- **Push notifications:** ntfy by default — push metadata stays on your own
|
||||
(self-hosted) ntfy server.
|
||||
- **FCM is opt-in** (`IRIS_PUSH_BACKEND=fcm`): standard and reliable, but 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.
|
||||
|
||||
No file limit. No character limit. Full markdown + HTML artifact preview.
|
||||
All settings live in the app.
|
||||
|
||||
Runs on Android and desktop (Linux, macOS, Windows) from one shared codebase.
|
||||
|
||||
## Privacy note (for the "Data safety" section / FAQ)
|
||||
|
||||
Iris talks directly to your own hermes gateway over a private, token-authenticated
|
||||
connection. By default, push notifications use ntfy, which you can self-host so
|
||||
that push metadata never leaves your infrastructure. If you explicitly enable
|
||||
FCM, push metadata (notification title, device token) is sent via Google's FCM
|
||||
servers; chat content itself is not sent to Google — FCM only carries a short
|
||||
preview, and full content is fetched from your gateway over the authenticated
|
||||
connection. For truly private communication, use the default ntfy backend
|
||||
(self-hosted).
|
||||
+23
-19
@@ -39,7 +39,8 @@ 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 (`fcm` or `ntfy`, default `fcm`).
|
||||
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`).
|
||||
@@ -100,26 +101,10 @@ 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.
|
||||
|
||||
### FCM (default; needs a Firebase project)
|
||||
|
||||
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. Keep `IRIS_PUSH_BACKEND=fcm` (the default).
|
||||
|
||||
Without a Firebase project the FCM path is **inert** (the app's FCM service
|
||||
does nothing) — use ntfy below, or add Firebase later.
|
||||
|
||||
**What you see:** system notifications for new messages when the app is
|
||||
backgrounded; tapping one deep-links to the chat.
|
||||
|
||||
### ntfy (zero-config fallback)
|
||||
### ntfy (default; zero-config)
|
||||
|
||||
```
|
||||
IRIS_PUSH_BACKEND=ntfy
|
||||
IRIS_PUSH_BACKEND=ntfy # the default — can be left unset
|
||||
```
|
||||
|
||||
- The device **generates its own topic** automatically (no `NTFY_TOPIC` needed);
|
||||
@@ -128,10 +113,29 @@ IRIS_PUSH_BACKEND=ntfy
|
||||
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;
|
||||
|
||||
Reference in new issue
Block a user