# 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).