$uicide Music Bot
A self-hosted Discord music bot with a clean interface — built from source and served from this Gitea, ready for Unraid.
---
> [!NOTE]
> **This is a fork of a fork of a fork.** The bot is JMusicBot and the credit belongs to the
> people below. This repository exists so there is a copy of the working 0.7.1 line that
> cannot disappear, with a container image built *here* rather than pulled from someone
> else's registry. Nothing under `src/` is changed.
>
> | Author | Repository | Role |
> | --- | --- | --- |
> | **[jagrosh](https://github.com/jagrosh)** | [jagrosh/MusicBot](https://github.com/jagrosh/MusicBot) | Original author of JMusicBot |
> | **[arif-banai](https://github.com/arif-banai)** | [arif-banai/MusicBot](https://github.com/arif-banai/MusicBot) | Revived and maintains it — dependency updates, Java 25, config migration |
> | **[purnell569](https://github.com/purnell569)** | [purnell569/MusicBot](https://github.com/purnell569/MusicBot) | Fixed YouTube playback (0.7.1). **Direct parent of this repository.** |
> | **mward4** | this repository | Reliable copy, image built and served from `gitea.milesward.dev`, Unraid template, Gitea CI |
>
> Licensed under Apache 2.0 — see [LICENSE](LICENSE) and [NOTICE](NOTICE).
## Contents
- [What this copy adds](#what-this-copy-adds)
- [Quick start (Docker)](#quick-start-docker)
- [Unraid](#unraid)
- [Versions and upgrades](#versions-and-upgrades)
- [What's different in 0.7.1](#whats-different-in-071)
- [Requirements](#requirements)
- [Features](#features)
- [Supported sources and formats](#supported-sources-and-formats)
- [Running directly (without Docker)](#running-directly-without-docker)
- [Docker notes](#docker-notes)
- [Performance tuning](#performance-tuning)
- [Proxy configuration](#proxy-configuration)
- [Development and publishing](#development-and-publishing)
- [Questions, bug reports, credits](#questions-bug-reports-credits)
## What this copy adds
- **The image is built here.** `gitea.milesward.dev/mward4/musicbot` comes out of this
repository's [Dockerfile](Dockerfile), `linux/amd64`, with OCI labels recording the commit
and version it was built from. No dependency on `ghcr.io` or on anyone else's build.
- **An Unraid template**, [deploy/unraid/musicbot.xml](deploy/unraid/musicbot.xml): repository,
appdata mapping, JVM flags, and a `--user 99:100` default so a freshly created appdata
folder works without a chown step.
- **CI on Gitea Actions.** [.gitea/workflows/build-and-test.yml](.gitea/workflows/build-and-test.yml)
runs the full Maven build — 676 unit and 48 integration tests — and a Docker build on every
push to `main`. Upstream's GitHub workflows are kept in `.github/` but do not run here.
- **Upstream tags preserved** under `upstream/` (`upstream/v0.7.1`). This repository's
own release tags are `vX.Y.Z` and match the version in `pom.xml`.
- **A first run that actually works headless.** Upstream's image prompts for a token on a
console that does not exist and exits without writing anything. This image's
[entrypoint](docker/entrypoint.sh) runs the bot's own `generate-config` when `config.txt` is
missing, tells you what to edit, and stops. It also checks the data directory is writable
up front — the bot's own message for that case is a misleading "Another instance is already
running".
- **A publishing runbook**, [docs/PUBLISHING.md](docs/PUBLISHING.md), and a one-shot
[build-and-publish script](scripts/build-and-publish-unraid.sh) for cutting the next release.
## Quick start (Docker)
The container runs headless and writes a default `config.txt` on first run.
```bash
mkdir -p /path/to/musicbot
docker run --rm -it \
--name musicbot \
--user "$(id -u):$(id -g)" \
-v "/path/to/musicbot:/musicbot" \
gitea.milesward.dev/mward4/musicbot:latest
```
1. The first run finds no `config.txt`, writes the default one into the mounted directory, and
exits.
2. Edit `/path/to/musicbot/config.txt`: in the `discord { … }` block set `token` to your
Discord bot token and `owner` to your Discord user ID.
3. Run the container again.
If the log says the directory is not writable, fix the ownership of the mounted directory (or
change `--user`) and run again.
Already have a directory with `config.txt`, `Playlists/` and `serversettings.json` from a JAR
install? Mount that directory instead; nothing else changes.
Docker Compose:
```yaml
services:
musicbot:
image: gitea.milesward.dev/mward4/musicbot:latest # or :0.7.1 to pin a release
container_name: musicbot
user: "1000:1000" # whoever owns the mounted directory
volumes:
- /path/to/musicbot:/musicbot
restart: unless-stopped
```
See [docker-compose.example.yml](docker-compose.example.yml) for the commented version.
## Unraid
Import [deploy/unraid/musicbot.xml](deploy/unraid/musicbot.xml) — Docker tab → Add Container →
Template, or drop it into `/boot/config/plugins/dockerMan/templates-user/` — and apply. The
template maps `/musicbot` to `/mnt/user/appdata/musicbot`, publishes no ports (the bot only
makes outbound connections), and runs as `99:100` so the appdata folder Unraid creates is
writable straight away. Two things to know:
- **First start writes the config and stops.** Open `/mnt/user/appdata/musicbot/config.txt`,
set `token` and `owner` in the `discord { … }` block, start the container again. If the log
instead says `/musicbot is not writable by uid 99`, the folder was created with different
ownership: `chown -R 99:100 /mnt/user/appdata/musicbot` from the Unraid terminal and start
again.
- **The Discord side needs one switch.** In the [Developer Portal](https://discord.com/developers/applications),
under your application → Bot → Privileged Gateway Intents, turn **on** *Message Content
Intent*. Without it the bot never sees a command.
To run as the image's own non-root user (UID 10001) instead, clear *Extra Parameters* and
`chown -R 10001:10001 /mnt/user/appdata/musicbot` once.
## Versions and upgrades
| Tag | Meaning |
| --- | --- |
| `gitea.milesward.dev/mward4/musicbot:latest` | The newest release. |
| `gitea.milesward.dev/mward4/musicbot:0.7.1` | That release, pinned. A version tag is never rebuilt with different code. |
Each release is listed on this repository's
[Releases page](https://gitea.milesward.dev/mward4/MusicBot/releases) with the pull command,
what changed, and the JAR for non-Docker installs. To upgrade an Unraid container, change the
tag and apply; `config.txt`, `serversettings.json` and `Playlists/` live in the appdata mount
and carry over. The config format is versioned and migrates itself on start.
## What's different in 0.7.1
These are purnell569's notes for the release this copy is built from.
YouTube changed how it serves streams, which broke playback for everyone on the upstream
0.7.0 release. This version fixes that:
- **Signature cipher is no longer tied to OAuth.** Deciphering and authentication are unrelated
concerns, but the cipher was only applied when OAuth was on — so the default install fell back
to the bundled extractor and died with `Must find sig function from script: /s/player/…`.
It is now always applied and configurable via `playback.youtube.remoteCipher.url`.
- **Added the ANDROID client and reordered the client list by reliability.** ANDROID is currently
the only client YouTube reliably streams to anonymously; WEB now sits last because YouTube
answers it with SABR-only responses that contain no playable format.
- **Sign-in failures are now reported usefully.** The existing hint could never fire: it matched
on the aggregate exception message rather than the per-client reasons, and compared against an
ASCII apostrophe while YouTube sends `’` (U+2019).
- **youtube-source pinned past 1.18.2**, which predates the fixes for the TV client User-Agent
and for formats that omit `contentLength`.
Measured on the tracks that were failing: **5/5 playing, stable across repeat runs** (previously
1/5). No OAuth or Google account required.
## Requirements
Inside the container all of this is already handled. It matters when running the JAR directly.
- **Java 25 or newer.**
- **glibc 2.38 or newer** for the JDave/udpqueue native libraries. Ubuntu 24.04 "Noble" and
Debian 13 "Trixie" qualify; check with `ldd --version`. On Debian/Ubuntu you may also need
`libopus0` and `libsodium23`.
- **Message Content Intent** enabled in the Discord Developer Portal (see [Unraid](#unraid)).
[](https://jmusicbot.com/setup)
## Features
- Easy to run (just make sure Java is installed, and run!)
- Fast loading of songs
- No external keys needed (besides a Discord Bot token)
- Smooth playback
- Server-specific setup for the "DJ" role that can moderate the music
- Clean and beautiful menus
- Supports many sites, including YouTube, SoundCloud, and more
- Supports many online radio/streams
- Supports local files
- Playlist support (both web/youtube, and local)
## Supported sources and formats
Everything [lavaplayer](https://github.com/lavalink-devs/lavaplayer#supported-formats) supports:
- **Sources:** YouTube, SoundCloud, Bandcamp, Vimeo, Twitch streams, local files, HTTP URLs
- **Formats:** MP3, FLAC, WAV, Matroska/WebM (AAC, Opus or Vorbis), MP4/M4A (AAC), OGG streams
(Opus, Vorbis and FLAC), AAC streams, stream playlists (M3U and PLS)

## Running directly (without Docker)
The JAR for each version is attached to its
[release](https://gitea.milesward.dev/mward4/MusicBot/releases) as `JMusicBot-.zip`
(Gitea does not allow `.jar` attachments; unzip it) with a `SHA256SUMS.txt` beside it. Then,
with Java 25 on the path:
**Linux / macOS / Windows (CMD):**
```bash
java -Dfile.encoding=UTF-8 -Dnogui=true --enable-native-access=ALL-UNNAMED -jar JMusicBot-0.7.1.jar
```
**Windows (PowerShell):** PowerShell treats `-D` as its own parameter, so quote each option:
```powershell
java "-Dfile.encoding=UTF-8" "-Dnogui=true" "--enable-native-access=ALL-UNNAMED" "-jar" ".\JMusicBot-0.7.1.jar"
```
`-Dfile.encoding=UTF-8` keeps non-English characters (Cyrillic, Japanese, …) intact in Discord.
See the [Setup Page](https://jmusicbot.com/setup) and [Config reference](https://jmusicbot.com/config)
for `config.txt`.
## Docker notes
- **Config persistence.** `/musicbot` is the container's working directory and **must** be
mounted; `config.txt`, `serversettings.json` and `Playlists/` are read and written there.
- **User.** The image's own user is `jmusicbot` (UID 10001). Either make the mounted directory
writable by 10001 or pass `--user` (the Unraid template does the latter with `99:100`).
- **JAVA_OPTS.** The entrypoint starts the JVM with `-XX:+UseZGC -XX:+AlwaysPreTouch`. Set
`JAVA_OPTS` to replace that, e.g. `-Xms256m -Xmx512m -XX:+UseZGC -XX:+AlwaysPreTouch` to cap
the heap.
- **Image contents.** A jlink-trimmed Java 25 runtime on `bitnami/minideb:trixie` (glibc 2.41)
and the shaded JAR built with the `docker` Maven profile, which drops the Windows and macOS
natives. Labels: `org.opencontainers.image.revision` (commit) and `.version`.
Published images:
## Performance tuning
For optimal audio quality with minimal stuttering, the following JVM and configuration options
are recommended.
### JVM flags
The bot works best with ZGC (Z Garbage Collector), which provides sub-millisecond pause times:
```bash
java -Dfile.encoding=UTF-8 \
-XX:+UseZGC \
-XX:+AlwaysPreTouch \
-Dnogui=true \
--enable-native-access=ALL-UNNAMED \
-jar JMusicBot-0.7.1.jar
```
- `-XX:+UseZGC`: sub-millisecond GC pauses (generational mode is default in JDK 24+)
- `-XX:+AlwaysPreTouch`: pre-allocates memory at startup to avoid page faults
- `-Xms` / `-Xmx`: optional; fix or cap the heap (e.g. `-Xms256m -Xmx512m`)
The Docker image uses ZGC and AlwaysPreTouch by default; `JAVA_OPTS` overrides.
### Audio buffers
Configurable buffers in `config.txt` protect against GC pauses. Higher values add latency;
the defaults suit most setups.
```hocon
performance {
# NAS buffer duration in ms (protects against JVM pauses up to this duration)
nasBufferMs = 800
# Lavaplayer frame buffer duration in ms (amount of decoded audio to buffer)
frameBufferMs = 2000
}
```
## Proxy configuration
Specific components can be routed through an HTTP proxy while others connect directly —
useful for proxying audio traffic without touching Discord API traffic. Add to `config.txt`:
```hocon
proxy {
host = "127.0.0.1"
port = 8080
lavaplayer = true # Audio source requests (YouTube, SoundCloud, etc.)
jda = false # Discord API traffic
github = false # Version check requests
}
```
Leave `host` empty or `port` at 0 to disable. SOCKS proxies are not supported.
## Development and publishing
There is no Java toolchain requirement on the workstation; the build runs in a container:
```bash
docker run --rm -v "$PWD":/build -w /build -v musicbot-m2:/root/.m2 maven:3.9.12-eclipse-temurin-25-alpine mvn -B -ntp verify
```
That is what CI runs on every push to `main`. Cutting a release — tag, image build, registry
push, Gitea release with the JAR attached — is documented step by step in
[docs/PUBLISHING.md](docs/PUBLISHING.md); `scripts/build-and-publish-unraid.sh` does the image
part in one pass.
Keeping up with upstream:
```bash
git fetch upstream --tags
git merge upstream/main
```
`upstream` is `purnell569/MusicBot` on GitHub. Their
[DEVELOPMENT_WORKFLOW.md](docs/DEVELOPMENT_WORKFLOW.md) and
[ARCHITECTURE.md](docs/ARCHITECTURE.md) are kept as-is for reference; the GitHub Actions and
`ghcr.io` specifics in the former do not apply to this repository.
## Questions, bug reports, credits
Problems with the bot itself — playback, commands, config — are almost always upstream's to
fix and are best raised at [arif-banai/MusicBot](https://github.com/arif-banai/MusicBot/issues)
or [purnell569/MusicBot](https://github.com/purnell569/MusicBot/issues). Anything specific to
this copy (the image, the Unraid template, CI, publishing) belongs in
[this repository's issues](https://gitea.milesward.dev/mward4/MusicBot/issues).
The libraries that make the bot possible deserve the stars:
- [**JDA**](https://github.com/discord-jda/JDA)
- [**lavaplayer**](https://github.com/lavalink-devs/lavaplayer)
- [**youtube-source**](https://github.com/lavalink-devs/youtube-source)
### Editing
This bot (and the source code here) might not be easy to edit for inexperienced programmers.
The main purpose of having the source public is to show the capabilities of the libraries, to
allow others to understand how the bot works, and to allow those knowledgeable about Java, JDA,
and Discord bot development to contribute. If you choose to make edits, please do so in
accordance with the Apache 2.0 License.