Gitea copy: $uicide Music Bot — image, Unraid template, CI, publishing
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:
Miles WardandClaude Fable 5.1 committed 2026-09-17 19:19:59 -04:00
1 parent 9991962e86
commit e772eb7c2b
8 files changed
+731 -249

No files matched your search

+60
View File
@@ -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
View File
@@ -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
+239 -235
View File
@@ -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.
[![Downloads](https://img.shields.io/github/downloads/purnell569/MusicBot/total.svg)](https://github.com/purnell569/MusicBot/releases/latest)
[![Stars](https://img.shields.io/github/stars/purnell569/MusicBot.svg)](https://github.com/purnell569/MusicBot/stargazers)
[![Release](https://img.shields.io/github/release/purnell569/MusicBot.svg)](https://github.com/purnell569/MusicBot/releases/latest)
[![License](https://img.shields.io/github/license/purnell569/MusicBot.svg)](https://github.com/purnell569/MusicBot/blob/main/LICENSE)
[![Build and Test](https://github.com/purnell569/MusicBot/actions/workflows/build-and-test.yml/badge.svg)](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)).
[![Setup](http://i.imgur.com/VvXYp5j.png)](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)
![Loading Example...](https://i.imgur.com/kVtTKvS.gif)
## 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.
+74
View File
@@ -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 -3
View File
@@ -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
View File
@@ -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)
+188
View File
@@ -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
```
+120
View File
@@ -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