TLS for the phone app went live on the VPS on 2026-09-30:
- Mosquitto got a second, non-published listener on 8083 with
`protocol websockets`; port 1883 stays plain TCP for the boards
(ESP32s can't spare RAM for TLS).
- The mosquitto service joined the external Docker network npm_npmnet so
NPM (NPMplus) can reach mosquitto:8083 by name; it stays on `default`
to reach the Console backend at 172.20.0.1:8000.
- NPM proxy host mqtt.bellsystems.net -> http://mosquitto:8083 terminates
TLS and renews the Let's Encrypt cert. proxy_read/send_timeout 3600s
added so NPM doesn't drop idle MQTT connections after 60s. NPMplus has no
"Websockets Support" toggle (always on).
- Verified end to end: a paho client over wss://mqtt.bellsystems.net:443
(path /mqtt) authenticated via the Console backend and received a
heartbeat.
Chosen over native 8883 because NPM already owns 80/443 and certificate
renewal, so there is no extra cert handling on the host, and 443 also gets
through networks that block 8883.
The doc now gives the app's final transport (wss, 443, /mqtt, never 1883,
keepalive < 3600s), the listener/network/NPM layout, the end-to-end test,
rollback steps, and a new known gap: the backend's port 8000 is published
on 0.0.0.0, so the /mqtt/auth/* endpoints are reachable from the internet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Broker logs after the 2026-09-30 restart show some boards (PV26B02BP01R01,
BSVSPR-26C20B-STD10R-2KCDPH) subscribing to vesper/{serial}/control rather
than control/command. The app ACL only allows publishing to control/command,
so the app can't command those boards until their firmware is updated.
Recorded so nobody widens the ACL by accident.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The full mosquitto.conf (2026-09-30) has a single plain listener on 1883,
used by the boards. The phone app sends a Firebase ID token as its MQTT
password, so a TLS listener must be added before app users go live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Checked the live broker on 2026-09-30:
- mosquitto.conf sets no files-backend ACL path. A live test (device A
subscribing to device B's topics) was denied while A's own topics were
delivered, so the files backend does not grant-all and every ACL check
reaches the Console. The missing ACL file is therefore harmless.
- Recorded the container/image, config and passwd locations, which lines
already match the new code, and the one change still pending
(auth/acl cache 300s -> 60s before app users go live).
- Added the copy-paste isolation test so it can be re-run after any broker
config change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/mqtt-app-user-auth.md records what future sessions (and whoever builds
the phone app) need and can't get from the code alone:
- app connection contract: username app_<uid>, Firebase ID token as
password, client id prefix app_<uid>_, TLS only, allowed topics per acc,
- serial field (serial_number, legacy device_id) and the device_serials
mirror + every code path that must keep it in sync,
- the uid-field lookup rule and ACL cache invalidation,
- legacy "vesper" password flag and its log line,
- rollout checklist: backfill, go-auth VPS config (required/recommended),
files-ACL check, TLS listener,
- decisions/gaps: FlutterFlow must sync device_serials itself; the
device_users subcollection is intentionally ignored.
CLAUDE.md gets a short section pointing agents at it before they touch
MQTT auth or anything that edits user_list/status.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>