Fast HTTP/JSON API for girar build tasks
Find a file
2026-04-24 11:10:54 +00:00
.gear 1.5.0 release 2026-04-15 11:07:03 +03:00
bin feat: log client IP and User-Agent in access log 2026-04-24 11:10:54 +00:00
docs/superpowers docs: add SSE implementation plan 2026-03-17 06:35:28 +00:00
lib feat: add approved_by/disapproved_by to list endpoint subtasks 2026-04-14 11:34:36 +00:00
test test: add OpenAPI spec validity test 2026-04-01 18:04:55 +00:00
.gitignore Add CLAUDE.md and .gitignore 2026-03-09 17:02:15 +00:00
AUTHORS Add license headers to all source files, add AUTHORS 2026-03-09 17:31:39 +00:00
CHANGELOG.md feat: log client IP and User-Agent in access log 2026-04-24 11:10:54 +00:00
CLAUDE.md docs: add release checklist to CLAUDE.md 2026-04-01 12:28:10 +00:00
dune-project fix: update opam version to 1.4.0, make it single source of truth 2026-04-01 12:22:56 +00:00
LICENSE Add GPL-2.0 license text 2026-03-09 17:32:12 +00:00
README.md docs: update changelog, spec and READMEs for 1.4.1 2026-04-02 06:00:16 +00:00
README.ru.md docs: update changelog, spec and READMEs for 1.4.1 2026-04-02 06:00:16 +00:00
taskoteka.opam 1.5.0 release 2026-04-15 11:07:03 +03:00
taskoteka.service Drop AF_UNIX from systemd unit RestrictAddressFamilies 2026-03-10 09:26:22 +00:00
taskoteka.sysconfig Fix systemd unit: remove invalid bash syntax and redundant options 2026-03-10 06:23:58 +00:00

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 --json output 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: adds EPERM, FAILED, NEW, SWEPT, TESTED
  • With q: same as with user (all states except DONE)

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 matches field 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