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
This commit is contained in:
@@ -1,14 +1,149 @@
|
||||
# Terminal Duty
|
||||
Terminal Duty(termdut) is a server that helps teams manage alerts, oncall schedules etc.
|
||||
- The app will integrate with alertmanager(prometheus alertmanager), as a receiver of alertmanager webhooks without the need for adapters.
|
||||
- Incoming alerts will be logged to a database(postgres or sqlite).
|
||||
- Alerts can be marked as "acknowledged", when someone is already working on it.
|
||||
- Comments can be added to alerts, such as solutions, tips or actions.
|
||||
- Statistics can be produced for the alerts in the database, such as most common alert, most busy times of the day/week etc.
|
||||
- The server exposes a rest-api, where users, schedules, alerts etc can be managed.
|
||||
- Alerts will be received from alertmanager with no authentication initially.
|
||||
- The exposed api however(besides receiving alert from alertmanager) needs authentication.
|
||||
- Authentication is managed on a user basis, each user has his or her own credentials, these are stored hashed in the database.
|
||||
- Each user can be assigned to time slots in the oncall-schedule.
|
||||
- The smallest unit of time managed in the schedule is a day. This may change later.
|
||||
- The server is written in golang
|
||||
# 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+
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash
|
||||
export KEY=<your-key>
|
||||
curl -H "Authorization: Bearer $KEY" http://localhost:8080/api/users
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
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`:
|
||||
|
||||
```yaml
|
||||
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
|
||||
|
||||
```bash
|
||||
go test ./... # run all tests
|
||||
go build ./... # compile all packages
|
||||
go run ./cmd/terdut # run locally
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user