Root cause: only /data/sounds/ survives container recreation (it's the
volume-mounted directory). /data/hub-state.json was written to the
container's ephemeral layer and lost on every redeploy.
- State file now saved to /data/sounds/hub-state.json
- Auto-migrates from legacy /data/hub-state.json if found
- Favorites and radio volumes will now persist across deploys
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- loadState: logs DATA_DIR path, writability check, lists files, shows
radio_favorites count on load
- saveState: read-back verification after atomic write, fallback to
direct write if rename fails
- /api/health: shows state diagnostics (file exists, file size, keys,
favorites count in memory vs disk, lastSaveOk)
- Helps diagnose why favorites are not persisting across deploys
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Atomic save: write to .tmp file then rename (prevents corruption
if container is killed mid-write)
- Backup: .bak copy created on successful load, used as fallback
if main file is corrupted
- Startup log shows loaded keys (verifies favorites survived)
Ensures radio_favorites and radio_volumes survive container
updates, crashes, and forced restarts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Each plugin gets its own Discord client and token:
- DISCORD_TOKEN_JUKEBOX (fallback: DISCORD_TOKEN) → Soundboard
- DISCORD_TOKEN_RADIO → Radio
discord.ts: factory createClient() instead of singleton
plugin.ts: per-plugin context storage via registerPlugin(p, ctx)
index.ts: creates/logins/shutdowns multiple bots independently
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Plugins now claim voice per guild via claimVoice(). When soundboard
plays a sound, radio's cleanup runs automatically (kills ffmpeg,
broadcasts SSE stop event). Fixes stale "now playing" UI on tab switch.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Only request Guilds + GuildVoiceStates. GuildMembers, GuildPresences
and MessageContent are privileged and require manual portal activation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add ADMIN_PWD and ALLOWED_GUILD_IDS env vars to config
- Extend PluginContext with adminPwd and allowedGuildIds
- Add adminAuth and guildFilter middleware for plugins
- Add /api/admin/login endpoint
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>