The Updater is a small Go tool that watches the Grav wiki, Redmine and Gitea for changes and posts new activity to Discord webhooks. A local cache makes sure every change is announced only once.
Every INTERVAL_MINUTES minutes the updater walks through its sources:
*_CONTENT_LIMIT items.cache.json.cache.json back at the end of the round so the next one does not repeat it.| Source | Request | Change signal | Message |
|---|---|---|---|
| Grav wiki | GET /custom/updates.json?limit=N |
the page's modified timestamp |
📖 [Grav] - Page title |
| Redmine | GET /issues.json?limit=N&sort=updated_on:desc |
the issue's updated_on |
🎫 [Redmine] - #123 Subject |
| Gitea | GET /api/v1/repos/ORG/REPO/commits?limit=N per repo |
the commit SHA | 📝 [Gitea] - (org/repo) commit message |
The wiki lists its pages at /custom/updates.json, newest file modification first (template: themes/ttgwiki/templates/updates.json.twig in the wiki-pages repo). The updater only reads route, title and modified; the link is GRAV_BASE_URL + route.
{"lang":"en","count":1,"pages":[
{"route":"/team/onboarding","title":"Onboarding","modified":1792479011,
"updatedAt":"2026-10-20T06:50:11+00:00","locale":"en","tags":["team"],"private":true}
]}
grav_ + route, so every save of a page (a new modified) produces a new message.private: true) are listed with their titles too; the endpoint does not return their bodies, and the updater does not ask for them.Environment variables; copy env-example to .env. Every source runs in every round — one without a URL quietly returns an empty list.
| Variable | Description | Default |
|---|---|---|
INTERVAL_MINUTES |
Time between two rounds (minutes). | 5 |
DISCORD_WEBHOOK |
Fallback webhook; every source has its own now, so nothing uses it. | - |
DISCORD_FAKE |
If true, messages only go to the log, not to Discord. |
false |
| Variable | Description |
|---|---|
GRAV_BASE_URL |
The wiki base URL, without a trailing slash: https://wiki.teletypegames.org. |
GRAV_DISCORD_WEBHOOK |
Discord webhook for wiki notifications. |
GRAV_CONTENT_LIMIT |
How many recently modified pages to check. |
The Grav endpoint needs no token.
| Variable | Description |
|---|---|
REDMINE_BASE_URL |
The Redmine base URL (https://pm.teletypegames.org). |
REDMINE_KEY |
Redmine API key (X-Redmine-API-Key). |
REDMINE_DISCORD_WEBHOOK |
Discord webhook for Redmine notifications. |
REDMINE_CONTENT_LIMIT |
How many recently updated issues to check. |
| Variable | Description |
|---|---|
GITEA_BASE_URL |
The Gitea base URL (https://git.teletypegames.org). |
GITEA_TOKEN |
Gitea API token. |
GITEA_REPOS |
Comma-separated repos to watch, e.g. games/mranderson,games/impostor. Only commits are watched, not issues or PRs. |
GITEA_DISCORD_WEBHOOK |
Discord webhook for Gitea notifications. |
GITEA_CONTENT_LIMIT |
How many recent commits to check per repo. |
Set up .env first, then either:
go run main.go # locally
docker compose up -d # in a container
The container mounts the repo folder as /app and runs go run main.go on start. cache.json lives in the same folder and survives restarts. The container runs on the interstack network with a 384m memory limit.
In production: /srv/stacks/updater on the server, logs via docker logs updater-app. A push to master that changes the Go sources, the Dockerfile or compose.yaml deploys it from Woodpecker (ci-deploy updater, see Deployment) after go vet and a build; .env and cache.json stay on the server.
| File | Contents |
|---|---|
main.go |
Entry point, calls lib.Runner(). |
lib/runner.go |
Reads the config, builds the source and webhook list, runs the main loop. |
lib/fetcher.grav.go, lib/fetcher.redmine.go, lib/fetcher.gitea.go |
The three live sources. |
lib/fetcher.go |
Shared HTTP request and cache check. |
lib/sender.discord.go, lib/message.go |
The Discord webhook and the message format. |
lib/cache.go |
Loads and saves cache.json. |
env-example |
Configuration template. |
https://git.teletypegames.org/services/updater