Resilient, self-hosted music bot for Fluxer with persistent music data, queue safety, and operational monitoring.
| Area | Highlights |
|---|---|
| Playback | YouTube, SoundCloud, Deezer, Audius, radio streams, and mirrored imports from Spotify, Apple Music, Amazon Music, Tidal, Bandcamp, Audiomack, Mixcloud, and JioSaavn |
| Reliability | reconnect, playback resume, heartbeat watchdogs, REST retries, graceful shutdown |
| Persistence | playlists, favorites, history, templates, recap state, reputation/taste signals |
| Operations | /healthz, /readyz, /metrics, structured logging, optional Sentry |
- Reliable gateway handling with reconnect, resume, heartbeat watchdogs, and REST retry logic.
- Playback from YouTube, SoundCloud, Deezer, Audius, radio streams, and mirrored imports from Spotify, Apple Music, Amazon Music, Tidal, Bandcamp, Audiomack, Mixcloud, and JioSaavn URLs.
- Independent multi-voice playback sessions per guild, with separate queues per voice channel.
- Voice-channel-scoped 24/7 mode plus one-shot restart recovery for active non-24/7 sessions.
- Fast playlist UX with first-track start plus background queueing for large external playlists and mixes.
- Optional opportunistic YouTube startup prefetch to reduce time-to-audio for direct video playback.
- Persistent guild playlists, favorites, history, queue templates, recap data, and lightweight user taste/reputation signals in MongoDB.
- Built-in
/healthz,/readyz, and Prometheus/metricsendpoints. - Optional Sentry reporting, opt-in runtime playback diagnostics, and memory telemetry / heap snapshot controls.
This project is intended to be self-hosted for Fluxer.
The defaults already point at the official Fluxer services:
| Variable | Default |
|---|---|
API_BASE |
https://api.fluxer.app/v1 |
GATEWAY_URL |
wss://gateway.fluxer.app |
For most operators, the practical requirements are:
- a valid Fluxer bot token
- MongoDB
ffmpeg- ideally
yt-dlp
GATEWAY_ONLY_MODE=1 only skips the startup REST health check and gateway discovery. During normal Fluxer operation the bot still talks to the Fluxer REST API.
| Requirement | Notes |
|---|---|
| Node.js | >= 24 |
| MongoDB | local or managed |
ffmpeg |
on PATH or via FFMPEG_BIN |
yt-dlp |
strongly recommended for YouTube playback |
If pnpm is not installed yet, enable it through Corepack first:
corepack enableThen install dependencies and create your env file:
pnpm install --frozen-lockfile
cp .env.example .envWindows PowerShell:
Copy-Item .env.example .envBOT_TOKEN=your_fluxer_bot_token
MONGODB_URI=mongodb://127.0.0.1:27017pnpm startUseful commands:
| Action | Command |
|---|---|
| Start | pnpm start |
| Dev mode | pnpm dev |
| Build | pnpm run build |
| Typecheck | pnpm run typecheck |
| Tests | pnpm test |
| Spotify token helper | pnpm spotify:token |
This repo now includes docker-compose.yml and a production Dockerfile for Coolify.
- Use the Docker Compose deployment type.
- Set at least
BOT_TOKENin Coolify. - Leave
MONGODB_URIunset if you want to use the bundledmongoservice fromdocker-compose.yml. - Expose port
9091if you want Coolify or external monitoring to reach/healthz,/readyz, and/metrics. - Add any optional provider secrets such as
SPOTIFY_CLIENT_ID,SPOTIFY_CLIENT_SECRET,SPOTIFY_REFRESH_TOKEN,DEEZER_ARL, orSENTRY_DSNdirectly in Coolify as environment variables. - The bundled Docker defaults already set conservative memory values:
NODE_OPTIONS=--max-old-space-size=1024 --openssl-legacy-provider,MONGODB_MAX_POOL_SIZE=20, andMONGODB_MIN_POOL_SIZE=2. Override them in Coolify only if you want different limits. - The app now exits with code
1if it stays unhealthy for too long, sorestart: unless-stoppedcan actually restart the container. Tune this withUNHEALTHY_EXIT_AFTER_MSif needed.
If you use an external MongoDB instead of the bundled container, set:
MONGODB_URI=mongodb://user:password@your-mongo-host:27017The container image already installs ffmpeg, the standalone yt-dlp package, and the bgutil-ytdlp-pot-provider plugin. The bundled Compose file also starts the matching PO-token provider sidecar for harder YouTube hosts. YouTube cookies are not enabled by default; set YTDLP_COOKIES_FILE=/app/cookies.txt only when a video really needs account access. If YouTube blocks the server IP, set YTDLP_PROXY_URL so only failed YouTube attempts are retried through that proxy.
Minimal setup
Good for the simplest Fluxer self-hosted deployment.
- keep
ENABLE_YT_SEARCH=1 - keep
ENABLE_YT_PLAYBACK=1 - leave Spotify, Deezer, and Tidal credentials empty
- install
ffmpeg - install
yt-dlp
This still supports YouTube, SoundCloud, Audius, radio streams, and keyless metadata mirroring from Bandcamp, Audiomack, Mixcloud, and JioSaavn URLs.
Spotify import setup
Use this if users should be able to paste Spotify URLs.
- set
SPOTIFY_CLIENT_ID - set
SPOTIFY_CLIENT_SECRET - set
SPOTIFY_REFRESH_TOKEN - optionally set
SPOTIFY_MARKET
Spotify is metadata resolution only. Playback is mirrored to Deezer first when DEEZER_ARL is configured, otherwise to YouTube.
Deezer-first setup
Use this if you want Deezer-first text search and the best direct Deezer playback path.
- set
DEEZER_ARL - keep
ENABLE_DEEZER_IMPORT=1
With DEEZER_ARL configured, plain text play resolution prefers Deezer before falling back to YouTube.
| Problem | What to check |
|---|---|
| YouTube playback fails | ffmpeg, yt-dlp, YTDLP_COOKIES_FILE, YTDLP_COOKIES_FROM_BROWSER, YTDLP_YOUTUBE_CLIENT, YTDLP_EXTRA_ARGS, YTDLP_PROXY_URL, bgutil-pot |
| Commands fail but gateway connects | API_BASE, token validity, runtime REST access |
| Voice joins but no audio | Fluxer voice-side setup, VOICE_MAX_BITRATE, LiveKit-based publisher flow, and whether a restart recovery snapshot existed before reboot |
Container becomes unhealthy and just sits there |
Docker does not restart a process only because the healthcheck failed. Keep UNHEALTHY_EXIT_ENABLED=1 so prolonged /readyz failure forces a real restart, and inspect gateway/API reachability |
More detail
- make sure
ffmpegworks on the host - install
yt-dlp - if a video needs account access, set
YTDLP_COOKIES_FILEorYTDLP_COOKIES_FROM_BROWSER; avoid account cookies for normal public playback - for Docker, keep the default
bgutil-potsidecar,YTDLP_YOUTUBE_CLIENT=mweb, andYTDLP_EXTRA_ARGSwith theyoutubepot-bgutilhttpprovider URL - if your server IP is blocked but a residential proxy works, set
YTDLP_PROXY_URL=http://user:pass@host:port; the bot retries YouTube through it only after direct yt-dlp fails with a block-like error - if a specific YouTube extractor profile is unstable outside Docker, try
YTDLP_YOUTUBE_CLIENT=mweb,android_vr,web_safari - the runtime now retries multiple
yt-dlpclient strategies and can fall back toplay-dl, so outright startup failures usually point to host binaries or provider-side blocking ENABLE_YOUTUBE_PREFETCHED_PLAYBACK=1can improve startup latency for direct YouTube playback, but trades some robustness for speed
- check that
API_BASEstill points to the Fluxer API - verify the token works against both Fluxer Gateway and REST
- remember that
GATEWAY_ONLY_MODEdoes not remove the runtime REST dependency
- check the voice-side setup on Fluxer first
- the bot uses the LiveKit-based publisher in
src/voice/VoiceConnection.ts - lower
VOICE_MAX_BITRATEif your voice environment is bandwidth-constrained
src/config.ts is the main source of truth for runtime env parsing and validation. A few direct-read env vars also exist outside it, such as BOT_OWNER_USER_ID for owner-only maintenance commands and the script-only Spotify helper vars.
- Full env reference: docs/CONFIGURATION.md
- Architecture notes: docs/ARCHITECTURE.md
- Env template: .env.example
Default prefix: !
help [command|page_number],support,pingjoin,leaveradio <station|random|url>,stations [filter] [page]play <query|url>,playnext <query|url>,search <query>,pick <index>skip,voteskip,pause,resume,seek <time>now,queue [page],history [page],previous,replayremove <index>,clear,shuffle,loop <off|track|queue>,volume [0-200]filter,eq,tempo,pitch,effects,lyrics,stats
playlist <create|add|remove|show|list|delete|play> ...station <list|show|save|delete> ...fav,favs,ufav,favplay
prefixsettingsminimalmode [on|off]/minimal [on|off]volumedefault [0-200]djrolemusiclogvoteskipcfg247dedupe
moodmusicwebhookqueueguardtemplatechartsrecapvoiceprofilereputationtastehandoffpartyimportdiag [now|last|track|cancel](owner-only)eval <code>(owner-only, hidden)
Notes:
247is voice-channel-scoped, not guild-wide.- Owner-only commands use
BOT_OWNER_USER_ID.
High-level runtime notes are in docs/ARCHITECTURE.md.
| Area | Files |
|---|---|
| Bootstrap | src/app/bootstrap.ts, src/index.ts |
| Gateway | src/gateway.ts |
| REST | src/rest.ts |
| Commands and sessions | src/bot/ |
| Playback | src/player/ |
| Voice | src/voice/ |
| Storage | src/storage/ |
| Monitoring | src/monitoring/ |
- Website: vinto.music
- Contributing: CONTRIBUTING.md
- Security: SECURITY.md
- Code of conduct: CODE_OF_CONDUCT.md
- Support: SUPPORT.md
- Changelog: CHANGELOG.md
- Code license: LICENSE
- License model: source-available, private-use-only
- Operator policy templates: TERMS.md, PRIVACY.md
If you operate a public instance, you are responsible for complying with platform, provider, privacy, and copyright rules in your jurisdiction.