A Go bot that watches repositories on GitHub, GitLab, and Gitea for new releases or tags, and announces them via XMPP — both to MUC rooms and to individual users. https://github.com/roiarthurb/xmpp-releasetracker
  • Go 98.7%
  • Dockerfile 1.3%
Find a file
RoiArthurB 52723211e6
All checks were successful
Release / goreleaser (push) Successful in 3m18s
Test / test (push) Successful in 1m22s
fix: Avoid goreleaser-action GITHUB_TOKEN injection on Forgejo
The goreleaser-action injects GITHUB_TOKEN via core.exportVariable
before calling the binary, so setting GITHUB_TOKEN: "" in the step
env has no effect — the action overwrites it. GoReleaser v2 then sees
both GITHUB_TOKEN and GITEA_TOKEN and fails with 'multiple tokens found'.

Split into two steps: install-only puts the binary on PATH without
running it (and without injecting any token), then a plain run: step
unsets GITHUB_TOKEN before invoking goreleaser so only GITEA_TOKEN
is visible to it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 15:10:00 +07:00
.forgejo/workflows fix: Avoid goreleaser-action GITHUB_TOKEN injection on Forgejo 2026-06-10 15:10:00 +07:00
.github ci: Run vet and the race-enabled test suite on push and PRs 2026-06-10 14:06:19 +07:00
internal test: Cover xmpp stanza-building helpers 2026-06-10 14:05:27 +07:00
.dockerignore Add Docker support and update README 2026-03-05 11:10:23 +07:00
.gitignore Use repo owner avatar instead of release author avatar 2026-03-05 14:05:21 +07:00
.goreleaser.forgejo.yml feat: Add forgejo's workflow aside of Github's one 2026-06-03 09:28:39 +07:00
.goreleaser.yml Log version on startup; inject it from Git tag via ldflags 2026-03-11 11:39:15 +07:00
config.yml.example feat: Fetch GitHub releases via REST API when a token is configured 2026-06-10 10:06:03 +07:00
docker-compose.yml chore: Use github pre-built container in compose file 2026-03-13 21:10:46 +07:00
Dockerfile Add Docker support and update README 2026-03-05 11:10:23 +07:00
Dockerfile.goreleaser Fix GoReleaser Docker build context error 2026-03-05 14:56:29 +07:00
go.mod Implement xmpp-releasetracker in Go 2026-03-05 11:06:58 +07:00
go.sum Implement xmpp-releasetracker in Go 2026-03-05 11:06:58 +07:00
main.go refactor: Remove dead backend import shim 2026-06-10 09:33:39 +07:00
README.md feat: Fetch GitHub releases via REST API when a token is configured 2026-06-10 10:06:03 +07:00

xmpp-releasetracker

Banner logo

A Go bot that watches repositories on GitHub, GitLab, and Gitea for new releases or tags, and announces them via XMPP — both to MUC rooms and to individual users.

Features

  • Monitors GitHub, GitLab (self-hosted or gitlab.com), and Gitea instances
  • Tracks individual repos, user starred repos, organizations, and GitLab groups
  • Sends notifications to XMPP MUC rooms and direct messages
  • Persists seen releases in SQLite — no duplicate announcements after a restart
  • Silent on first run: snapshots current releases without announcing them
  • Optional pre-release filtering per entry or globally
  • Pure Go, no CGO required

Requirements

  • Go 1.25+
  • An XMPP account for the bot

Installation

git clone https://github.com/RoiArthurB/xmpp-releasetracker.git
cd xmpp-releasetracker
go build -o xmpp-releasetracker .

Configuration

Copy the example config and edit it:

cp config.yml.example config.yml
$EDITOR config.yml

Full reference

xmpp:
  jid: "bot@example.com"        # Bot's JID
  password: "secret"
  server: "example.com:5222"    # Optional. Defaults to the domain part of the JID on port 5222
  muc_nick: "releasebot"        # Nickname used when joining MUC rooms

backends:
  github:
    token: "ghp_xxx"            # Optional. With a token, releases are fetched via the REST API
                                # (authoritative pre-release/draft flags); without one, the
                                # unmetered Atom feed is used and pre-releases are guessed
                                # from tag names
  gitlab:
    - url: "https://gitlab.com"
      token: "glpat-xxx"
    - url: "https://gitlab.example.com"   # Self-hosted instance
      token: "glpat-yyy"
  gitea:
    - url: "https://gitea.example.com"
      token: "xxx"

database:
  path: "./releasetracker.db"   # Default: ./releasetracker.db

interval: 3600                  # Seconds between poll cycles. Default: 3600
verbose: false                  # Log warnings for repos without releases (404s). Default: false. Optional
skip_prereleases: false         # Skip pre-releases globally. Default: false. Optional

# Optional. Receives notifications for every tracked repo.
# Per-entry notify lists are additive on top of this.
# Duplicates between the two are ignored automatically.
default_notify:
  - jid: "you@example.com"
    type: direct
  - jid: "general@conference.example.com"
    type: muc

tracking:
  - ...                         # See "Tracking entries" below

Tracking entries

Each entry in tracking describes what to watch and where to send notifications.

All entries accept an optional skip_prereleases field that overrides the global setting for that entry:

- type: repo
  backend: gitea
  slug: "owner/repo"
  skip_prereleases: true        # only stable releases for this repo
  notify:
    - jid: "room@conference.example.com"
      type: muc

Backend support: on Gitea, and on GitHub when a token is configured, the pre-release flag comes straight from the forge's API and is authoritative. On GitLab, and on GitHub without a token (or while rate-limited, when the bot falls back to the Atom feed), pre-releases are detected heuristically from the tag and release name (-rc1, -beta, -alpha, …), so a release marked pre-release on the forge without such a marker will slip through.

Single repository

- type: repo
  backend: github               # github, gitlab, or gitea
  slug: "owner/repo"
  notify:
    - jid: "room@conference.example.com"
      type: muc
    - jid: "user@example.com"
      type: direct

For GitLab and Gitea, add an instance field to select which configured instance to use:

- type: repo
  backend: gitlab
  slug: "group/project"
  instance: "https://gitlab.example.com"
  notify:
    - jid: "room@conference.example.com"
      type: muc

User starred repositories

Watches all repositories starred by a given user:

- type: user_stars
  backend: github
  username: "someuser"
  notify:
    - jid: "room@conference.example.com"
      type: muc

Organization repositories (GitHub / Gitea)

Watches all repositories belonging to an organization:

- type: org
  backend: github
  org: "golang"
  notify:
    - jid: "room@conference.example.com"
      type: muc

Group repositories (GitLab)

Watches all projects inside a GitLab group:

- type: group
  backend: gitlab
  group: "gitlab-org"
  instance: "https://gitlab.com"
  notify:
    - jid: "room@conference.example.com"
      type: muc

Notification targets

Field Values Description
jid any JID Destination address
type muc or direct MUC room or direct (1:1) message

Usage

./xmpp-releasetracker -config config.yml

The -config flag defaults to config.yml in the current directory.

Docker

Build and run manually

docker build -t xmpp-releasetracker .
docker run -d \
  --name releasetracker \
  --restart unless-stopped \
  -v "$(pwd)/config.yml:/etc/xmpp-releasetracker/config.yml:ro" \
  -v releasetracker-data:/data \
  xmpp-releasetracker

Docker Compose

cp config.yml.example config.yml
$EDITOR config.yml        # fill in your credentials
docker compose up -d

To follow the logs:

docker compose logs -f

To rebuild after a code change:

docker compose up -d --build

Database path in containers

The container exposes /data as a volume for persistent storage. Set the database path in your config.yml accordingly:

database:
  path: "/data/releasetracker.db"

The config file itself is mounted read-only at /etc/xmpp-releasetracker/config.yml and never written to by the bot.

Notification format

[Github] owner/repo — v1.2.3 "Release name"
https://github.com/owner/repo/releases/tag/v1.2.3

Release notes here, capped at 10 lines / 2000 characters...

Messages use XEP-0393 Message Styling for the bold headline (*…*).

Inline images

The bot attaches the repository owner's avatar as an inline image using XEP-0385 Stateless Inline Media Sharing (SIMS). Support varies by client:

Client Image displayed?
MonocleChat (iOS/Android) Yes
Gajim No
Movim No (sanitizes external <img> tags)

Note: XEP-0385 has Deferred status. Contributions to improve client compatibility are welcome!

How it works

On each poll cycle the tracker:

  1. Resolves the list of repos for each tracking entry (direct slug, or expanded from stars/org/group)
  2. Fetches the latest releases from the backend API (up to 5 per repo)
  3. Checks each release against the seen_releases table in SQLite
  4. Announces unseen releases published within the last 7 days, in chronological order
  5. Records every release as seen so it is never announced again

First run / new repo: all currently known releases are recorded silently without announcement. This prevents flooding when a repo is first added to the config.

Project structure

main.go
config.yml.example
Dockerfile
compose.yml
internal/
  config/       # YAML loading and validation
  store/        # SQLite persistence (seen releases)
  backend/
    backend.go  # Backend interface and Release type
    github/     # GitHub Atom feed
    gitlab/     # GitLab REST API
    gitea/      # Gitea REST API
  tracker/      # Polling loop and notification logic
  xmpp/         # XMPP connection, MUC join, message sending

License

MIT