- OCaml 100%
| .gear | ||
| bin | ||
| docs/superpowers | ||
| lib | ||
| test | ||
| .gitignore | ||
| AUTHORS | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| dune-project | ||
| LICENSE | ||
| README.md | ||
| README.ru.md | ||
| taskoteka.opam | ||
| taskoteka.service | ||
| taskoteka.sysconfig | ||
taskoteka
Fast HTTP/JSON API for girar build tasks.
Reads task data directly from the /tasks/ filesystem (NFS) into an in-memory cache and serves it over HTTP with sub-millisecond response times. Designed as a drop-in replacement for girar task ls and girar task show --json.
Features
- In-memory cache with mtime-based invalidation (NFS-compatible, no inotify)
- Parallel filesystem reads (16 threads by default)
- Full compatibility with
girar task show --jsonoutput format - Full-text search across tasks (owner, repo, state, message, subtask fields)
- Filtering by state, repository, owner, and approval status
- Per-subtask build status per architecture (built/cached/swept/excluded/failed)
- Approval/disapproval comments with parsed dates
- OpenAPI 3.0 specification at
/openapi.json - Server-Sent Events (
GET /events) for real-time task change notifications - Refresh interval and debug logging configurable at runtime
Building
Requires OCaml >= 5.0 and dune >= 3.0.
opam install tiny_httpd yojson alcotest
dune build
dune runtest
Usage
taskoteka [OPTIONS]
--port PORT HTTP port (default: 1337)
--addr ADDR Listen address (default: 0.0.0.0)
--tasks-dir PATH Path to tasks directory (default: /tasks)
--archive-dir PATH Path to archive tasks directory (e.g. /archive/tasks)
--refresh-interval SEC Seconds between cache refreshes (default: 5.0)
--threads N Parallel I/O threads for loading/refreshing (default: 16)
--acl-dir PATH Path to ACL directory (e.g. /ALT/acl) for needs_approval filter
--debug Log refresh details to stderr
--max-sse-connections N Max concurrent SSE connections (default: 256)
--max-sse-per-ip N Max SSE connections per IP (default: 5)
--sse-heartbeat-interval SEC SSE heartbeat interval in seconds (default: 5)
--sse-max-lifetime SEC SSE forced reconnect after N seconds (default: 1800)
--sse-buffer-size N SSE per-client event buffer size (default: 128)
--sse-ip-header HEADER Header for client IP behind proxy (default: X-Real-IP)
--sse-trusted-proxy IPS Comma-separated trusted proxy IPs (default: none)
API
Base URL: https://git.altlinux.org/tasks/api (or http://<host>:<port> for local instances).
All responses have Content-Type: application/json and include headers:
x-api-version— API version (e.g.1.4.1)x-cache-age— seconds since last cache refresh (e.g.3)
GET /tasks
List tasks matching filters. Returns a JSON array of task summaries.
Parameters:
| Parameter | Type | Description |
|---|---|---|
state |
string | Filter by state, comma-separated. ALL = no filter. Values: NEW, AWAITING, PENDING, BUILDING, COMMITTING, TESTED, FAILING, FAILED, DONE, SWEPT, EPERM, POSTPONED |
repo |
string | Filter by repository, comma-separated. ALL or omit = no filter |
user |
string | Filter by task owner. ALL = no filter |
brief |
string | true, 1, or yes — return only task IDs (integers) |
needs_approval |
string | maint or tester — show EPERM tasks needing approval from this group. Requires --acl-dir on server |
q |
string | Full-text search (see below) |
Default states (when state is omitted):
- Without
user:AWAITING,BUILDING,COMMITTING,FAILING,PENDING,POSTPONED - With
user: addsEPERM,FAILED,NEW,SWEPT,TESTED - With
q: same as withuser(all states exceptDONE)
Search (q parameter):
- Space-separated terms, all must match (AND logic)
- Case-insensitive substring matching
- Searches: id, owner, repo, state, message, subtask dir/pkgname/tag_name/userid/srpm
- Limits: max 200 chars total, max 10 terms, min 2 chars per term
- Results capped at 500
- Cannot be combined with
needs_approval - Response includes a
matchesfield showing where each term matched
Examples:
# Active tasks (default states)
curl -s https://git.altlinux.org/tasks/api/tasks | jq '.[0]'
{
"id": 411555,
"state": "BUILDING",
"repo": "p11",
"owner": "rider",
"created": "2026-03-16T12:22:27",
"build_time": "2026-03-16T17:15:28",
"age": 0,
"test_only": false,
"fail_early": true,
"try": 2,
"iter": 1,
"message": "update PDAL",
"subtasks": {
"100": { "dir": "/gears/p/pdal.git", "tag_name": "2.9.3-alt1", "type": "repo", "userid": "rider", "pkgname": "pdal" },
"200": { "dir": "/gears/g/grass.git", "tag_name": "8.4.2-alt1", "type": "repo", "userid": "rider", "pkgname": "grass" },
"300": { "dir": "/gears/q/qgis.git", "tag_name": "3.44.7-alt2", "type": "repo", "userid": "rider", "pkgname": "qgis" }
}
}
# Tasks by specific user
curl -s 'https://git.altlinux.org/tasks/api/tasks?user=rider'
# EPERM tasks in sisyphus
curl -s 'https://git.altlinux.org/tasks/api/tasks?state=EPERM&repo=sisyphus'
# Multiple states
curl -s 'https://git.altlinux.org/tasks/api/tasks?state=BUILDING,FAILING'
# All tasks in all states
curl -s 'https://git.altlinux.org/tasks/api/tasks?state=ALL'
# Only task IDs
curl -s 'https://git.altlinux.org/tasks/api/tasks?brief=true'
[411555, 411543, 411469]
# Search for EPERM tasks in p11 with "lib" in package name
curl -s 'https://git.altlinux.org/tasks/api/tasks?q=p11+EPERM+lib'
[
{
"id": 411543,
"state": "EPERM",
"repo": "p11",
"owner": "zerg",
"created": "2026-03-16T11:09:35",
"build_time": "2026-03-16T12:38:04",
"age": 0,
"test_only": false,
"fail_early": true,
"try": 1,
"iter": 1,
"message": "update",
"subtasks": {
"300": { "dir": "/people/zerg/packages/nvidia_glx_libs_580.142.git", "tag_name": "580.142.-alt1", "type": "repo", "userid": "zerg", "pkgname": "nvidia_glx_libs_580.142" }
},
"matches": ["300/dir:lib", "300/pkgname:lib", "repo:p11", "state:eperm"]
},
{
"id": 411469,
"state": "EPERM",
"repo": "p11",
"owner": "lav",
"created": "2026-03-15T15:22:25",
"build_time": "2026-03-15T15:31:29",
"age": 0,
"test_only": false,
"fail_early": true,
"try": 2,
"iter": 1,
"message": "Debian libcurl compatibility",
"subtasks": {
"100": { "dir": "/gears/l/libcurl4-gnutls.git", "tag_name": "8.0-alt1", "type": "repo", "userid": "lav", "pkgname": "libcurl4-gnutls" }
},
"matches": ["100/dir:lib", "100/pkgname:lib", "message:lib", "repo:p11", "state:eperm"]
}
]
# EPERM tasks needing maintainer approval
curl -s 'https://git.altlinux.org/tasks/api/tasks?needs_approval=maint'
# EPERM tasks needing tester approval, in sisyphus
curl -s 'https://git.altlinux.org/tasks/api/tasks?needs_approval=tester&repo=sisyphus'
GET /tasks/{id}
Full task details. Compatible with girar task show --json.
Includes per-subtask build status per architecture and approval/disapproval comments with parsed dates. If the task is not found in the active cache and --archive-dir is configured, it will be read from the archive on demand (not cached).
Parameters:
| Parameter | Type | Description |
|---|---|---|
id |
integer (path) | Task ID |
brief |
string (query) | true, 1, or yes — return only the task ID |
Example:
curl -s https://git.altlinux.org/tasks/api/tasks/389466 | jq .
{
"updated": 1773681066,
"taskid": 389466,
"shared": false,
"fail_early": false,
"test_only": false,
"repo": "sisyphus",
"state": "DONE",
"try": 85,
"iter": 1,
"age": 22,
"build_time": "2025-10-10T16:12:42",
"message": "Python3.13",
"owner": "grenka",
"subtasks": {
"1": {
"dir": "/people/grenka/packages/python3.git",
"tag_name": "3.13.0-alt1",
"tag_id": "55ba186a3b712f453c9e3c62ec90b6e3a07e9b51",
"tag_author": "Grigory Ustinov <grenka@altlinux.org>",
"type": "repo",
"pkgname": "python3",
"userid": "grenka",
"version": "3.13.0",
"release": "alt1",
"build": {
"aarch64": "swept",
"i586": "swept",
"x86_64": "swept"
}
}
}
}
# Just get the task ID (useful for existence check)
curl -s 'https://git.altlinux.org/tasks/api/tasks/389466?brief=true'
389466
Error (task not found):
curl -s https://git.altlinux.org/tasks/api/tasks/999999
{"error": "task 999999 not found"}
GET /events
Server-Sent Events stream of real-time task changes. The connection is long-lived; the server pushes events as tasks are created, updated, or removed.
Event types:
| Event | Data fields | Description |
|---|---|---|
task_new |
id, state, owner |
A new task appeared in the cache |
task_updated |
id, state, prev_state, owner |
Task state or data changed |
task_removed |
id |
Task was removed from cache |
heartbeat |
Unix timestamp | Sent every 5s (configurable) to keep the connection alive |
reconnect |
reason |
Server requests client to reconnect (slow consumer or max lifetime reached) |
Example:
curl -N http://localhost:8099/events
event: task_updated
data: {"id":12345,"state":"BUILDING","prev_state":"NEW","owner":"rider"}
event: heartbeat
data: 1773073640
event: task_new
data: {"id":12346,"state":"NEW","owner":"lav"}
CLI parameters for SSE:
| Parameter | Default | Description |
|---|---|---|
--max-sse-connections |
256 | Max concurrent SSE connections |
--max-sse-per-ip |
5 | Max SSE connections from one IP |
--sse-heartbeat-interval |
5 | Heartbeat interval in seconds |
--sse-max-lifetime |
1800 | Forced reconnect after N seconds |
--sse-buffer-size |
128 | Per-client event buffer size |
--sse-ip-header |
X-Real-IP | Header to read client IP from (behind proxy) |
--sse-trusted-proxy |
(none) | Comma-separated trusted proxy IPs; header is only used from these |
Proxy configuration note: The server sends Cache-Control: no-cache and X-Accel-Buffering: no headers to prevent proxy buffering. For nginx, ensure proxy_buffering off or rely on the X-Accel-Buffering: no header. Security: The --sse-ip-header is only trusted when the request comes from an IP listed in --sse-trusted-proxy. Without trusted proxies configured, the socket address is always used for per-IP rate limiting.
| HTTP code | When |
|---|---|
| 503 | SSE capacity exceeded (too many concurrent SSE connections) |
| 429 | Too many SSE connections from this IP |
GET /owners
Returns a sorted list of unique owner usernames from all cached tasks.
Example:
curl -s https://git.altlinux.org/tasks/api/owners | jq .
["aris", "cas", "ldv", "rider", "vt"]
GET /health
Server health check with task counts.
Example:
curl -s https://git.altlinux.org/tasks/api/health
{"status": "ok", "total_tasks": 3662, "active_tasks": 3155}
GET /openapi.json
OpenAPI 3.0 specification. Can be imported into Swagger UI or other API tools.
curl -s https://git.altlinux.org/tasks/api/openapi.json | jq .info.version
"1.4.1"
Error responses
All errors return JSON with an error field:
{"error": "description of the problem"}
| HTTP code | When |
|---|---|
| 400 | Invalid query parameters (bad search query, invalid needs_approval value) |
| 404 | Task not found by ID |
| 429 | Too many SSE connections from this IP |
| 503 | needs_approval used but --acl-dir not configured on server; or SSE capacity exceeded |
systemd
A hardened systemd unit file is provided:
cp taskoteka.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now taskoteka
License
GPL-2.0-or-later