Initial Arcana FX soundboard
This commit is contained in:
114
README.md
Normal file
114
README.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# Arcana FX
|
||||
|
||||
A dark-fantasy soundboard for D&D nights. The frontend shows a tile grid of
|
||||
audio effects; pressing a tile asks the backend to play that file through a
|
||||
configured Home Assistant `media_player`. Future panels for light control are
|
||||
present but disabled.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌────────────┐ /api/play ┌──────────────────────┐ POST media_player/play_media
|
||||
│ Frontend │ ─────────────▶│ Arcana FX backend │ ───────────────────────────────▶ Home Assistant
|
||||
│ (static) │ │ (Node 20 + Express) │ │
|
||||
└────────────┘ │ │◀──── fetches /media ─────────────────┘
|
||||
│ Filestash client │ ◀── share proof cookie + cat ── Filestash
|
||||
└──────────────────────┘
|
||||
```
|
||||
|
||||
- **Backend** (`src/`) is the only component that holds the Home Assistant
|
||||
token and the Filestash proof cookie. It exposes a deliberately narrow API.
|
||||
- **Frontend** (`public/`) is plain static HTML/CSS/JS — no build step.
|
||||
- Home Assistant pulls audio from `PUBLIC_BASE_URL/media?path=...`, which the
|
||||
backend streams from Filestash on demand. Nothing is preloaded.
|
||||
|
||||
## API surface
|
||||
|
||||
| Method | Path | Description |
|
||||
|-------:|------------------|------------------------------------------------------------|
|
||||
| GET | `/health` | Liveness probe + cache stats |
|
||||
| GET | `/api/config` | Public config (media player entity, light-control stubs) |
|
||||
| GET | `/api/effects` | Recursive list of audio effects (`?refresh=1` to force) |
|
||||
| POST | `/api/play` | Body `{ "path": "/Combat/Sword.mp3" }` |
|
||||
| POST | `/api/stop` | Stops the configured media player |
|
||||
| GET | `/media?path=…` | Intranet proxy streaming the file from Filestash |
|
||||
|
||||
`path` must be a share-relative path that was returned by `/api/effects`.
|
||||
Arbitrary Filestash paths, HA entities, or HA services cannot be reached
|
||||
through this API.
|
||||
|
||||
## Configuration
|
||||
|
||||
Two layers:
|
||||
|
||||
- `config/config.json` — non-secret, version-controlled. Filestash share,
|
||||
allowed extensions, allowed media player entity, cache TTL, light-control
|
||||
placeholders.
|
||||
- `.env` — secrets and per-deployment values. Copy from `.env.example`:
|
||||
|
||||
```
|
||||
HA_URL=http://192.168.33.33:8123
|
||||
HA_TOKEN=... # long-lived access token, never commit
|
||||
PUBLIC_BASE_URL=http://<lan-ip>:8091 # URL HA uses to fetch /media
|
||||
PORT=8091
|
||||
ALLOWED_ORIGINS= # optional CORS allow-list
|
||||
```
|
||||
|
||||
`PUBLIC_BASE_URL` must be reachable **from Home Assistant**, not just from
|
||||
your browser. On a typical home LAN it is `http://<docker-host-lan-ip>:8091`.
|
||||
|
||||
The allowed media player entity is `media_player.audio_test_media_player`,
|
||||
hardcoded in `config/config.json`. Requests for any other entity are rejected
|
||||
with `403`.
|
||||
|
||||
## Run with Docker Compose
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# edit .env: set HA_TOKEN and PUBLIC_BASE_URL
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Then open `http://<host>:8091`.
|
||||
|
||||
## Run locally (no Docker)
|
||||
|
||||
Requires Node.js 20+.
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# edit .env
|
||||
npm install
|
||||
npm run check # syntax check on backend sources
|
||||
npm start
|
||||
```
|
||||
|
||||
## Filestash flow
|
||||
|
||||
The backend obtains a proof cookie on first request:
|
||||
|
||||
1. `POST /api/share/<id>/proof` with `X-Requested-With: XmlHttpRequest`.
|
||||
Response sets the proof cookie and tells us the share's base path (e.g.
|
||||
`/DnD/Audio/`).
|
||||
2. `GET /api/files/ls?path=<absolute>&share=<id>` to walk directories.
|
||||
3. `GET /api/files/cat?path=<absolute>&share=<id>` to stream a file.
|
||||
|
||||
Recursive listing is cached in memory (`cacheTtlSeconds` in
|
||||
`config/config.json`, default 10 minutes). `/api/effects?refresh=1` forces
|
||||
a rescan.
|
||||
|
||||
## Security notes
|
||||
|
||||
- The HA token lives only in the backend process environment. It is never
|
||||
sent to the frontend.
|
||||
- Only the configured `media_player` entity is allowed. The HA client refuses
|
||||
any other entity id at the client layer.
|
||||
- Effect paths must match a known effect from the cached share scan, must
|
||||
start with `/`, and must not contain `..` or NUL bytes.
|
||||
- `.env` is git-ignored; only `.env.example` is committed.
|
||||
|
||||
## Future work
|
||||
|
||||
The "Light Control" panel renders disabled placeholders (`config/config.json
|
||||
> lightControl.placeholders`). Wiring real switches/lights will be a new
|
||||
narrow set of endpoints, on the same principle as the audio side.
|
||||
Reference in New Issue
Block a user