Files
bellsystems-cp/docs/melody-binary-serving.md
bonamin 72f7e00990 feat: serve melody .bsm binaries over plain HTTP instead of Firebase Storage
ESP32 devices can't spare the 40KB+ RAM a TLS client needs, so Firebase
Storage's HTTPS-only download URLs were blocking melody downloads. Binaries
are now written to local disk (./data/melody_binaries) and served through a
new unauthenticated /api/melodies/download/{pid} route, exposed publicly on
a separate melodies.bellsystems.net vhost (plain HTTP, no TLS) so the main
console domain can stay HTTPS-only with no exceptions. Preview audio still
uses Firebase Storage since it's only ever fetched by the HTTPS admin UI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 09:41:24 +03:00

72 lines
3.9 KiB
Markdown

# Melody Binary Serving (Plain HTTP)
## Why this exists
ESP32 devices download `.bsm` melody files directly, using a URL supplied through the
Android app. The download previously pointed at a Firebase Storage public URL (HTTPS
only). ESP32's TLS client needs 40KB+ of RAM to open an HTTPS connection, which these
devices can't reliably spare. To fix this, melody `.bsm` binaries are now stored and
served by our own backend over plain HTTP, instead of Firebase Storage.
This does **not** affect the audio preview file (`information.previewURL`) — that's
only ever fetched by the browser admin UI, which is already HTTPS, so it stays on
Firebase Storage unchanged.
## What changed
- **Storage**: `.bsm` binaries are written to `./data/melody_binaries/{melody_uid}.bsm`
on the host (mounted into the backend container at `/app/storage/melody_binaries`,
same pattern as `./data/firmware` and `./data/built_melodies`).
- **Upload path**: `SelectArchetypeModal` / `BuildOnTheFlyModal` still call
`POST /api/melodies/{melodyId}/upload/binary` exactly as before. The backend
(`backend/melodies/service.py::save_binary_for_melody`) now writes the bytes to
local disk instead of uploading to Firebase Storage, and returns a URL like
`http://melodies.bellsystems.net/download/{pid}`.
- **Stored URL**: the melody's `url` field (Firestore + SQLite) now holds that
plain-HTTP URL instead of a Firebase public URL. No schema change — `url` was
already a plain string field.
- **Download route**: `GET /api/melodies/download/{pid}` (`backend/melodies/router.py`)
is unauthenticated (devices have no login token) and resolves `pid` → the melody's
`{uid}.bsm` file on disk, same pattern as the existing firmware download route
(`backend/firmware/router.py::download_firmware`).
- **PID uniqueness**: since `pid` is now the public lookup key for the download route,
`backend/melodies/service.py::_check_pid_unique` rejects creating/renaming a melody
to a PID already used by another melody (409 Conflict).
- **Cleanup**: deleting a melody or its binary file now also deletes the local
`.bsm` file (`service.delete_local_binary`).
## Infrastructure (outside this repo)
The plain-HTTP requirement is handled by keeping `melodies.bellsystems.net` on a
**separate** hostname from `console.bellsystems.net`, so the console's NPM proxy host
can stay HTTPS-only with no per-path exceptions.
Required, one-time setup outside this repository:
1. **DNS**: add an A/CNAME record for `melodies.bellsystems.net` pointing at the same
host as `console.bellsystems.net`.
2. **NPM (Nginx Proxy Manager) proxy host**: create a new proxy host for
`melodies.bellsystems.net` forwarding to the `nginx` container's exposed port
(`90` per `docker-compose.yml`). **Do not force SSL / do not enable the "Force
SSL" redirect** on this proxy host — it must remain reachable over plain HTTP.
The in-repo `nginx/nginx.conf` already has a dedicated `server_name
melodies.bellsystems.net` block that maps `/download/{pid}` to the backend's
`/api/melodies/download/{pid}` route, so no further nginx changes are needed once
the NPM proxy host exists.
## Verification
1. Build/select an archetype for a melody → confirm a file appears at
`./data/melody_binaries/{uid}.bsm` and the melody's `url` becomes
`http://melodies.bellsystems.net/download/{pid}`.
2. `curl http://localhost:8000/api/melodies/download/{pid}` (direct to backend,
bypassing nginx) → returns the `.bsm` bytes, no auth header needed.
3. `curl http://localhost:90/api/melodies/download/{pid}` (through nginx on the
console server block) → same result.
4. Once NPM is configured, `curl http://melodies.bellsystems.net/download/{pid}`
→ same result, over plain HTTP.
5. Delete the melody → confirm the local `.bsm` file is removed.
6. Upload a preview audio file → confirm it still lands in Firebase Storage and
`previewURL` still populates (regression check).