Document the LAN-only ntfy decision and the WireGuard split tunnel
Records the networking decision behind Phase 6b's arrival notifications so it doesn't get re-derived later. ntfy stays LAN-only: identity -> ntfy is a container-to-container call that never reaches OPNsense, and remote delivery rides a WireGuard split tunnel routing only the smart-home VLAN. No DMZ, no port forward, no certificates, no inter-VLAN rule. Both alternatives were weighed and rejected, with reasons recorded: - DMZ + port forward buys only "notifications without the VPN connected," at the cost of a public listener, TLS upkeep, and mandatory ntfy auth — an exposed topic leaks "the house is empty right now". - DMZ + NAT reflection does avoid a new inter-VLAN rule (the smart-home VLAN's existing outbound-WAN allow already covers traffic to your own public IP), but makes a purely internal path depend on the ISP and external DNS being up, so two devices metres apart stop talking during a WAN outage. CGNAT breaks it outright, and the rule it avoids was the safe direction anyway. New §2.1 covers the split tunnel itself, including the trap that motivated it: AllowedIPs must be the actual VLAN prefix, never 192.168.0.0/16, which collides with typical café/hotel LANs and breaks the phone's connectivity there. Plus always-on VPN yes / lockdown no, and no DNS= so queries aren't pulled home. The household uses no Apple devices, which removes the only forcing function for exposure (ntfy's iOS app needs APNs and therefore an upstream relay). Open decision #37 closes as a household decision; the Apple case is kept as a footnote in case that ever changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>digest-per-person-and-agendas
parent
0caa21fc42
commit
15e6715469
|
|
@ -93,11 +93,11 @@ chores/ Presence/calendar-driven household chore nudging +
|
||||||
- [ ] Kitchen/fridge display + `pantry-vision` (`hosts/kitchen-display/`, `pantry-vision/`) — hold a grocery item up to the camera, an Ollama vision model proposes what it is and roughly how long it keeps, a human confirms (never auto-committed) before it's written into Grocy stock; the display then shows inventory sorted by soonest-to-expire, groceries running low, and Grocy's recipes — built and wired into `setup-container-host.sh` (`ENABLE_PANTRY_VISION`, off by default), **nothing run against a real camera, vision model, or Grocy instance** — the Grocy API call shapes in particular are written from documentation only, see `pantry-vision/README.md` and `hosts/kitchen-display/README.md`
|
- [ ] Kitchen/fridge display + `pantry-vision` (`hosts/kitchen-display/`, `pantry-vision/`) — hold a grocery item up to the camera, an Ollama vision model proposes what it is and roughly how long it keeps, a human confirms (never auto-committed) before it's written into Grocy stock; the display then shows inventory sorted by soonest-to-expire, groceries running low, and Grocy's recipes — built and wired into `setup-container-host.sh` (`ENABLE_PANTRY_VISION`, off by default), **nothing run against a real camera, vision model, or Grocy instance** — the Grocy API call shapes in particular are written from documentation only, see `pantry-vision/README.md` and `hosts/kitchen-display/README.md`
|
||||||
- [ ] `identity` + door panel (`identity/`, `hosts/door-panel/`) — the person <-> BLE-identifier registry: "register me as `<name>`" by voice or touchscreen, multi-phone support (multiple identifiers per person), anti-spoofing (only allowlisted IRK-resolved/fixed-tag entities are ever accepted as candidates, never a raw MAC), device-less people (a "no device" flag plus a hand-operated Home/Away toggle — the concrete case: a grandmother without a smartphone), and an anonymous "Guest" path. Backs `hosts/door-panel/`'s weather+clothing/who's-home/groceries-running-low dashboard and `hosts/kitchen-display/`'s "Show registration" screen — built and wired into `setup-container-host.sh` (`ENABLE_IDENTITY`, off by default), **nothing run against a real HA instance, real Private BLE Device entities, or a real voice pipeline** — `TRUSTED_ENTITY_PREFIXES` above all needs checking against Developer Tools -> States, see `identity/README.md` and `hosts/door-panel/README.md`
|
- [ ] `identity` + door panel (`identity/`, `hosts/door-panel/`) — the person <-> BLE-identifier registry: "register me as `<name>`" by voice or touchscreen, multi-phone support (multiple identifiers per person), anti-spoofing (only allowlisted IRK-resolved/fixed-tag entities are ever accepted as candidates, never a raw MAC), device-less people (a "no device" flag plus a hand-operated Home/Away toggle — the concrete case: a grandmother without a smartphone), and an anonymous "Guest" path. Backs `hosts/door-panel/`'s weather+clothing/who's-home/groceries-running-low dashboard and `hosts/kitchen-display/`'s "Show registration" screen — built and wired into `setup-container-host.sh` (`ENABLE_IDENTITY`, off by default), **nothing run against a real HA instance, real Private BLE Device entities, or a real voice pipeline** — `TRUSTED_ENTITY_PREFIXES` above all needs checking against Developer Tools -> States, see `identity/README.md` and `hosts/door-panel/README.md`
|
||||||
- [ ] `identity` also corroborates presence from Frigate face recognition (Phase 20, Tapo pan/tilt cameras) — an OR-ed-in second signal only, **never** a registration signal; and owns the per-person chore-system settings (`chore_exempt`, `chore_reminder_style`, plus chore assignment) consumed by `chores/`, see `identity/README.md`
|
- [ ] `identity` also corroborates presence from Frigate face recognition (Phase 20, Tapo pan/tilt cameras) — an OR-ed-in second signal only, **never** a registration signal; and owns the per-person chore-system settings (`chore_exempt`, `chore_reminder_style`, plus chore assignment) consumed by `chores/`, see `identity/README.md`
|
||||||
- [ ] `identity`'s admin panel (`identity/frontend/admin.html`, Phase 6b) — managing people/guests: edit every field, **nicknames** (an input alias only — `/resolve` accepts them, but the assistant always speaks the real `speak_name`), **visit history** sampled from `/presence` plus a derived "who was home with whom" view, **"select all that last visited before `<date>`"** pruning (the filter selects, a human confirms the exact list, the filter is never re-run at delete time), **per-device rights** for self-entry via a smart lock (`identity` only ever *answers* `GET /device-access` — HA asks and HA acts, deny is the default), chore assignment, and **opt-in arrival push notifications** ("tell me when someone gets home", via the self-hosted ntfy this stack already runs — `identity` itself never touches the WAN; getting the push while you're *away* is `docs/network-integration.md`'s WireGuard answer, and iOS is a documented exception). Deliberately **not** a kiosk page and not linked from any wall panel. Covered by API-level tests; **never opened in a real browser**, and `DEPARTURE_GRACE_SECONDS` is an untuned guess — see `identity/README.md`
|
- [ ] `identity`'s admin panel (`identity/frontend/admin.html`, Phase 6b) — managing people/guests: edit every field, **nicknames** (an input alias only — `/resolve` accepts them, but the assistant always speaks the real `speak_name`), **visit history** sampled from `/presence` plus a derived "who was home with whom" view, **"select all that last visited before `<date>`"** pruning (the filter selects, a human confirms the exact list, the filter is never re-run at delete time), **per-device rights** for self-entry via a smart lock (`identity` only ever *answers* `GET /device-access` — HA asks and HA acts, deny is the default), chore assignment, and **opt-in arrival push notifications** ("tell me when someone gets home", via the self-hosted ntfy this stack already runs — `identity` itself never touches the WAN; ntfy stays LAN-only and remote delivery rides a WireGuard split tunnel — see `docs/network-integration.md` §2.2 for why a DMZ/port-forward was weighed and rejected). Deliberately **not** a kiosk page and not linked from any wall panel. Covered by API-level tests; **never opened in a real browser**, and `DEPARTURE_GRACE_SECONDS` is an untuned guess — see `identity/README.md`
|
||||||
- [ ] `trash-calendar` + `transit` (Phase 19, Kennelbach AT trash pickup + Vorarlberg public transit) — built and wired into `setup-container-host.sh` (`ENABLE_TRASH_CALENDAR`/`ENABLE_TRANSIT`/`ENABLE_TRIP_PLANNING`, all off by default), **nothing run against a live ICS feed, a live GTFS feed, or a real OpenTripPlanner instance** — trip planning also needs a manually-built OTP graph this repo does not build for you, see `trash-calendar/README.md` and `transit/README.md`'s "Route planning scope"
|
- [ ] `trash-calendar` + `transit` (Phase 19, Kennelbach AT trash pickup + Vorarlberg public transit) — built and wired into `setup-container-host.sh` (`ENABLE_TRASH_CALENDAR`/`ENABLE_TRANSIT`/`ENABLE_TRIP_PLANNING`, all off by default), **nothing run against a live ICS feed, a live GTFS feed, or a real OpenTripPlanner instance** — trip planning also needs a manually-built OTP graph this repo does not build for you, see `trash-calendar/README.md` and `transit/README.md`'s "Route planning scope"
|
||||||
- [ ] `chores` (Phase 20) — presence/calendar-driven household chore nudging: "I don't care who does it, as long as it gets done" — prefers whoever's been assigned a chore in `identity`'s admin panel but falls through to whoever's actually home rather than waiting (`CHORE_ASSIGNMENT_STRICT` flips that), redirects to someone else if a chore goes neglected, keeps a passive fairness tally that never feeds back into who gets nudged, and camera-checks trash bins/dishes/litter via Frigate + an Ollama vision model. **Litter remains the exception to everything** — it ignores both chore-exemption and assignment, because cleaning up what you left out was never a task anyone could be assigned. Built and wired into `setup-container-host.sh` (`ENABLE_CHORES`, off by default, every-2-hours systemd timer), **no Tapo camera hardware chosen and nothing run against real hardware**, see `chores/README.md`
|
- [ ] `chores` (Phase 20) — presence/calendar-driven household chore nudging: "I don't care who does it, as long as it gets done" — prefers whoever's been assigned a chore in `identity`'s admin panel but falls through to whoever's actually home rather than waiting (`CHORE_ASSIGNMENT_STRICT` flips that), redirects to someone else if a chore goes neglected, keeps a passive fairness tally that never feeds back into who gets nudged, and camera-checks trash bins/dishes/litter via Frigate + an Ollama vision model. **Litter remains the exception to everything** — it ignores both chore-exemption and assignment, because cleaning up what you left out was never a task anyone could be assigned. Built and wired into `setup-container-host.sh` (`ENABLE_CHORES`, off by default, every-2-hours systemd timer), **no Tapo camera hardware chosen and nothing run against real hardware**, see `chores/README.md`
|
||||||
- [ ] Music Assistant (optional, additive multi-room audio) — wired into `setup-container-host.sh` (`ENABLE_MUSIC_ASSISTANT`, off by default), **its default port is an unverified guess that collides with `PANTRY_VISION_PORT`** if both are enabled together, see `docs/project-plan.md` open decision #31
|
- [ ] Music Assistant (optional, additive multi-room audio) — wired into `setup-container-host.sh` (`ENABLE_MUSIC_ASSISTANT`, off by default), **its default port is an unverified guess that collides with `PANTRY_VISION_PORT`** if both are enabled together, see `docs/project-plan.md` open decision #31
|
||||||
- [ ] `docs/network-integration.md` (OPNsense VLAN segmentation + why nothing here should be port-forwarded to the WAN) — written, not run against a real OPNsense instance
|
- [ ] `docs/network-integration.md` (OPNsense VLAN segmentation, the WireGuard split tunnel that carries arrival notifications, and why nothing here — ntfy included — gets port-forwarded to the WAN) — written, not run against a real OPNsense instance
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,56 @@ side per new service this repo adds later.
|
||||||
If you don't need remote access at all, skip this section entirely — every service
|
If you don't need remote access at all, skip this section entirely — every service
|
||||||
in this stack works purely on the LAN with zero WAN configuration.
|
in this stack works purely on the LAN with zero WAN configuration.
|
||||||
|
|
||||||
|
### 2.1 Split tunnel — route the smart-home VLAN, nothing else
|
||||||
|
|
||||||
|
`AllowedIPs` on the **client** profile is both the route table and WireGuard's crypto
|
||||||
|
ACL, so it alone decides what enters the tunnel. Route the smart-home VLAN and leave
|
||||||
|
everything else on the phone's own connection:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Peer]
|
||||||
|
AllowedIPs = 192.168.30.0/24 # the smart-home VLAN, adjust to your real subnet
|
||||||
|
# NOT 0.0.0.0/0 (sends all your traffic home for no reason)
|
||||||
|
# NOT 192.168.0.0/16 — see the collision warning below
|
||||||
|
```
|
||||||
|
|
||||||
|
**Never route the whole `192.168.0.0/16`.** Practically every café, hotel and hotspot
|
||||||
|
LAN lives on `192.168.0.0/24` or `192.168.1.0/24`; routing the entire /16 makes the
|
||||||
|
phone send *that* network's own gateway down the tunnel, and you lose connectivity on
|
||||||
|
that Wi-Fi altogether. Route only your actual VLAN prefix — and if your home LAN is
|
||||||
|
itself on `192.168.1.0/24`, put the smart-home VLAN somewhere unlikely
|
||||||
|
(`192.168.73.0/24`, say) so the clash can't arise in the first place.
|
||||||
|
|
||||||
|
Two client-side settings that are easy to get wrong together:
|
||||||
|
|
||||||
|
- **Android "always-on VPN": on. "Block connections without VPN" (lockdown): OFF.**
|
||||||
|
Lockdown drops everything not traversing the tunnel, which with a split tunnel kills
|
||||||
|
all non-`192.168.x` traffic on the device.
|
||||||
|
- **Leave `DNS =` unset.** Reach services by LAN IP (`http://192.168.30.x:8090` for
|
||||||
|
ntfy). No internal DNS needed, and your DNS queries don't get pulled home while
|
||||||
|
you're out.
|
||||||
|
|
||||||
|
This is just §3's rule 4 (route WireGuard peers into the smart-home VLAN, not Trusted
|
||||||
|
LAN) expressed on the client side.
|
||||||
|
|
||||||
|
## 2.2 Decided: ntfy stays LAN-only, no DMZ, no port forward
|
||||||
|
|
||||||
|
`identity`'s arrival notifications (Phase 6b — "tell me when someone gets home") were
|
||||||
|
the first thing in this project with a real argument for WAN exposure, so the
|
||||||
|
alternatives were weighed properly and **rejected**:
|
||||||
|
|
||||||
|
| Option | Verdict |
|
||||||
|
|---|---|
|
||||||
|
| **ntfy stays in the compose stack, reached over WireGuard** | **Chosen.** `identity → ntfy` is a container-to-container call that never reaches OPNsense at all — no firewall rule, no DMZ, no certificates. ntfy's Android app does instant delivery straight to a LAN server, so it works at home with nothing configured and away over the split tunnel above |
|
||||||
|
| ntfy in a DMZ, port-forwarded | Rejected. Buys only "notifications without the VPN connected," at the cost of a public listener, TLS/reverse-proxy upkeep, and mandatory ntfy auth — an exposed topic leaks *"the house is empty right now"*, which is materially worse than the usual leak of a self-hosted service |
|
||||||
|
| ntfy in a DMZ, reached from the smart-home VLAN by **NAT reflection** (hairpin) instead of an inter-VLAN rule | Rejected. It does avoid a new rule — the smart-home VLAN's existing blanket outbound-WAN allow already covers traffic to your own public IP — but it makes a purely internal notification path depend on the **ISP and external DNS being up**, so two devices metres apart stop talking during a WAN outage. CGNAT breaks it outright, and the rule it avoids (`smart-home → DMZ:443`, one host, one port, outbound) was the *safe* direction anyway; `DMZ → smart-home` stays denied either way |
|
||||||
|
|
||||||
|
**The household uses no Apple devices**, which removes the one genuine forcing
|
||||||
|
function for exposure: ntfy's iOS app can only be woken by Apple's APNs, so a
|
||||||
|
self-hosted server would need `upstream-base-url` relaying through ntfy.sh — WAN
|
||||||
|
egress through a third party, in an otherwise fully-local stack. Android needs none of
|
||||||
|
that. Revisit this table only if an Apple device ever joins the household.
|
||||||
|
|
||||||
## 3. VLAN segmentation — not a DMZ, a blast-radius boundary
|
## 3. VLAN segmentation — not a DMZ, a blast-radius boundary
|
||||||
|
|
||||||
A traditional DMZ exists to host something the WAN needs to reach. Nothing here
|
A traditional DMZ exists to host something the WAN needs to reach. Nothing here
|
||||||
|
|
@ -110,7 +160,7 @@ column shows which are opt-in vs. always-on with the base stack.
|
||||||
| 8080 | zigbee2mqtt frontend | Yes | none by default — VERIFY you've set `frontend.auth` if this VLAN isn't fully trusted |
|
| 8080 | zigbee2mqtt frontend | Yes | none by default — VERIFY you've set `frontend.auth` if this VLAN isn't fully trusted |
|
||||||
| 1880 | Node-RED | Yes | Node-RED's own login (if enabled) |
|
| 1880 | Node-RED | Yes | Node-RED's own login (if enabled) |
|
||||||
| 3000 | Homepage dashboard | `ENABLE_HOMEPAGE` (default on) | none |
|
| 3000 | Homepage dashboard | `ENABLE_HOMEPAGE` (default on) | none |
|
||||||
| 8090 | ntfy | `ENABLE_NTFY` (default on) | ntfy's own auth (if configured) — off by default, treat the topic name as the only barrier until you set one |
|
| 8090 | ntfy | `ENABLE_NTFY` (default on) | ntfy's own auth (if configured) — off by default, treat the topic name as the only barrier until you set one. Carries `identity`'s arrival notifications ("X just got home"), so an exposed topic leaks when the house is empty — this is the reason §2.2 keeps it LAN-only. Reach it from a phone at `http://<container-host>:8090`, at home or over the split tunnel |
|
||||||
| 9000 | Portainer | `ENABLE_PORTAINER` (default on) | Portainer's own login |
|
| 9000 | Portainer | `ENABLE_PORTAINER` (default on) | Portainer's own login |
|
||||||
| 445 | Gallery SMB share | `ENABLE_GALLERY_SMB` (default off) | SMB username/password you set (`GALLERY_SMB_USERNAME`/`PASSWORD`) |
|
| 445 | Gallery SMB share | `ENABLE_GALLERY_SMB` (default off) | SMB username/password you set (`GALLERY_SMB_USERNAME`/`PASSWORD`) |
|
||||||
| 9925 | Mealie | `ENABLE_MEALIE` (default off) | Mealie's own login |
|
| 9925 | Mealie | `ENABLE_MEALIE` (default off) | Mealie's own login |
|
||||||
|
|
|
||||||
|
|
@ -885,6 +885,6 @@ These need a decision before their respective implementation steps can be built
|
||||||
34. **`identity`'s `DEPARTURE_GRACE_SECONDS` default (15 min) is an untuned guess at how much a real Private BLE Device setup flaps** (new, Phase 6b) — too low and one evening at home is recorded as several separate "visits," too high and a quick trip out never registers. The entire usefulness of the visit history and the co-presence view rests on this number, and nobody has watched a real BLE presence entity over a day to pick it. First thing to check once `GET /visits` has real data in it.
|
34. **`identity`'s `DEPARTURE_GRACE_SECONDS` default (15 min) is an untuned guess at how much a real Private BLE Device setup flaps** (new, Phase 6b) — too low and one evening at home is recorded as several separate "visits," too high and a quick trip out never registers. The entire usefulness of the visit history and the co-presence view rests on this number, and nobody has watched a real BLE presence entity over a day to pick it. First thing to check once `GET /visits` has real data in it.
|
||||||
35. **The device-rights HA wiring is a worked example, not a tested one, and this is the one place where getting it wrong opens a door** (new, Phase 6b) — `identity` only ever *answers* `GET /device-access`; an HA automation that calls `lock.unlock` outside the `condition: template` guard in `identity/README.md`'s example would unlock regardless of that answer, and nothing on this side can detect or prevent that. Same "documented shape, not verified" caveat as #21, with a materially worse failure mode.
|
35. **The device-rights HA wiring is a worked example, not a tested one, and this is the one place where getting it wrong opens a door** (new, Phase 6b) — `identity` only ever *answers* `GET /device-access`; an HA automation that calls `lock.unlock` outside the `condition: template` guard in `identity/README.md`'s example would unlock regardless of that answer, and nothing on this side can detect or prevent that. Same "documented shape, not verified" caveat as #21, with a materially worse failure mode.
|
||||||
36. **The admin panel has never been opened in a real browser** (new, Phase 6b) — every endpoint it calls is covered by API-level tests, but the page itself (the `<dialog>`-based person editor, the prune checkbox flow) has only been checked for syntax. `showModal()` needs a reasonably current browser; the door panel's Chromium qualifies, something older might not.
|
36. **The admin panel has never been opened in a real browser** (new, Phase 6b) — every endpoint it calls is covered by API-level tests, but the page itself (the `<dialog>`-based person editor, the prune checkbox flow) has only been checked for syntax. `showModal()` needs a reasonably current browser; the door panel's Chromium qualifies, something older might not.
|
||||||
37. **iOS cannot receive `identity`'s arrival notifications without WAN egress through a third party** (new, Phase 6b) — ntfy's iOS app depends on Apple's APNs, so a self-hosted server must relay via `upstream-base-url` (i.e. through ntfy.sh) for iOS devices to be woken, *even on the home Wi-Fi*. Android needs none of that (instant delivery straight to the LAN server), and a WireGuard-connected phone reaches the LAN server from anywhere. This is an Apple architecture constraint, not something this repo can engineer around, but it turns "who gets arrival notifications" into a household-platform decision that should be made consciously rather than discovered. Nothing here has been delivered to a real phone of either kind yet.
|
37. ~~iOS cannot receive `identity`'s arrival notifications without WAN egress through a third party~~ — **household decision made** (Phase 6b): **no Apple devices are used here**, which removes the only forcing function for exposing ntfy at all. ntfy therefore **stays LAN-only** — no DMZ, no port forward, no NAT-reflection hairpin; `identity → ntfy` is a container-to-container call that never reaches OPNsense, and remote delivery rides a **WireGuard split tunnel** routing only the smart-home VLAN. The full comparison (including why the hairpin variant, which avoids an inter-VLAN rule, was rejected for making an internal path depend on the ISP) is recorded in `docs/network-integration.md` §2.2 so it doesn't get re-litigated. **Still genuinely open**: nothing has been delivered to a real phone yet, and the split tunnel's `AllowedIPs` is the fiddly part — routing all of `192.168.0.0/16` would collide with typical café/hotel LANs and break the phone's connectivity there. Revisit §2.2 only if an Apple device ever joins the household.
|
||||||
38. **Both of `chores`' shipped container URLs pointed at `127.0.0.1` and could never have worked** (found while wiring Phase 6b's arrival notifications) — inside the `chores` container that address is the chores container itself, not the host. `NTFY_URL=http://127.0.0.1:8090` meant every nudge failed to send, and `IDENTITY_URL=http://127.0.0.1:8097` meant `_presence()` never reached identity at all, so **`chores` would have done nothing whatsoever** — and silently, since both failure paths log and continue by design (a deliberate never-block-on-a-dependency choice that here hid a total outage). Corrected in the template to `http://ntfy` and `http://identity:8097` (compose-bridge DNS; ntfy on its internal port 80, not the 8090 published to the LAN). **An already-deployed `/opt/smart-home/chores/chores.env` still has the old values** — templates are copied once at setup and never re-synced, so existing installs need this edited by hand. The broader lesson worth acting on: several services fail soft on an unreachable dependency, which means a misconfigured address produces silence rather than an error — nothing in this repo currently distinguishes "nothing to do" from "never reached the thing that would have told me".
|
38. **Both of `chores`' shipped container URLs pointed at `127.0.0.1` and could never have worked** (found while wiring Phase 6b's arrival notifications) — inside the `chores` container that address is the chores container itself, not the host. `NTFY_URL=http://127.0.0.1:8090` meant every nudge failed to send, and `IDENTITY_URL=http://127.0.0.1:8097` meant `_presence()` never reached identity at all, so **`chores` would have done nothing whatsoever** — and silently, since both failure paths log and continue by design (a deliberate never-block-on-a-dependency choice that here hid a total outage). Corrected in the template to `http://ntfy` and `http://identity:8097` (compose-bridge DNS; ntfy on its internal port 80, not the 8090 published to the LAN). **An already-deployed `/opt/smart-home/chores/chores.env` still has the old values** — templates are copied once at setup and never re-synced, so existing installs need this edited by hand. The broader lesson worth acting on: several services fail soft on an unreachable dependency, which means a misconfigured address produces silence rather than an error — nothing in this repo currently distinguishes "nothing to do" from "never reached the thing that would have told me".
|
||||||
39. **Nothing enforces that voice/TTS consumers actually read `speak_name` rather than `nickname`** (new, Phase 6b) — the field exists, is documented, and `chores/` uses it, but a future HA intent script that reaches for the friendlier-looking `nickname` would break the "the assistant uses real names" rule silently. Worth checking whenever a new consumer of `/presence` or `/resolve` is written.
|
39. **Nothing enforces that voice/TTS consumers actually read `speak_name` rather than `nickname`** (new, Phase 6b) — the field exists, is documented, and `chores/` uses it, but a future HA intent script that reaches for the friendlier-looking `nickname` would break the "the assistant uses real names" rule silently. Worth checking whenever a new consumer of `/presence` or `/resolve` is written.
|
||||||
|
|
|
||||||
|
|
@ -233,22 +233,24 @@ through the door and then notice nothing happened.
|
||||||
### Getting the push while you're actually away
|
### Getting the push while you're actually away
|
||||||
|
|
||||||
**`identity` never touches the WAN.** It POSTs to the self-hosted ntfy this stack
|
**`identity` never touches the WAN.** It POSTs to the self-hosted ntfy this stack
|
||||||
already runs for `chores` (`setup-container-host.sh`'s `ENABLE_NTFY`, published on
|
already runs for `chores` (`setup-container-host.sh`'s `ENABLE_NTFY`) — one container
|
||||||
`:8090`) — one container to another over the LAN. Whether the message then reaches
|
to another on the compose network, never even reaching the firewall.
|
||||||
your phone is a **network** question, and it's the one that decides whether this
|
|
||||||
feature is useful, since "did they get home while I was out?" is most of the point:
|
|
||||||
|
|
||||||
| Where your phone is | What's needed |
|
Getting the message onto a phone is a **network** question, and it's settled in
|
||||||
|---|---|
|
`docs/network-integration.md` §2.2: **ntfy stays LAN-only.** At home, ntfy's Android
|
||||||
| On the home Wi-Fi, Android | Nothing. The ntfy Android app holds a connection straight to your self-hosted server ("instant delivery") — no Google services, no WAN |
|
app holds a connection straight to it ("instant delivery" — no Google services, no
|
||||||
| Away from home | **WireGuard back into your own network** — `docs/network-integration.md`'s existing answer, unchanged. On the VPN your phone reaches LAN ntfy exactly as if it were at home |
|
WAN). Away, a **WireGuard split tunnel** routing just the smart-home VLAN (§2.1)
|
||||||
| Away, without a VPN | Would need ntfy exposed to the WAN — **which `docs/network-integration.md` explicitly tells you not to do**, or ntfy's `upstream-base-url` relay, which puts a third party in the path |
|
reaches it exactly as if you were sitting at home. No DMZ, no port forward, no
|
||||||
| iOS, anywhere | ntfy's iOS app depends on Apple's APNs, so a self-hosted server needs `upstream-base-url` set to relay through ntfy.sh — **WAN egress and a third party**, even on your own Wi-Fi. An Android-only or VPN-only household avoids this entirely |
|
certificates, no firewall rule.
|
||||||
|
|
||||||
The honest summary: **Android + WireGuard keeps this fully local and works anywhere;
|
That doc records why exposing ntfy in a DMZ — with or without NAT reflection — was
|
||||||
iOS cannot, because of Apple's push architecture, not because of anything here.**
|
weighed and rejected, so the reasoning doesn't have to be re-derived later.
|
||||||
Neither path has been tested against a real phone from this repo — see the
|
|
||||||
verification list below.
|
> Apple footnote, for completeness only: this household uses no Apple devices. If one
|
||||||
|
> ever joins, note that ntfy's iOS app can only be woken via Apple's APNs, so a
|
||||||
|
> self-hosted server would need `upstream-base-url` relaying through ntfy.sh — real WAN
|
||||||
|
> egress through a third party, even on your own Wi-Fi. That would reopen §2.2's
|
||||||
|
> decision. Android needs none of it.
|
||||||
|
|
||||||
## Pruning: the filter selects, the human deletes
|
## Pruning: the filter selects, the human deletes
|
||||||
|
|
||||||
|
|
@ -536,14 +538,12 @@ no way to send an `Authorization` header.
|
||||||
12. **Arrival pushes have never been delivered to a real phone from here** — the
|
12. **Arrival pushes have never been delivered to a real phone from here** — the
|
||||||
notification logic is covered by tests (subscription, opt-out, dedup, the
|
notification logic is covered by tests (subscription, opt-out, dedup, the
|
||||||
startup-baseline guard), but nothing has been sent through a real ntfy server to a
|
startup-baseline guard), but nothing has been sent through a real ntfy server to a
|
||||||
real device. The two things to check first: that the ntfy **Android** app's
|
real device. Two things to check: that the ntfy **Android** app's instant delivery
|
||||||
instant-delivery against a self-hosted LAN server behaves as its docs describe, and
|
against a self-hosted LAN server behaves as its docs describe, and that a phone on
|
||||||
that a **WireGuard**-connected phone reaches `NTFY_URL` while away (both per the
|
the **WireGuard split tunnel** (`docs/network-integration.md` §2.1) actually reaches
|
||||||
table above). **iOS is expected not to work fully locally at all** — ntfy's iOS app
|
`NTFY_URL` from outside. The split tunnel is the more likely of the two to need
|
||||||
needs APNs, so a self-hosted server has to relay via `upstream-base-url`, which is
|
fiddling — get `AllowedIPs` wrong and it fails either silently or by breaking the
|
||||||
real WAN egress through a third party and directly contrary to this project's
|
phone's connectivity on café Wi-Fi.
|
||||||
local-first default. That's an Apple constraint, not something this repo can fix,
|
|
||||||
but it should be a conscious choice rather than a surprise.
|
|
||||||
13. **The device-rights HA wiring above is a worked example, not a tested one** — same
|
13. **The device-rights HA wiring above is a worked example, not a tested one** — same
|
||||||
caveat as the voice-registration YAML. The consequence of getting it wrong is worse
|
caveat as the voice-registration YAML. The consequence of getting it wrong is worse
|
||||||
here than anywhere else in this repo: an automation that calls `lock.unlock`
|
here than anywhere else in this repo: an automation that calls `lock.unlock`
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue