Users now have an avatar. The account page takes an image, lets the user
drag and zoom it under a circular mask, and uploads exactly what the circle
shows as a 256x256 PNG. The picture replaces the initial-letter circle in
the sidebar, on the account page, on the on-call hero and week strip, on
the schedule's day chips and in the incident assign menu; someone without
one keeps the initial.
The picture lives in Postgres (user_avatars, bytea) rather than on a
volume: the chart runs two replicas on a read-only root filesystem, and
the k8up pg_dump backup then covers it without any chart or operator
change. The server does not trust the client's crop. It decodes the PNG or
JPEG, checks the declared size before decoding, centre-crops to a square,
scales to 256 and re-encodes, which drops EXIF and anything that is not
pixels. Standard library only; WebP is cropped by the browser and arrives
as PNG, a raw WebP sent to the API is refused.
GET/PUT/DELETE /api/users/{id}/avatar: anyone signed in may read, only the
owner or an administrator may change. /api/me and /api/users gain
avatar_version, absent when there is no picture, which is also the ?v= that
busts the cache after a change. Incident timeline entries and the queue are
text-only and are unchanged.
Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN
terdut-server
Incident management for teams that already run Prometheus Alertmanager. Point Alertmanager at it, and alerts become incidents that get assigned to whoever is on call, paged, escalated when nobody answers, and tracked to resolution. One binary, one Postgres.
Highlights
- Alertmanager-native. Receives Alertmanager webhooks directly, with no adapter, and groups
alerts into incidents by Alertmanager's own
groupKey. An incident opens on a new occurrence, not on every re-send. Details - A real incident workflow. Acknowledge, assign, snooze, add notes, resolve and archive, with a full timeline of who did what and when. Several clusters can feed one team without cross-talk. Details
- On-call rota. Each team keeps its own rota, and new incidents go to whoever is on call. Web UI
- Pages that escalate. Notifications through pluggable channels (ntfy today), with an Acknowledge button right in the notification where the channel can show one, and escalation ladders that move on to the next level when nobody answers. Notifications · Escalation
- Notices when the alerts stop. Dead man's switches turn the absence of a heartbeat such as
Watchdoginto an incident. Details - Teams. Every team owns its queue, rota, escalation, alert sources and switches; people see only the teams they belong to.
- Stats. Incident counts, mean time to acknowledge and resolve, and alert frequency by name, hour and day. Web UI
- Single sign-on. OpenID Connect with group-to-team and administrator mapping, and a device flow so a terminal client can sign in through the browser. Details
- Built for the phone first. The web UI is served by the same binary, follows the system's dark mode, and can be added to the home screen.
- An API for everything. A REST API with API keys and service accounts for automation. Reference
- Easy to run. A scratch container image, a Helm chart, and terdut-operator if you want teams and escalation as Kubernetes objects. Deployment
Screenshots
On a phone the queue and the incident page are the same interface, with a sticky action bar:
Quick start
You need Go 1.25+ and a Postgres 14+ the server can reach.
git clone https://git.ryuvia.com/niklas/terdut-server
cd terdut-server
export TERDUT_DB_DSN='postgres://terdut:secret@localhost:5432/terdut?sslmode=disable'
go run ./cmd/terdut
The server creates its schema on startup and listens on :8080. Create the first user, an
administrator, while no user exists yet:
curl -X POST http://localhost:8080/api/bootstrap \
-H "Content-Type: application/json" \
-d '{"username": "admin", "email": "admin@example.com", "password": "<at least 10 characters>"}'
Sign in at http://localhost:8080 with that username and password. The response also carries an API key, shown once, for scripts:
curl -H "Authorization: Bearer $KEY" http://localhost:8080/api/users
Then create a team, add an alert source to get a webhook URL, and point Alertmanager's webhook receiver at it: see Alertmanager configuration.
Documentation
The documentation index lists everything. The main pages:
| Deployment | Docker, Helm chart, database, backups, the operator |
| Configuration | Environment variables and settings |
| Metrics | Graphs from the alert's Prometheus on the incident page |
| Alertmanager configuration | Routes, keys, webhooks |
| Alerts and incidents | Correlation, lifecycle, on-call assignment |
| Single sign-on | OIDC and the terminal device flow |
| API reference | Every endpoint |
| Development and releasing | Tests, CI gate, release pipeline |
Related
- terdut-tui: a terminal client for the same server.
- terdut-operator: a Kubernetes operator that runs the server and manages teams, escalation, switches and alert sources as objects.
License
See LICENSE.








