Server

One Linux box. Caddy is the only public listener: it terminates TLS for every queue domain and reverse-proxies to a small, dependency-free Node API bound to 127.0.0.1:8790. The API stores definitions, keeps every task as a file, records each task's lifecycle, and samples the machine's health. Every path below is fixed in the code; the only per-install input is the master token.

/srv/
  taskhoster/                  queue + user definitions, mirrored up from your machine
  taskhoster-items/            every task, one file per state directory
  taskhoster-logs/             Caddy's access log, and the durable task-event history
  taskhoster-server-stats/     server-health day-files (CPU, memory, disc, network)
/etc/
  taskhoster/                  the master token and the self-signed TLS cert
  caddy/Caddyfile              the Caddy config the installer writes
  systemd/system/taskhoster-api.service  the API service unit
/opt/
  taskhoster/                  the installed server code, one dir per version + a "current" symlink
Everything a Taskhoster host keeps on disc.

Contents

NameTypeDescription
/srv/taskhoster/directoryQueue and user definitions, one file each, as the local package pushes them.
/srv/taskhoster-items/directoryEvery task as a file, under a pending / processing / done state directory. Detailed on the Store page.
/srv/taskhoster-logs/directoryCaddy's access log, the durable task-event log, and any archived payloads.
/srv/taskhoster-server-stats/directoryServer-health samples, one dated file per metric.
/etc/taskhoster/directoryThe master token and the self-signed bare-IP certificate.
/opt/taskhoster/directoryThe installed server code, versioned, with a current symlink.

Caddy out front

Caddy is the whole public surface. It terminates TLS for every queue domain with on-demand certificates, gated by a local call to /internal/allow so it only issues for a domain that actually has queue definitions. Requests are routed to the API by Host header, and the WebSocket upgrades pass through transparently. Bare-IP requests (the management and tools surface) are reverse-proxied too, with /internal/* blocked from the public side. A no-SNI or bare-IP handshake is answered with a self-signed certificate for th.invalid, so a probe never gets a name-bearing cert.

The API

A single dependency-free Node process, taskhoster-api, bound to 127.0.0.1:8790. It carries three surfaces: the public producer and worker endpoints (routed by Host, gated by each queue's access rules), and the token-gated management + tools API under /api/v2 (reached by bare IP). A periodic sweep runs inside the same process, so there is no separate worker service. With a valid master token the HTTP status line carries the outcome; without one, every management route returns an identical 404 wall.

StatusMeaning
200ok (data, or a result body)
400bad request (an invalid path or definition)
426a request on a different API version's path — upgrade the local package
500a write failed on the server
404the wall: no/!bad token, or an unknown route (indistinguishable)

The Sync and Tools pages document each endpoint.

/srv/taskhoster/

The definitions the local package mirrors up, one folder per queue host, each with the two fixed definition folders. Nothing else is written here; the items live elsewhere.

/srv/taskhoster/
  task.example.com/            a queue host (a domain)
    taskhoster-queues/
      resize.json              the "resize" queue definition
      thumbnail.json
    taskhoster-users/
      alice.json               a user: { "taskhoster-name", "taskhoster-token" }
Verbatim copies of the files you author, as validated on upload.

/srv/taskhoster-items/

Every task, one JSON file, under a directory that is its status. The claim, the sweep and the full item object are on the Store page.

/srv/taskhoster-items/
  task.example.com/
    resize/
      pending/    1f3c…9a.json   waiting to be claimed
      processing/ 7b21…04.json   leased to a worker
      done/       c8d0…e2.json   finished or failed (terminal)
An item id is 16 hex characters; its directory is its state.

/srv/taskhoster-logs/

Two independent logs live here, both read-only through the token-gated tools API: Caddy's access log (the Web Logs source), and the durable task-event log the API appends to on every lifecycle transition (the Task Logs source), out of band of a queue's short retention. When a queue sets taskhoster-archivePayloads, the payload bodies are kept alongside, separate from the queue's own item storage.

/srv/taskhoster-logs/
  access.log                         Caddy's JSON access log (rolled by size)
  tasks/
    tasks-2026-07-26.log             one JSON line per task event; a file per UTC day
  tasks-payloads/
    task.example.com/resize/1f3c…9a/
      item.json  result.json       archived bodies (only when archivePayloads is on)
The access log and the durable task history; dated files are the rotation.

/srv/taskhoster-server-stats/

The API samples the machine's health every 15 seconds and appends one JSON line per metric to a dated day-file. It is the Server Logs source, and the same recorder every hoster uses. Older files are pruned; the local browser mirrors them and keeps its own history.

/srv/taskhoster-server-stats/
  cpu-2026-07-26.log       per-core busy %
  memory-2026-07-26.log    total / used / available bytes
  disk-2026-07-26.log      diskio-2026-07-26.log   netio-2026-07-26.log
One dated file per metric type.

/etc/taskhoster/

The one secret and the fallback cert. The token is the whole authorisation story for the management and tools API; the cert answers bare-IP and no-SNI TLS handshakes.

/etc/taskhoster/
  token      the master token (64 hex chars, mode 600)
  tls.crt    tls.key   the self-signed th.invalid certificate
Read the token back with sudo cat /etc/taskhoster/token.

/opt/taskhoster/

The installed code, one directory per version with a current symlink the service runs. An upgrade lays a new version beside the old and flips the link, so a rollback is a symlink change. The bundle is just the server halves of src/.

/opt/taskhoster/
  0.1.0/              the server code for this version
    server/            api.js, store.js, ws.js, Caddyfile
    shared/            schema.js, paths.js, version.js, the recorders
    common/            server/logs-stats.js (the shared logs + stats routes)
  current → 0.1.0
Versioned code; current is what taskhoster-api runs.

Security

There is one secret: the master token in /etc/taskhoster/token, read once at startup. The management and tools API compares it in constant time, and anything without it — a bad token, or an unknown route — gets an identical 404 Not Found, so an unauthenticated probe cannot tell the API is there at all. The service runs as an unprivileged taskhoster user with NoNewPrivileges. Producer and worker access is separate: each queue's access rules decide who may post, read and work, by user token or public.