# denpa-radio a tiny independent multi-station internet radio. cassette-deck aesthetic, real audio, no algorithms. live at ****. ## what's playing | station | description | mp3 | opus | | --------- | ---------------------------------------- | --------------------------------------------------- | ----------------------------------------------------- | | minecraft | C418, Lena Raine, Aaron Cherof — vol α+β | (192k) | (96k) | drop a stream URL into VLC, mpv, foobar, or any internet-radio app. ## endpoints - `/.mp3` — MP3 192 kbps stream - `/.opus` — Opus 96 kbps stream - `/now-playing/.json` — currently playing (artist, title, album, duration, started_at) - `/now-playing/.history.json` — last 50 tracks for the station - `/api/stations.json` — list of all stations with metadata - `/api/stations//cover` — cover art (when present) - `/status-json.xsl` — raw Icecast status (listener counts, mount info) CORS is open on all of the above; build your own scrobbler / dashboard / overlay. ## architecture ``` Caddy (host) ─┬─ /.{mp3,opus}, /status-json.xsl → icecast (libretime/icecast) ├─ /now-playing/*.json → caddy file_server ├─ /api/* → frontend (astro 5 + react) └─ / → frontend ▲ └─ liquidsoap (savonet/liquidsoap) │ reads station folders │ writes now-playing JSON ▼ Hetzner Storage Box (CIFS, mounted RO) ``` three docker containers (icecast, liquidsoap, frontend) and a Caddy host service. each station is a folder on the storage box with a `_meta.yml`, a `tracks/` subdir, and an optional `cover.{jpg,png,webp}`. the frontend reads `_meta.yml` files at request time, so adding a station does not require a rebuild. ## adding a station on the storage box (mounted at `/mnt/trashbox/denpa-radio/library/` on summer): ``` mkdir -p /{tracks,jingles} cat > /_meta.yml </tracks/ # subdirs supported: tracks/Volume Alpha/, tracks/Singles/, etc — Liquidsoap recurses # (optional) drop cover.jpg into / (NOT inside tracks/ — would fail to decode) ``` then on summer: ``` # duplicate the existing minecraft station block in /srv/denpa-radio/config/liquidsoap.liq # rename references to your new id, then: docker compose -f /srv/denpa-radio/docker-compose.yml restart liquidsoap ``` the frontend picks up the new station within ~30s. no Caddy reload, no Icecast restart. station ids must match `[a-z0-9-]+`. ## tags - **v0.1.0** — radio backend live (Minecraft station, MP3 + Opus). - **v0.2.0** — frontend live (cassette deck UI, real WebAudio spectrum, recently-played). ## working on this repo see [`CLAUDE.md`](./CLAUDE.md) for the full operator's manual: SSH conventions, deploy quirks, frontend stack notes, Liquidsoap gotchas, and the things we've already learned the hard way.