Gitea copy: $uicide Music Bot — image, Unraid template, CI, publishing
Build and Test / Build and Test (push) Canceled after 1m24s
Build and Test / Build and Test (push) Canceled after 1m24s
Fork of purnell569/MusicBot 0.7.1 (JMusicBot), kept here as a reliable copy with a container image built and served from this Gitea. - README retitled "$uicide Music Bot"; lineage table, Gitea image, Unraid and release instructions. Nothing under src/ changes. - Dockerfile: Maven stage on $BUILDPLATFORM (JAR is arch-neutral, so no QEMU for the build), OCI labels point at this repo and record commit and version via GIT_SHA/VERSION build args. - docker/entrypoint.sh: check the data dir is writable up front (the bot's own error for that is a misleading "another instance is running"), and on a missing config.txt run the bot's generate-config and stop with instructions instead of prompting on a console that does not exist. - deploy/unraid/musicbot.xml: Unraid template, --user 99:100 default. - .gitea/workflows/build-and-test.yml: mvn verify + docker build on push; its presence makes Gitea ignore the GitHub-only workflows in .github/. - scripts/build-and-publish-unraid.sh + docs/PUBLISHING.md: release runbook (keychain-safe registry push, release with zipped JAR). - docker-compose.example.yml points at the Gitea image. Upstream's v0.7.1 tag is preserved as upstream/v0.7.1; this repository's own release tag v0.7.1 is on this commit. Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
1 parent
9991962e86
commit
e772eb7c2b
8 files changed
+731
-249
No files matched your search
@@ -0,0 +1,60 @@
|
||||
name: Build and Test
|
||||
|
||||
# Gitea Actions counterpart of upstream's GitHub workflow. Gitea reads
|
||||
# .gitea/workflows first and ignores .github/workflows whenever this directory
|
||||
# exists, so the GitHub-only workflows (ghcr.io publishing, Codecov upload)
|
||||
# never run here. Everything executes inside containers, so the runner needs
|
||||
# nothing but Docker. Publishing the image and cutting a release stay manual:
|
||||
# see docs/PUBLISHING.md.
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- 'README.md'
|
||||
- 'LICENSE'
|
||||
- 'NOTICE'
|
||||
- 'docs/**'
|
||||
- 'deploy/**'
|
||||
- '*.example.yml'
|
||||
- '.github/**'
|
||||
- '.circleci/**'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and Test
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# The workspace is streamed in over stdin rather than bind-mounted, so
|
||||
# this works whether the runner talks to a local daemon or a remote
|
||||
# socket. A named volume keeps the Maven repository warm between runs.
|
||||
- name: Maven verify (Java 25, unit + integration tests)
|
||||
run: |
|
||||
set -eu
|
||||
COMMIT_TIME=$(git log -1 --format=%cI)
|
||||
tar cf - --exclude=./.git --exclude=./target . | docker run --rm -i \
|
||||
-v musicbot-ci-m2:/root/.m2 \
|
||||
maven:3.9.12-eclipse-temurin-25-alpine \
|
||||
sh -ec "mkdir -p /build && cd /build && tar xf - && mvn -B -ntp verify -Dproject.build.outputTimestamp=$COMMIT_TIME"
|
||||
|
||||
- name: Docker image builds and starts a JVM
|
||||
run: |
|
||||
set -eu
|
||||
VERSION=$(grep -m1 '<version>' pom.xml | sed 's/.*<version>\([^<]*\)<\/version>.*/\1/')
|
||||
docker build --pull --provenance=false --sbom=false \
|
||||
--build-arg "GIT_SHA=$(git rev-parse HEAD)" \
|
||||
--build-arg "VERSION=$VERSION" \
|
||||
--build-arg "BUILD_TIMESTAMP=$(git log -1 --format=%cI)" \
|
||||
-t "musicbot-ci:${{ github.sha }}" .
|
||||
docker run --rm --entrypoint java "musicbot-ci:${{ github.sha }}" -version
|
||||
docker rmi "musicbot-ci:${{ github.sha }}"
|
||||
+17
-6
@@ -2,7 +2,10 @@
|
||||
|
||||
# Multi-stage build for JMusicBot
|
||||
# Stage 1: Build the application
|
||||
FROM maven:3.9.12-eclipse-temurin-25-alpine AS builder
|
||||
# The JAR is architecture-neutral (the natives it bundles are chosen by the pom,
|
||||
# not by the build host), so this stage runs natively on whatever machine is
|
||||
# building — no QEMU for Maven when producing linux/amd64 on Apple Silicon.
|
||||
FROM --platform=$BUILDPLATFORM maven:3.9.12-eclipse-temurin-25-alpine AS builder
|
||||
|
||||
ARG BUILD_TIMESTAMP
|
||||
ENV BUILD_TIMESTAMP=$BUILD_TIMESTAMP
|
||||
@@ -62,12 +65,20 @@ RUN $JAVA_HOME/bin/jlink \
|
||||
# Required for jdave/udpqueue native libraries which need glibc >= 2.38
|
||||
FROM bitnami/minideb:trixie
|
||||
|
||||
# Provenance: which commit and version this image was built from. Set by
|
||||
# scripts/build-and-publish-unraid.sh; harmless when left empty.
|
||||
ARG GIT_SHA=""
|
||||
ARG VERSION=""
|
||||
|
||||
# OCI image labels for better traceability and management
|
||||
LABEL org.opencontainers.image.title="JMusicBot" \
|
||||
org.opencontainers.image.description="A cross-platform Discord music bot with a clean interface" \
|
||||
org.opencontainers.image.url="https://jmusicbot.com" \
|
||||
org.opencontainers.image.source="https://github.com/purnell569/MusicBot" \
|
||||
org.opencontainers.image.vendor="JMusicBot" \
|
||||
LABEL org.opencontainers.image.title="\$uicide Music Bot" \
|
||||
org.opencontainers.image.description="A cross-platform Discord music bot with a clean interface (JMusicBot fork)" \
|
||||
org.opencontainers.image.url="https://gitea.milesward.dev/mward4/MusicBot" \
|
||||
org.opencontainers.image.source="https://gitea.milesward.dev/mward4/MusicBot" \
|
||||
org.opencontainers.image.documentation="https://gitea.milesward.dev/mward4/MusicBot/src/branch/main/README.md" \
|
||||
org.opencontainers.image.revision="$GIT_SHA" \
|
||||
org.opencontainers.image.version="$VERSION" \
|
||||
org.opencontainers.image.vendor="Miles Ward" \
|
||||
org.opencontainers.image.licenses="Apache-2.0"
|
||||
|
||||
# Copy custom JRE from jre-builder stage
|
||||
|
||||
@@ -1,26 +1,153 @@
|
||||
<p align="center">
|
||||
<img src="https://i.imgur.com/zrE80HY.png" alt="JMusicBot logo" width="180">
|
||||
<img src="https://i.imgur.com/zrE80HY.png" alt="$uicide Music Bot" width="180">
|
||||
</p>
|
||||
|
||||
# JMusicBot
|
||||
<h1 align="center">$uicide Music Bot</h1>
|
||||
|
||||
<p align="center">
|
||||
<strong>A self-hosted Discord music bot with a clean interface — built from source and served from this Gitea, ready for Unraid.</strong>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img alt="release" src="https://img.shields.io/gitea/v/release/mward4/MusicBot?gitea_url=https%3A%2F%2Fgitea.milesward.dev&sort=semver&label=release&color=4d6bfe" />
|
||||
<img alt="ci" src="https://gitea.milesward.dev/mward4/MusicBot/actions/workflows/build-and-test.yml/badge.svg?branch=main" />
|
||||
<img alt="java" src="https://img.shields.io/badge/java-25-007396?logo=openjdk&logoColor=white" />
|
||||
<img alt="license" src="https://img.shields.io/badge/license-Apache--2.0-330f1f" />
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
> [!NOTE]
|
||||
> **This is a fork of a fork.** Credit for the bot belongs to the two people below — this
|
||||
> repository only fixes YouTube playback.
|
||||
> **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. **Direct parent of this fork.** |
|
||||
>
|
||||
> arif-banai's note on their own fork: *"This is a fork of JMusicBot from jagrosh. I started this
|
||||
> because it was not maintained and was not working anymore."*
|
||||
> | **[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).
|
||||
|
||||
## What's different in this fork
|
||||
## Contents
|
||||
|
||||
### 0.7.1
|
||||
- [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/<tag>` (`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:
|
||||
@@ -41,185 +168,85 @@ YouTube changed how it serves streams, which broke playback for everyone on the
|
||||
Measured on the tracks that were failing: **5/5 playing, stable across repeat runs** (previously
|
||||
1/5). No OAuth or Google account required.
|
||||
|
||||
[](https://github.com/purnell569/MusicBot/releases/latest)
|
||||
[](https://github.com/purnell569/MusicBot/stargazers)
|
||||
[](https://github.com/purnell569/MusicBot/releases/latest)
|
||||
[](https://github.com/purnell569/MusicBot/blob/main/LICENSE)
|
||||
[](https://github.com/purnell569/MusicBot/actions/workflows/build-and-test.yml)
|
||||
## Requirements
|
||||
|
||||
A cross-platform Discord music bot with a clean interface, and that is easy to set up and run yourself!
|
||||
|
||||
## ⚠️ Check your Java Version and other requirements
|
||||
|
||||
This version of JMusicBot changes/updates various dependencies. To ensure your bot continues to function correctly, please note the following mandatory changes:
|
||||
|
||||
* **Java 25 Minimum:** The bot now requires **Java 25 or higher**.
|
||||
|
||||
* **LibDave/udpqueue:** You **must** have **glibc >= 2.38**. *If you are using Docker, this is already handled for you.*
|
||||
* **Privileged Gateway Intents:** You **must** enable the **Message Content Intent** in your [Discord Developer Portal](https://discord.com/developers/applications).
|
||||
* *Navigate to: Your Application > Bot > Privileged Gateway Intents > Toggle "Message Content Intent" to ON.*
|
||||
* *Without this, the bot will not see your commands.*
|
||||
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)
|
||||
|
||||
- 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
|
||||
JMusicBot supports all sources and formats supported by [lavaplayer](https://github.com/lavalink-devs/lavaplayer#supported-formats):
|
||||
### Sources
|
||||
* YouTube
|
||||
* SoundCloud
|
||||
* Bandcamp
|
||||
* Vimeo
|
||||
* Twitch streams
|
||||
* Local files
|
||||
* HTTP URLs
|
||||
### Formats
|
||||
* MP3
|
||||
* FLAC
|
||||
* WAV
|
||||
* Matroska/WebM (AAC, Opus or Vorbis codecs)
|
||||
* MP4/M4A (AAC codec)
|
||||
* OGG streams (Opus, Vorbis and FLAC codecs)
|
||||
* AAC streams
|
||||
* Stream playlists (M3U and PLS)
|
||||
|
||||
## Example
|
||||
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)
|
||||
|
||||

|
||||
|
||||
## Setup
|
||||
Please see the [Setup Page](https://jmusicbot.com/setup) to run this bot yourself!
|
||||
## Running directly (without Docker)
|
||||
|
||||
## Running Directly (Without Docker)
|
||||
|
||||
When running JMusicBot directly (not in Docker), make sure to pass these JVM flags:
|
||||
The JAR for each version is attached to its
|
||||
[release](https://gitea.milesward.dev/mward4/MusicBot/releases) as `JMusicBot-<version>.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-All.jar
|
||||
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. Quote each JVM option so they are passed to `java` correctly:
|
||||
**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-All.jar"
|
||||
```
|
||||
Alternatively, use the stop-parsing token so the rest of the line is passed literally: `java --% -Dfile.encoding=UTF-8 -Dnogui=true ...`
|
||||
|
||||
`-Dfile.encoding=UTF-8` ensures non-English characters (Cyrillic, Japanese, etc.) display correctly in Discord. On Windows or older JDKs, omitting it can cause mojibake in slash-command autocomplete and embeds.
|
||||
|
||||
### Linux System Requirements
|
||||
|
||||
**Important:** Your system **must have glibc version 2.38 or higher**. Failure to meet this requirement will result in errors when using JDave and udpqueue.
|
||||
|
||||
> **Note:** Ubuntu 24.04 "Noble" and Debian 13 "Trixie" already include a compatible glibc version out of the box.
|
||||
|
||||
You can check your glibc version with:
|
||||
```bash
|
||||
ldd --version
|
||||
java "-Dfile.encoding=UTF-8" "-Dnogui=true" "--enable-native-access=ALL-UNNAMED" "-jar" ".\JMusicBot-0.7.1.jar"
|
||||
```
|
||||
|
||||
On Debian/Ubuntu-based systems, you may also need to install the following native audio library dependencies:
|
||||
`-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`.
|
||||
|
||||
```bash
|
||||
# Install required native library dependencies
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libopus0 libsodium23
|
||||
```
|
||||
## 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`.
|
||||
|
||||
If your version is below 2.38, you will need to upgrade your system or use a compatible runtime. (You can also use Docker, which is recommended.)
|
||||
Published images: <https://gitea.milesward.dev/mward4/-/packages/container/musicbot>
|
||||
|
||||
## Docker
|
||||
## Performance tuning
|
||||
|
||||
JMusicBot can be run using Docker for easy deployment and management. Pre-built images are available from the GitHub Container Registry. The container is configured to run headless and automatically generate a default `config.txt` on first run.
|
||||
For optimal audio quality with minimal stuttering, the following JVM and configuration options
|
||||
are recommended.
|
||||
|
||||
### Quick Start
|
||||
### JVM flags
|
||||
|
||||
#### For Existing Users (Migrating from JAR)
|
||||
|
||||
If you already have a directory with your `config.txt`, `Playlists/` folder, and other bot files, either provide the path to that directory or run the container from within it:
|
||||
|
||||
```bash
|
||||
docker run --rm -it \
|
||||
--name jmusicbot \
|
||||
-v "$(pwd):/musicbot" \
|
||||
ghcr.io/purnell569/musicbot:latest
|
||||
```
|
||||
|
||||
This mounts your current directory as the musicbot volume, so the bot will use your existing configuration and playlists.
|
||||
|
||||
#### For New Users
|
||||
|
||||
1. **Create a directory for your bot and run the container:**
|
||||
```bash
|
||||
mkdir -p /path/to/jmusicbot
|
||||
|
||||
docker run --rm -it \
|
||||
--name jmusicbot \
|
||||
-v "/path/to/musicbot:/musicbot" \
|
||||
ghcr.io/purnell569/musicbot:latest
|
||||
```
|
||||
|
||||
2. **First Run:**
|
||||
- On first run, if the mounted directory is empty, the bot will automatically generate a default `config.txt` file.
|
||||
- Edit `/path/to/musicbot/config.txt` on your host and add your Discord bot token.
|
||||
- Run the container again.
|
||||
|
||||
#### Using Docker Compose (Optional)
|
||||
|
||||
If you prefer docker-compose, copy the example compose file and update the volume path:
|
||||
|
||||
```bash
|
||||
cp docker-compose.example.yml docker-compose.yml
|
||||
# Edit docker-compose.yml and update the volume path
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
Example `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
jmusicbot:
|
||||
image: ghcr.io/purnell569/musicbot:latest
|
||||
container_name: jmusicbot
|
||||
volumes:
|
||||
- /path/to/musicbot:/musicbot
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
Check the [Docker Compose Example](docker-compose.example.yml) for more details.
|
||||
|
||||
### Important Notes
|
||||
|
||||
- **Config Persistence:** The `/musicbot` volume **must** be mounted for your configuration to persist. The bot reads and writes `config.txt` from `/musicbot` (the container's working directory).
|
||||
- **First Run:** If `config.txt` doesn't exist, the bot will generate a default one automatically. You'll need to edit it with your bot token before the bot can start.
|
||||
- **Image Tags:**
|
||||
- Use `ghcr.io/purnell569/musicbot:latest` for the latest build from the main branch
|
||||
- Use `ghcr.io/purnell569/musicbot:0.7.1` (replace with actual version) to pin a specific release version
|
||||
- **Recommendation:** For production, pin your image tag rather than using `latest`
|
||||
- **JAVA_OPTS:** The container uses ZGC and AlwaysPreTouch by default. Set `JAVA_OPTS` to add heap limits (e.g. `-Xms256m -Xmx512m`) or other flags. See [Performance Tuning](#performance-tuning) for details.
|
||||
|
||||
|
||||
To view published images, visit: `https://github.com/purnell569/MusicBot/pkgs/container/musicbot`
|
||||
|
||||
## Performance Tuning
|
||||
|
||||
For optimal audio quality with minimal stuttering, the following JVM and configuration options are recommended.
|
||||
|
||||
### JVM Flags (Recommended)
|
||||
|
||||
The bot works best with ZGC (Z Garbage Collector) which provides sub-millisecond pause times:
|
||||
The bot works best with ZGC (Z Garbage Collector), which provides sub-millisecond pause times:
|
||||
|
||||
```bash
|
||||
java -Dfile.encoding=UTF-8 \
|
||||
@@ -227,114 +254,91 @@ java -Dfile.encoding=UTF-8 \
|
||||
-XX:+AlwaysPreTouch \
|
||||
-Dnogui=true \
|
||||
--enable-native-access=ALL-UNNAMED \
|
||||
-jar JMusicBot-*.jar
|
||||
-jar JMusicBot-0.7.1.jar
|
||||
```
|
||||
|
||||
**Flag explanations:**
|
||||
- `-Dfile.encoding=UTF-8`: Ensures non-English characters display correctly in Discord (required on Windows or older JDKs)
|
||||
- `-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; set heap size if you want to limit or fix memory (e.g. `-Xms256m -Xmx512m`)
|
||||
- `-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. Set `JAVA_OPTS` to add heap limits or override:
|
||||
```yaml
|
||||
environment:
|
||||
- JAVA_OPTS=-Xms256m -Xmx512m -XX:+UseZGC -XX:+AlwaysPreTouch
|
||||
```
|
||||
The Docker image uses ZGC and AlwaysPreTouch by default; `JAVA_OPTS` overrides.
|
||||
|
||||
### Audio Buffer Configuration
|
||||
### Audio buffers
|
||||
|
||||
The bot includes configurable audio buffers that protect against GC pauses. These can be tuned in `config.txt`:
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
Higher values provide more protection against stuttering but add latency. The defaults (800ms NAS, 2000ms frame buffer) should work well for most setups.
|
||||
## Proxy configuration
|
||||
|
||||
## Proxy Configuration
|
||||
|
||||
JMusicBot supports granular proxy configuration, allowing you to route specific components through a proxy while letting others connect directly. This is useful when you need to proxy audio traffic (e.g., to bypass regional restrictions) without affecting Discord API communication.
|
||||
|
||||
### Configuration
|
||||
|
||||
Add the following to your `config.txt`:
|
||||
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 {
|
||||
# Proxy server hostname and port
|
||||
host = "127.0.0.1"
|
||||
port = 8080
|
||||
|
||||
# Enable proxy for specific components
|
||||
|
||||
lavaplayer = true # Audio source requests (YouTube, SoundCloud, etc.)
|
||||
jda = false # Discord API traffic
|
||||
github = false # Version check requests
|
||||
}
|
||||
```
|
||||
|
||||
### Common Use Cases
|
||||
Leave `host` empty or `port` at 0 to disable. SOCKS proxies are not supported.
|
||||
|
||||
**Route only audio traffic through proxy** (most common):
|
||||
```hocon
|
||||
proxy {
|
||||
host = "127.0.0.1"
|
||||
port = 18080
|
||||
lavaplayer = true
|
||||
jda = false
|
||||
github = false
|
||||
}
|
||||
## 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
|
||||
```
|
||||
|
||||
**Route all traffic through proxy**:
|
||||
```hocon
|
||||
proxy {
|
||||
host = "proxy.example.com"
|
||||
port = 8080
|
||||
lavaplayer = true
|
||||
jda = true
|
||||
github = true
|
||||
}
|
||||
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
|
||||
```
|
||||
|
||||
### Notes
|
||||
`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.
|
||||
|
||||
- Leave `host` empty or `port` as 0 to disable proxy entirely
|
||||
- Each component can be independently enabled/disabled
|
||||
- HTTP proxies are supported (SOCKS proxies are not currently supported)
|
||||
## Questions, bug reports, credits
|
||||
|
||||
## Development Workflow
|
||||
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).
|
||||
|
||||
This project follows a **trunk-based development** workflow. The `master` branch is always releasable, and all work happens in short-lived branches:
|
||||
The libraries that make the bot possible deserve the stars:
|
||||
|
||||
- **`feature/<slug>`** - New features (e.g., `feature/new-player-ui`)
|
||||
- **`fix/<slug>`** - Bug fixes (e.g., `fix/youtube-oauth`)
|
||||
- **`chore/<slug>`** - Maintenance tasks (e.g., `chore/update-deps`)
|
||||
- **`deps/<slug>`** - Dependency experiments (e.g., `deps/youtube-source-pr195`)
|
||||
- **`release/<version>`** - Release stabilization (optional, e.g., `release/0.6.3`)
|
||||
- [**JDA**](https://github.com/discord-jda/JDA)
|
||||
- [**lavaplayer**](https://github.com/lavalink-devs/lavaplayer)
|
||||
- [**youtube-source**](https://github.com/lavalink-devs/youtube-source)
|
||||
|
||||
Branch names are automatically validated by CI to ensure consistency. For detailed information about the development workflow, branch naming rules, and best practices, see [DEVELOPMENT_WORKFLOW.md](docs/DEVELOPMENT_WORKFLOW.md).
|
||||
### Editing
|
||||
|
||||
## Questions/Suggestions/Bug Reports
|
||||
**Please read the [Issues List](https://github.com/purnell569/MusicBot/issues) before suggesting a feature.**
|
||||
|
||||
If you have a question, need troubleshooting help, or want to brainstorm a new feature, please start a
|
||||
[Discussion](https://github.com/purnell569/MusicBot/discussions).
|
||||
|
||||
Note that issues with the bot itself are usually better raised upstream at
|
||||
[arif-banai/MusicBot](https://github.com/arif-banai/MusicBot/issues) — this fork only changes YouTube playback.
|
||||
|
||||
If you'd like to suggest a feature or report a reproducible bug here, please open an
|
||||
[Issue](https://github.com/purnell569/MusicBot/issues) on this repository. If you like this bot, be sure to add a star to the libraries that make this possible:
|
||||
- [**JDA**](https://github.com/DV8FromTheWorld/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. There are many requirements and dependencies required to edit and compile it, and there will not be support provided for people looking to make changes on their own. Instead, consider making a feature request (see the above section). If you choose to make edits, please do so in accordance with the Apache 2.0 License.
|
||||
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.
|
||||
@@ -0,0 +1,74 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
Unraid Docker template for $uicide Music Bot.
|
||||
|
||||
Drop this into /boot/config/plugins/dockerMan/templates-user/ on the Unraid
|
||||
box, or add the repo as a template repository under Docker → Add Container.
|
||||
|
||||
Bridge networking is correct: the bot only makes outbound connections (to
|
||||
Discord and to the audio sources) and listens on nothing, so no ports are
|
||||
published and there is no web UI.
|
||||
-->
|
||||
<Container version="2">
|
||||
<Name>MusicBot</Name>
|
||||
<Repository>gitea.milesward.dev/mward4/musicbot:latest</Repository>
|
||||
<Registry>https://gitea.milesward.dev/mward4/-/packages/container/musicbot</Registry>
|
||||
<Network>bridge</Network>
|
||||
<MyIP/>
|
||||
<Shell>sh</Shell>
|
||||
<Privileged>false</Privileged>
|
||||
<Support>https://gitea.milesward.dev/mward4/MusicBot/issues</Support>
|
||||
<Project>https://gitea.milesward.dev/mward4/MusicBot</Project>
|
||||
<Overview>
|
||||
$uicide Music Bot - a self-hosted Discord music bot with a clean interface.
|
||||
A fork of JMusicBot (jagrosh, maintained by arif-banai, YouTube playback
|
||||
fixed by purnell569), built from source and served from this Gitea.
|
||||
|
||||
FIRST RUN: start the container once with an empty data folder. It writes
|
||||
a default config.txt there and stops. Edit that file - in the discord { }
|
||||
block set token (your Discord bot token) and owner (your Discord user ID)
|
||||
- then start the container again. Playlists/ and serversettings.json live
|
||||
in the same folder. If the log says "/musicbot is not writable by uid 99",
|
||||
run chown -R 99:100 /mnt/user/appdata/musicbot from the Unraid terminal
|
||||
and start again.
|
||||
|
||||
DISCORD SIDE: create an application at discord.com/developers, add a Bot,
|
||||
and under Privileged Gateway Intents turn ON "Message Content Intent". The
|
||||
bot will not see commands without it. Invite it to your server with the
|
||||
bot scope.
|
||||
|
||||
PERMISSIONS: Extra Parameters runs the bot as Unraid's usual nobody:users
|
||||
(99:100), so a freshly created appdata folder just works with no chown
|
||||
step. Remove the --user flag to run as the image's own non-root user
|
||||
(10001) instead; then chown -R 10001:10001 the data folder once.
|
||||
|
||||
VERSIONS: :latest is the newest release. Every release also has its own
|
||||
image tag (e.g. 0.7.1) that can be pinned instead; the Releases page on
|
||||
Gitea lists them with notes. Java 25 and glibc 2.38+ are inside the image,
|
||||
nothing to install on the host.
|
||||
</Overview>
|
||||
<Category>MediaApp:Music Tools:</Category>
|
||||
<WebUI/>
|
||||
<TemplateURL>https://gitea.milesward.dev/mward4/MusicBot/raw/branch/main/deploy/unraid/musicbot.xml</TemplateURL>
|
||||
<Icon>https://i.imgur.com/zrE80HY.png</Icon>
|
||||
<ExtraParams>--user 99:100</ExtraParams>
|
||||
<PostArgs/>
|
||||
<CPUset/>
|
||||
<DateInstalled/>
|
||||
<DonateText/>
|
||||
<DonateLink/>
|
||||
<Requires>
|
||||
A Discord bot token (discord.com/developers) with the Message Content
|
||||
Intent enabled, and your Discord user ID for the owner setting. Both go
|
||||
into config.txt in the data folder after the first run.
|
||||
</Requires>
|
||||
<Config Name="Data" Target="/musicbot"
|
||||
Default="/mnt/user/appdata/musicbot"
|
||||
Mode="rw"
|
||||
Description="config.txt, serversettings.json and Playlists/. Generated on first run; edit config.txt here to add your bot token."
|
||||
Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/musicbot</Config>
|
||||
<Config Name="JAVA_OPTS" Target="JAVA_OPTS" Default="-XX:+UseZGC -XX:+AlwaysPreTouch"
|
||||
Mode=""
|
||||
Description="JVM flags. The default is ZGC with pre-touched memory for stutter-free audio. Add -Xms/-Xmx to cap the heap, e.g. -Xms256m -Xmx512m -XX:+UseZGC -XX:+AlwaysPreTouch"
|
||||
Type="Variable" Display="advanced" Required="false" Mask="false">-XX:+UseZGC -XX:+AlwaysPreTouch</Config>
|
||||
</Container>
|
||||
@@ -1,8 +1,6 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
jmusicbot:
|
||||
image: ghcr.io/purnell569/musicbot:latest # or ghcr.io/purnell569/musicbot:0.7.1 for a specific version
|
||||
image: gitea.milesward.dev/mward4/musicbot:latest # or gitea.milesward.dev/mward4/musicbot:0.7.1 to pin a release
|
||||
container_name: jmusicbot
|
||||
# The container runs as non-root user 'jmusicbot' (UID 10001) for security.
|
||||
# Ensure the mounted volume directory is readable/writable by UID 10001,
|
||||
|
||||
+32
-5
@@ -9,15 +9,42 @@ if [ ! -f "$JAR" ]; then
|
||||
fi
|
||||
|
||||
echo "[INFO] ========================================"
|
||||
echo "[INFO] JMusicBot Containerized"
|
||||
echo "[INFO] \$uicide Music Bot (JMusicBot) Containerized"
|
||||
echo "[INFO] ========================================"
|
||||
echo "[INFO] Selected jar: $JAR"
|
||||
echo "[INFO] Working directory: $(pwd)"
|
||||
if [ -f "config.txt" ]; then
|
||||
echo "[INFO] config.txt: Found (existing)"
|
||||
else
|
||||
echo "[INFO] config.txt: Not found (will be generated on first run)"
|
||||
echo "[INFO] Running as: uid=$(id -u) gid=$(id -g)"
|
||||
|
||||
# The bot keeps config.txt, serversettings.json, Playlists/ and its instance
|
||||
# lock in the working directory. If that is not writable the bot's own error is
|
||||
# a misleading "Another instance of JMusicBot is already running", so check
|
||||
# first and say what is actually wrong.
|
||||
if ! touch .write-test 2>/dev/null; then
|
||||
echo "[ERROR] $(pwd) is not writable by uid $(id -u)."
|
||||
echo "[ERROR] Make the mounted data directory writable by that user, e.g."
|
||||
echo "[ERROR] chown -R $(id -u):$(id -g) <host path mounted at /musicbot>"
|
||||
echo "[ERROR] or run the container with --user <uid>:<gid> matching the directory's owner."
|
||||
exit 1
|
||||
fi
|
||||
rm -f .write-test
|
||||
|
||||
# First run: write the default config.txt (the bot's own generate-config
|
||||
# command) and stop, so there is something to edit. Headless, the bot would
|
||||
# otherwise prompt for a token on a console that does not exist and exit
|
||||
# without writing anything.
|
||||
if [ ! -f "config.txt" ]; then
|
||||
echo "[INFO] config.txt: Not found - generating the default one"
|
||||
java -Dnogui=true --enable-native-access=ALL-UNNAMED -jar "$JAR" generate-config
|
||||
echo "[INFO] ========================================"
|
||||
echo "[INFO] Wrote $(pwd)/config.txt"
|
||||
echo "[INFO] Edit it and set, at minimum:"
|
||||
echo "[INFO] discord.token - your Discord bot token"
|
||||
echo "[INFO] discord.owner - your Discord user ID"
|
||||
echo "[INFO] then start the container again."
|
||||
echo "[INFO] ========================================"
|
||||
exit 0
|
||||
fi
|
||||
echo "[INFO] config.txt: Found"
|
||||
echo "[INFO] ========================================"
|
||||
|
||||
# Default JVM options for optimal audio performance (ZGC with sub-ms pauses)
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
# Publishing to Gitea
|
||||
|
||||
Releases are cut by hand. Each one needs a Gitea **personal access token**
|
||||
with the `write:repository` and `write:package` scopes, which is why none of
|
||||
this is automated: the token should only ever exist in your own shell or
|
||||
keychain.
|
||||
|
||||
On a Mac that has pushed before, git already holds the token in the login
|
||||
keychain and nothing needs exporting. Elsewhere, create one at **Gitea →
|
||||
Settings → Applications → Generate New Token** and export it once per shell
|
||||
session:
|
||||
|
||||
```bash
|
||||
export GITEA_TOKEN='<paste-your-token>'
|
||||
```
|
||||
|
||||
There is no Java or Maven on the Mac; every build step below runs in a
|
||||
container, so Docker is the only local requirement.
|
||||
|
||||
## 0. Where the code comes from
|
||||
|
||||
`origin` is this Gitea repository; `upstream` is
|
||||
<https://github.com/purnell569/MusicBot>, the fork this copy was taken from.
|
||||
|
||||
```bash
|
||||
git remote -v
|
||||
# origin https://gitea.milesward.dev/mward4/MusicBot.git
|
||||
# upstream https://github.com/purnell569/MusicBot.git
|
||||
```
|
||||
|
||||
To pick up a new upstream version:
|
||||
|
||||
```bash
|
||||
git fetch upstream --tags
|
||||
git merge upstream/main
|
||||
```
|
||||
|
||||
Upstream's own tags are kept in this repository under `upstream/<tag>`
|
||||
(`upstream/v0.7.1`, …) so they never collide with this repository's release
|
||||
tags. After a fetch, copy any new upstream tag across before tagging here:
|
||||
|
||||
```bash
|
||||
git tag upstream/v0.7.2 v0.7.2 && git tag -d v0.7.2
|
||||
```
|
||||
|
||||
## 1. Cut the version
|
||||
|
||||
The version is `<version>` in `pom.xml` and tracks upstream's (`0.7.1`). Do
|
||||
not bump it independently; a merge from upstream brings the new number with
|
||||
it. Then update the `IMAGE_TAG` and `GIT_REF` defaults in
|
||||
`scripts/build-and-publish-unraid.sh`, the pinned tag in the README, and
|
||||
commit. Tag with single quotes — the name starts with a dollar sign:
|
||||
|
||||
```bash
|
||||
git tag -a v0.7.1 -m '$uicide Music Bot v0.7.1'
|
||||
```
|
||||
|
||||
Tags are `vMAJOR.MINOR.PATCH` and match the pom version; the image carries the
|
||||
same number without the `v` (`0.7.1`) plus a moving `latest`.
|
||||
|
||||
## 2. Push the code and the tags
|
||||
|
||||
```bash
|
||||
git push origin main --follow-tags && git push origin 'refs/tags/upstream/*'
|
||||
```
|
||||
|
||||
With a token in the environment rather than the keychain, prefix each git
|
||||
command with `git -c "http.extraHeader=Authorization: token $GITEA_TOKEN"`.
|
||||
|
||||
## 3. Validate
|
||||
|
||||
Runs the same thing upstream's CI runs: 676 unit tests and 48 integration
|
||||
tests, then packages the JAR. The named volume keeps the Maven repository
|
||||
between runs so the second run is a couple of minutes.
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Expect `BUILD SUCCESS` and `target/JMusicBot-0.7.1-All.jar`. This is the full
|
||||
JAR (all platforms' natives), the one to attach to the release; the Docker
|
||||
build uses the `docker` Maven profile, which strips the Windows and macOS
|
||||
natives.
|
||||
|
||||
## 4. Build the image
|
||||
|
||||
The Maven stage runs on the builder's native architecture (the JAR is
|
||||
architecture-neutral), so only the jlink and runtime stages run under
|
||||
emulation when building `linux/amd64` on Apple Silicon.
|
||||
`--provenance=false --sbom=false` matters: Unraid's Docker cannot load the
|
||||
attestation manifests BuildKit attaches by default.
|
||||
|
||||
```bash
|
||||
docker build --pull --platform linux/amd64 --provenance=false --sbom=false --build-arg "GIT_SHA=$(git rev-parse HEAD)" --build-arg VERSION=0.7.1 --build-arg "BUILD_TIMESTAMP=$(git log -1 --format=%cI)" -t gitea.milesward.dev/mward4/musicbot:0.7.1 -t gitea.milesward.dev/mward4/musicbot:latest .
|
||||
```
|
||||
|
||||
Smoke test: an empty, writable data directory must produce a `config.txt` and
|
||||
the "edit it and start again" message, exit code 0. A named volume is used
|
||||
because Colima only shares `$HOME` into the VM; a bind mount of a path outside
|
||||
it silently becomes an empty root-owned directory inside the VM.
|
||||
|
||||
```bash
|
||||
docker volume create musicbot-smoke && docker run --rm -v musicbot-smoke:/musicbot alpine:3.20 sh -c 'touch /musicbot/.keep && chown -R 99:100 /musicbot' && docker run --rm --platform linux/amd64 --user 99:100 -v musicbot-smoke:/musicbot gitea.milesward.dev/mward4/musicbot:0.7.1; docker run --rm -v musicbot-smoke:/musicbot alpine:3.20 ls -la /musicbot; docker volume rm musicbot-smoke
|
||||
```
|
||||
|
||||
## 5. Push the image
|
||||
|
||||
**Never run `docker login` or `docker logout` for `gitea.milesward.dev` on a
|
||||
Mac where git keeps the token in the keychain.** Docker's `osxkeychain`
|
||||
credential helper maps to the same keychain item as git's: `login` fails with
|
||||
"The specified item already exists in the keychain (-25299)", and `logout`
|
||||
silently deletes the credential git was using, so the next `git push` fails
|
||||
with a bare 401. Write the auth into a throwaway config instead — a config
|
||||
that already contains an `auths` entry makes the CLI skip the helper:
|
||||
|
||||
```bash
|
||||
export DOCKER_CONFIG="$(mktemp -d)" && printf '{"auths":{"gitea.milesward.dev":{"auth":"%s"}}}' "$(printf 'mward4:%s' "$GITEA_TOKEN" | base64 | tr -d '\n')" > "$DOCKER_CONFIG/config.json" && docker push gitea.milesward.dev/mward4/musicbot:0.7.1 && docker push gitea.milesward.dev/mward4/musicbot:latest; rm -rf "$DOCKER_CONFIG"; unset DOCKER_CONFIG
|
||||
```
|
||||
|
||||
On a Colima host, `export DOCKER_HOST="unix://$HOME/.colima/default/docker.sock"`
|
||||
first: an isolated `DOCKER_CONFIG` has no `currentContext`, and the CLI would
|
||||
otherwise look for `/var/run/docker.sock`.
|
||||
|
||||
On a fresh machine, `scripts/build-and-publish-unraid.sh` does the clone,
|
||||
build and push in one pass using that same private-config approach:
|
||||
|
||||
```bash
|
||||
GITEA_TOKEN="$GITEA_TOKEN" ./scripts/build-and-publish-unraid.sh
|
||||
```
|
||||
|
||||
## 6. Verify the pull
|
||||
|
||||
The repo and package are public, so this needs no credentials — which is
|
||||
exactly how Unraid will fetch it. An empty `DOCKER_CONFIG` keeps the keychain
|
||||
out of the test:
|
||||
|
||||
```bash
|
||||
export DOCKER_CONFIG="$(mktemp -d)" && docker pull --platform linux/amd64 gitea.milesward.dev/mward4/musicbot:0.7.1; rm -rf "$DOCKER_CONFIG"; unset DOCKER_CONFIG
|
||||
```
|
||||
|
||||
## 7. Create the release
|
||||
|
||||
One release per version, on the `vX.Y.Z` tag, named `$uicide Music Bot vX.Y.Z`,
|
||||
with the pull command and what changed. Gitea only accepts the attachment
|
||||
types in its allow-list (`.zip`, `.txt`, … but not `.jar`), so the JAR ships
|
||||
zipped alongside a checksum file:
|
||||
|
||||
```bash
|
||||
cd target && cp JMusicBot-0.7.1-All.jar JMusicBot-0.7.1.jar && zip -q JMusicBot-0.7.1.zip JMusicBot-0.7.1.jar && shasum -a 256 JMusicBot-0.7.1.jar JMusicBot-0.7.1.zip > SHA256SUMS.txt && cd ..
|
||||
```
|
||||
|
||||
Create the release and upload the two files (the release `id` comes back in
|
||||
the first response):
|
||||
|
||||
```bash
|
||||
curl -s -X POST https://gitea.milesward.dev/api/v1/repos/mward4/MusicBot/releases -H "Authorization: token $GITEA_TOKEN" -H 'Content-Type: application/json' -d @release.json
|
||||
curl -s -X POST "https://gitea.milesward.dev/api/v1/repos/mward4/MusicBot/releases/<id>/assets?name=JMusicBot-0.7.1.zip" -H "Authorization: token $GITEA_TOKEN" -F attachment=@target/JMusicBot-0.7.1.zip
|
||||
curl -s -X POST "https://gitea.milesward.dev/api/v1/repos/mward4/MusicBot/releases/<id>/assets?name=SHA256SUMS.txt" -H "Authorization: token $GITEA_TOKEN" -F attachment=@target/SHA256SUMS.txt
|
||||
```
|
||||
|
||||
`release.json` is `{"tag_name":"v0.7.1","target_commitish":"main","name":"$uicide Music Bot v0.7.1","body":"…","draft":false,"prerelease":false}`.
|
||||
|
||||
## 8. Deploy on Unraid
|
||||
|
||||
Add a container with Repository set to:
|
||||
|
||||
```
|
||||
gitea.milesward.dev/mward4/musicbot:0.7.1
|
||||
```
|
||||
|
||||
(or `:latest`). Map `/musicbot` to `/mnt/user/appdata/musicbot`, no ports,
|
||||
bridge networking, and `--user 99:100` in Extra Parameters so the bot runs as
|
||||
Unraid's usual `nobody:users` and the appdata folder needs no chown. Or import
|
||||
`deploy/unraid/musicbot.xml` into
|
||||
`/boot/config/plugins/dockerMan/templates-user/` to get all of that prefilled.
|
||||
|
||||
First start writes `config.txt` into the appdata folder and stops. Set `token`
|
||||
and `owner` in it, start again. To upgrade an existing container, change the
|
||||
tag and apply; `config.txt`, `serversettings.json` and `Playlists/` all live in
|
||||
the appdata mount and carry over.
|
||||
|
||||
## Afterwards
|
||||
|
||||
If a token was exported, make sure it is not left in the shell:
|
||||
|
||||
```bash
|
||||
unset GITEA_TOKEN
|
||||
```
|
||||
Executable
+120
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env bash
|
||||
# build-and-publish-unraid.sh — build the $uicide Music Bot image and push it
|
||||
# to the Gitea container registry, ready to pull from an Unraid template.
|
||||
#
|
||||
# Run this on any host that can reach Gitea and has Docker. It clones a fresh
|
||||
# copy rather than building the working tree, so what ships is exactly what is
|
||||
# committed.
|
||||
#
|
||||
# Inputs (env vars, all optional except GITEA_TOKEN):
|
||||
# GITEA_TOKEN personal access token with `write:package` scope
|
||||
# GITEA_HOST default: gitea.milesward.dev
|
||||
# GITEA_OWNER default: mward4
|
||||
# GITEA_REPO default: MusicBot
|
||||
# IMAGE_NAME default: musicbot
|
||||
# IMAGE_TAG default: 0.7.1 (also pushed as :latest)
|
||||
# PLATFORM default: linux/amd64 (Unraid is x86_64)
|
||||
# GIT_REF default: v0.7.1 (tag/branch to build)
|
||||
# WORKDIR default: /tmp/musicbot-build (removed on exit)
|
||||
#
|
||||
# After this, set the Unraid template's Repository to:
|
||||
# <GITEA_HOST>/<owner>/musicbot:<tag> (or :latest)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
GITEA_HOST=${GITEA_HOST:-gitea.milesward.dev}
|
||||
GITEA_OWNER=${GITEA_OWNER:-mward4}
|
||||
GITEA_REPO=${GITEA_REPO:-MusicBot}
|
||||
IMAGE_NAME=${IMAGE_NAME:-musicbot}
|
||||
IMAGE_TAG=${IMAGE_TAG:-0.7.1}
|
||||
PLATFORM=${PLATFORM:-linux/amd64}
|
||||
GIT_REF=${GIT_REF:-v0.7.1}
|
||||
WORKDIR=${WORKDIR:-/tmp/musicbot-build}
|
||||
|
||||
# OCI distribution rejects uppercase repository paths, so the image name is
|
||||
# lowercased independently of the repo name.
|
||||
IMAGE_NAME="$(printf '%s' "$IMAGE_NAME" | tr '[:upper:]' '[:lower:]')"
|
||||
FULL_TAG="$GITEA_HOST/$GITEA_OWNER/$IMAGE_NAME:$IMAGE_TAG"
|
||||
LATEST_TAG="$GITEA_HOST/$GITEA_OWNER/$IMAGE_NAME:latest"
|
||||
|
||||
if [[ -z "${GITEA_TOKEN:-}" ]]; then
|
||||
echo "ERROR: set GITEA_TOKEN to a token with write:package scope." >&2
|
||||
echo " Gitea -> Settings -> Applications -> Generate New Token." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
for tool in git docker; do
|
||||
command -v "$tool" >/dev/null 2>&1 \
|
||||
|| { echo "ERROR: '$tool' not found in PATH" >&2; exit 2; }
|
||||
done
|
||||
|
||||
# A private docker config so the token never lands in ~/.docker/config.json.
|
||||
# The trap wipes it (and the checkout) on any exit path.
|
||||
#
|
||||
# The auth is written into the file directly rather than via `docker login`.
|
||||
# With an empty config the CLI falls back to the platform credential helper,
|
||||
# and on macOS `docker-credential-osxkeychain` shares its keychain item with
|
||||
# git's `osxkeychain` helper: `login` then fails with -25299 if git already
|
||||
# stored the token, and `logout` deletes the credential git was using. A
|
||||
# config that already contains an `auths` entry skips the helper entirely.
|
||||
DOCKER_CONFIG="$(mktemp -d)"
|
||||
export DOCKER_CONFIG
|
||||
trap 'rm -rf "$DOCKER_CONFIG" "$WORKDIR"' EXIT
|
||||
|
||||
# Colima keeps its socket outside /var/run; an isolated DOCKER_CONFIG has no
|
||||
# `currentContext`, so point at the daemon explicitly when one is not set.
|
||||
if [[ -z "${DOCKER_HOST:-}" && -S "$HOME/.colima/default/docker.sock" ]]; then
|
||||
export DOCKER_HOST="unix://$HOME/.colima/default/docker.sock"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "==> Workspace : $WORKDIR"
|
||||
echo "==> Gitea host : $GITEA_HOST"
|
||||
echo "==> Building ref : $GIT_REF"
|
||||
echo "==> Image to push : $FULL_TAG"
|
||||
echo "==> Platform : $PLATFORM"
|
||||
echo
|
||||
|
||||
# Step 1: shallow clone of the exact ref.
|
||||
rm -rf "$WORKDIR"
|
||||
git -c "http.extraHeader=Authorization: token $GITEA_TOKEN" \
|
||||
clone --depth 1 --branch "$GIT_REF" \
|
||||
"https://$GITEA_HOST/$GITEA_OWNER/$GITEA_REPO.git" "$WORKDIR"
|
||||
|
||||
cd "$WORKDIR"
|
||||
GIT_SHA="$(git rev-parse HEAD)"
|
||||
COMMIT_TIME="$(git log -1 --format=%cI)"
|
||||
|
||||
# Step 2: build. --pull so a stale cached base image doesn't bake in a CVE
|
||||
# that upstream has already fixed. --provenance/--sbom off because Unraid's
|
||||
# Docker cannot load the attestation manifests BuildKit attaches by default.
|
||||
#
|
||||
# The Maven stage runs on the builder's native architecture (the JAR is
|
||||
# architecture-neutral); only the jlink and runtime stages run under emulation
|
||||
# when cross-building linux/amd64 on an arm64 host. BUILD_TIMESTAMP pins the
|
||||
# JAR's reproducible-build timestamp to the commit, as upstream's CI does.
|
||||
docker build \
|
||||
--pull \
|
||||
--platform "$PLATFORM" \
|
||||
--provenance=false --sbom=false \
|
||||
--build-arg "GIT_SHA=$GIT_SHA" \
|
||||
--build-arg "VERSION=$IMAGE_TAG" \
|
||||
--build-arg "BUILD_TIMESTAMP=$COMMIT_TIME" \
|
||||
--tag "$FULL_TAG" \
|
||||
--tag "$LATEST_TAG" \
|
||||
.
|
||||
|
||||
# Step 3: registry auth, written straight into the private config (see above).
|
||||
printf '{"auths":{"%s":{"auth":"%s"}}}' "$GITEA_HOST" \
|
||||
"$(printf '%s:%s' "$GITEA_OWNER" "$GITEA_TOKEN" | base64 | tr -d '\n')" \
|
||||
> "$DOCKER_CONFIG/config.json"
|
||||
|
||||
# Step 4: push both tags. The trap removes the config, so there is no logout.
|
||||
docker push "$FULL_TAG"
|
||||
docker push "$LATEST_TAG"
|
||||
|
||||
echo
|
||||
echo "==> Done. Pull from an Unraid template with:"
|
||||
echo " $FULL_TAG"
|
||||
echo " $LATEST_TAG"
|
||||
echo
|
||||
Reference in new issue
Block a user