Files
terdut-server/README.md
T
Niklas Ye 17f09558cb Stage 7: Dockerfile, integration tests, updated README
Dockerfile:
- Multi-stage build (golang:1.25-alpine → scratch)
- CGO_ENABLED=0, static binary, stripped with -ldflags="-w -s" (~11 MB)

Tests (13 cases, internal/api/api_test.go):
- Auth middleware: missing token, invalid token, valid token
- Bootstrap idempotency (second call → 403)
- Alert upsert: same fingerprint updates row; different fingerprints add rows
- Acknowledge: set and clear, verified via GET
- Comment ownership: only author can delete own comment (404 for others)
- Schedule conflict: duplicate date → 409; multi-date rollback on partial conflict
- Stats: totals, by-hour returns 24 slots, by-day returns 7 slots

README: quick start, Docker, env vars, Alertmanager config, full API reference
2026-05-20 22:35:26 +02:00

4.2 KiB

Terminal Duty (terdut-server)

On-call alert management server for teams using Prometheus Alertmanager.

  • Receives Alertmanager webhooks directly — no adapter needed
  • Stores and queries alerts (acknowledge, comment)
  • On-call schedule management (user-to-day assignments)
  • Alert statistics (by status, by hour, by day)
  • REST API with per-user API key authentication
  • Single binary, SQLite storage — trivial to self-host

Quick start

Prerequisites: Go 1.21+

git clone https://github.com/yeniklas/terdut-server
cd terdut-server
go run ./cmd/terdut

The server starts on :8080 with a terdut.db file in the working directory.

Create the first user

curl -X POST http://localhost:8080/api/bootstrap \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "email": "admin@example.com"}'

Save the api_key.key value from the response — it is shown once only.

Use it as a bearer token for all subsequent requests:

export KEY=<your-key>
curl -H "Authorization: Bearer $KEY" http://localhost:8080/api/users

Docker

docker build -t terdut-server .
docker run -p 8080:8080 -v $(pwd)/data:/data \
  -e TERDUT_DB_PATH=/data/terdut.db \
  terdut-server

Configuration

Variable Default Description
TERDUT_ADDR :8080 TCP address to listen on
TERDUT_DB_PATH terdut.db Path to the SQLite database file

Alertmanager configuration

Add terdut-server as a webhook receiver in your alertmanager.yml:

receivers:
  - name: terdut
    webhook_configs:
      - url: http://terdut-server:8080/api/alertmanager/webhook
        send_resolved: true

route:
  receiver: terdut

The webhook endpoint requires no authentication.


API reference

Authentication

All endpoints except /api/bootstrap and /api/alertmanager/webhook require:

Authorization: Bearer <api-key>

Users

Method Path Description
POST /api/bootstrap Create first user + API key (only works on empty DB)
GET /api/users List users
POST /api/users Create user {"username","email"}
DELETE /api/users/{id} Delete user (cascades to keys)
POST /api/users/{id}/api-keys Issue API key {"name"} — key shown once
DELETE /api/users/{id}/api-keys/{keyID} Revoke API key

Alert ingestion

Method Path Description
POST /api/alertmanager/webhook Alertmanager v4 webhook receiver (no auth)

Alerts

Method Path Description
GET /api/alerts List alerts. Filters: ?status=firing|resolved, ?name=, ?from=YYYY-MM-DD, ?to=YYYY-MM-DD, ?limit= (default 50, max 500)
GET /api/alerts/{id} Get single alert
POST /api/alerts/{id}/acknowledge Acknowledge alert (stamps authed user + time)
DELETE /api/alerts/{id}/acknowledge Clear acknowledgement
GET /api/alerts/{id}/comments List comments (chronological)
POST /api/alerts/{id}/comments Add comment {"content"}
DELETE /api/alerts/{id}/comments/{commentID} Delete own comment

On-call schedule

Method Path Description
POST /api/schedule Assign user to dates {"user_id", "dates":["YYYY-MM-DD",...]} — all-or-nothing
GET /api/schedule List entries. Filters: ?from=YYYY-MM-DD, ?to=YYYY-MM-DD
GET /api/schedule/current Today's on-call user (UTC), 404 if none
DELETE /api/schedule/{id} Remove schedule entry

Statistics

All stat endpoints accept optional ?from=YYYY-MM-DD and ?to=YYYY-MM-DD to filter by received_at.

Method Path Description
GET /api/stats/alerts {total, firing, resolved} counts
GET /api/stats/alerts/top Most frequent alert names. ?limit= (default 10, max 100)
GET /api/stats/alerts/by-hour Count per hour-of-day (UTC), all 24 slots returned
GET /api/stats/alerts/by-day Count per day-of-week, all 7 slots with names returned

Development

go test ./...        # run all tests
go build ./...       # compile all packages
go run ./cmd/terdut  # run locally