Files
MusicBot/docs/PUBLISHING.md
T
Miles WardandClaude Fable 5.1 6fa37315d8
Build and Test / Build and Test (push) Successful in 1m12s
publish: force gzip on every layer so no blob exceeds Cloudflare's 100 MB
bitnami/minideb ships its base layer as an uncompressed 133 MB tar; with
the containerd image store docker push sends it as-is and Cloudflare in
front of Gitea answers 413. Build with force-compression=true (base layer
becomes ~43 MB) and document why.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-17 19:27:05 -04:00

8.4 KiB

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:

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.

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:

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:

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:

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

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.

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. Two flags matter:

  • --provenance=false --sbom=false: Unraid's Docker cannot load the attestation manifests BuildKit attaches by default.
  • --output type=docker,compression=gzip,force-compression=true: bitnami/minideb publishes its base layer as an uncompressed 133 MB tar. A daemon using the containerd image store pushes blobs exactly as stored, and Gitea sits behind Cloudflare, which rejects any request body over 100 MB with 413 Payload Too Large. Forcing gzip on every layer takes that one to about 43 MB; the JAR layer (61 MB) and the JRE layer (41 MB) are already under the limit.
docker buildx 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 --output type=docker,compression=gzip,force-compression=true .

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.

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:

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:

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:

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:

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):

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:

unset GITEA_TOKEN