Private personal music app served from the Mac mini through Caddy. Cloudflare Workers handle auth/import APIs; the app UI and FLAC media stream through the home Caddy path. Access requires a signed-in account.
- Public app:
https://music.streamarena.xyz(login required) - Mac mini Tailscale app/server:
http://100.121.144.60:5174 - Mac mini LAN app/server:
http://192.168.1.240:5174 - Worker backend for auth/import APIs:
https://spotify.erlinhoxha.workers.dev - Mac mini music folder:
/Users/hermes/Music - Remote app folder on Mac mini:
/Users/hermes/Developer/spotify - Music server launchd service:
xyz.streamarena.spotify-app - Shared Caddy launchd service:
xyz.streamarena.caddy - DNS drift watcher launchd service:
xyz.streamarena.spotify-dns-watch
music.streamarena.xyz should be a DNS-only record pointing at the home
public IP. Caddy terminates TLS, routes static assets and media directly to the
Mac mini server, and forwards auth/import API calls to the Worker backend.
Phone/browser
-> Caddy: music.streamarena.xyz
-> static app and media ranges: 127.0.0.1:5174
-> auth/import APIs: spotify.erlinhoxha.workers.dev
-> Worker calls MAC_MINI_ORIGIN=https://music.streamarena.xyz
-> Caddy trusted proxy-token route
-> Mac mini local server on 127.0.0.1:5174
-> /Users/hermes/Music
Uploaded/imported music is stored on the Mac mini. R2 is still configured as a fallback/legacy storage mode in the Worker, but direct Caddy is the production media path.
- Library load: app calls
/api/home; Caddy forwards browser API requests to the Worker, and the Worker calls back through direct Caddy with the shared proxy token and user id. - Audio streaming: song
audioUrlvalues point at/api/files/local/*; Caddy routes those range requests directly to the Mac mini server so seeking stays off the Worker/Tunnel path. - Artwork: local sidecar/embedded art is served first; missing art can be cached from online artwork lookup by the Mac mini server.
- Manual upload: browser posts to
/api/songs; Worker requires auth, forwards the upload to the Mac mini, and the Mac mini saves it into/Users/hermes/Music. - Spotify import: Worker resolves metadata/provider stream URLs, then calls the direct Caddy origin; the Mac mini downloads and stores the audio, cover, lyrics, and sidecar metadata.
- Keep the Mac mini music server launchd service running:
xyz.streamarena.spotify-app
- Keep the shared Caddy launchd service running:
xyz.streamarena.caddy(the installer also detects the legacycom.fightingentropy.streamarena-caddylabel on existing machines)
- Keep the DNS drift watcher launchd service running:
xyz.streamarena.spotify-dns-watch
- Operational scripts try the Mac mini Tailscale SSH alias
m4mini-tsfirst, then the raw Tailscale targethermes@100.121.144.60, then the LAN aliasm4mini.local, then the raw Ethernet targethermes@192.168.1.240. SetMINI_HOSTto force a single host, orMINI_HOSTSto override the fallback list. - Public traffic should reach the Mac mini through the router's 80/443 port
forwards and the direct DNS-only record for
music.streamarena.xyz. MAC_MINI_PROXY_TOKENis a Worker secret. Do not commit the real value.SPOTIFY_PROXY_TOKENon the Mac mini must match the Worker secret.- Direct LAN and Tailscale API requests require that token by default. Set
SPOTIFY_TRUST_LOCAL_NETWORK=1only when every device on the network should be treated as the library owner; loopback health checks remain trusted. SPOTIFY_LIBRARY_OWNER_EMAILSorSPOTIFY_LIBRARY_OWNER_USER_IDScontrols which app accounts can see and mutate the Mac mini music folder. Ownership fails closed unless an immutable id or verified email matches.- The Mac mini DNS watcher compares public DNS against the current home IP and logs drift. It does not store a Cloudflare API token or mutate DNS.
- The Settings page intentionally only shows user-facing playback/download settings now. Source status, edit-mode toggles, and Spotify cookie UI were removed from normal app chrome.
- The client caches API responses in memory for a short window and dedupes in-flight fetches, so navigating between Home/Search/etc does not reload the full song list every render. Uploads, imports, likes, sign-in, and sign-out invalidate the cache.
src/client/- React app shell, routes, auth provider, shared API cache.src/components/- reusable UI, player bar, song list/grid, upload controls.src/store/- Zustand stores for player state, likes, and older browser-local library helpers.src/worker/index.ts- Cloudflare Worker API, auth, D1/R2 fallback paths, Spotify import helpers, and Mac mini proxy mode.src/server/local-music-server.ts- Bun server deployed to the Mac mini. It scans the music folder, serves media with range support, accepts uploads, and writes.spotify.jsonsidecars.scripts/deploy-mini.sh- builds/syncs the app and local server to Mac mini.scripts/mini-host.sh- shared Mac mini SSH host resolver with Tailscale then LAN fallback.scripts/install-mini-server.sh- installs/restarts the Mac mini launchd app service.scripts/install-mini-caddy.sh- installs/updates the direct Caddy route formusic.streamarena.xyz.scripts/install-mini-dns-watch.sh- installs/updates the DNS drift watcher for the direct home Caddy hostname.scripts/sync-mini-music.sh- syncs audio/artwork/lyrics/sidecars to/Users/hermes/Music.scripts/check-mini.sh- health check for Mac mini server, launchd, library scan count, and direct Mini reachability.wrangler.jsonc- Cloudflare Worker bindings,workers.devbackend, andMAC_MINI_ORIGIN.FEATURES.md- current user-facing features and production capabilities.
bun install
bun run devWrangler simulates the Worker bindings during local development. For the local Mac mini-style server on this machine:
bun run build
SPOTIFY_MUSIC_DIR="$HOME/Music" bun run local:musicDeploy app/server updates to the Mac mini:
bun run mini:deployReuse the current local dist/ build:
bash scripts/deploy-mini.sh --skip-buildCheck the Mac mini server:
bun run mini:checkInstall/update the direct Caddy route:
bun run mini:install-caddyInstall/update the DNS drift watcher:
bun run mini:install-dns-watchSync music to the Mac mini:
bun run mini:sync-musicSync music from a specific local folder:
bash scripts/sync-mini-music.sh --source /Users/erlinhoxha/MoviesThe sync copies audio, cover, lyrics, and .spotify.json sidecar files. It does
not delete remote files.
The mini:* scripts try m4mini-ts, hermes@100.121.144.60,
m4mini.local, then hermes@192.168.1.240. Override with MINI_HOST=... for
one host or MINI_HOSTS="host1 host2" for a custom ordered list.
Deploy the Worker backend:
bun run db:migrate:remote
bun run deployD1 changes live in db/d1-migrations. Apply them explicitly before deploying;
the production request path never runs schema DDL. Use
bun run db:migrate:local when initializing a local Wrangler database.
The active wrangler.jsonc contains:
"workers_dev": true,
"MAC_MINI_ORIGIN": "https://music.streamarena.xyz"Set or rotate the Worker secret:
wrangler secret put MAC_MINI_PROXY_TOKENMac mini server env lives at /Users/hermes/.config/spotify/env and should
include:
HOST=0.0.0.0
PORT=5174
SPOTIFY_MUSIC_DIR=/Users/hermes/Music
SPOTIFY_DIST_DIR=/Users/hermes/Developer/spotify/dist/client
SPOTIFY_CACHE_DIR=/Users/hermes/Developer/spotify/cache
SPOTIFY_ARTWORK_LOOKUP=1
SPOTIFY_ARTWORK_COUNTRY=GB
SPOTIFY_PROXY_TOKEN=...
SPOTIFY_PROXY_HOSTNAMES=music.streamarena.xyz
SPOTIFY_TRUST_LOCAL_NETWORK=0
SPOTIFY_LIBRARY_OWNER_EMAILS=
SPOTIFY_LIBRARY_OWNER_USER_IDS=422ecfe9-bda3-4ac5-9eef-bcaa1176ff42
SPOTIFY_DNS_WATCH_NAME=music.streamarena.xyzUseful checks after deploy:
bun run typecheck
bun run lint
bun run build
bun run mini:check
bun run mini:install-caddy
bun run mini:install-dns-watch
curl -I https://music.streamarena.xyz
curl -sS -o /dev/null -w "%{http_code}\n" https://spotify.erlinhoxha.workers.dev/api/auth/sessionExpected behavior:
- Direct Caddy app returns
200. - Worker backend is reachable on
workers.dev. mini:checkalso traverses public DNS, TLS, Caddy, and the Worker session route so a missing public hostname cannot be hidden by healthy loopback checks.- Audio range requests through Caddy return
206. - Direct Mac mini API requests without the proxy token return
401on public hostnames. - DNS watch logs show
dns_okin/Users/hermes/.local/state/spotify/dns-watch.log. - Mac mini direct health check returns
200.
GET /api/homeGET /api/search-indexGET /api/music/sourceGET /api/libraryGET /api/likedGET /api/playlist/:idPOST /api/playlist/:id/reorderGET /api/songsPOST /api/songsGET /api/songs/:idPATCH /api/songs/:idPOST /api/songs/:id/assetsPOST /api/songs/spotifyPOST /api/songs/spotify/filePOST /api/songs/spotify/batchGET /api/songs/spotify/coverGET /api/files/*GET /api/files/local/*GET /api/artwork/*GET /api/artwork/local/*GET /api/artwork/r2/*GET/POST/DELETE /api/likesPOST /api/registerGET /api/auth/sessionGET /api/auth/mePOST /api/auth/signinPOST /api/auth/signout
bun run dev- local Vite/Worker dev server.bun run build- production build for Worker and client assets.bun run deploy- build and deploy to Cloudflare.bun run upload- build and dry-run deploy.bun run lint- ESLint.bun run local:music- run the Bun local music server.bun run mini:deploy- deploy build/server to Mac mini.bun run mini:install-server- install Mac mini launchd app service.bun run mini:install-caddy- install direct Caddy route.bun run mini:install-dns-watch- install DNS drift watcher launchd service.bun run mini:sync-music- sync music files to Mac mini.bun run mini:check- verify Mac mini health.bun run cf-typegen- regenerate Cloudflare binding types.