mirror of
https://github.com/nestriness/nestri.git
synced 2026-09-19 09:15:19 +03:00
The agent side sends a claim token on every write; this side rejected the field outright, so every state report and every ticket publish answered 400. Both bodies now take it. Underneath that, nothing compared a holder. A run was reachable by any caller on the right machine, and a box names exactly one machine — so two attempts polling the same job presented identical credentials and were told apart only by which one's select landed first. That is timing, not a rule, and no caller could be told which case it was in. The row now remembers which attempt holds it. Taking a claim requires there to be no holder; every write after it requires the caller to be the holder. The same state reported by a different attempt is a lost race and not a retry, and is refused whatever the state is - which is the only thing that separates the two 200s from the 409s. The ticket is held to the claim too, for a worse reason than a double start: the client re-reads the address rather than keeping the first, so a ticket written by a losing attempt produces a client that connects, successfully, to a machine running nothing. The holder is never cleared, including on a terminal state, so a settled claim cannot be replayed and a finished run still records which attempt ran it. It is not in what goes out - holding one permits writing to a run, and the owner reading their own session is not the holder.
apps/api
The public HTTP API for Nestri — a Hono app deployed as a Cloudflare Worker.
What it does
Exposes the JSON API consumed by frontends and other clients. Every route is a thin wrapper that
validates input, delegates to a domain function in @nestri/core,
and returns { data: ... }. All business logic lives in the core package.
Routes:
| Prefix | Purpose |
|---|---|
/ |
Health check |
/user |
Current user profile, fingerprints, linked accounts |
/steam |
Link / sync / unlink a Steam account |
/library |
Owned games with playtime |
/games |
Game catalog |
/pairing-code |
Device pairing codes |
/machine |
Host machines |
/access-token |
Short-lived access tokens |
/doc |
Generated OpenAPI spec |
Structure
app/
index.ts # Hono entrypoint: middleware, routes, error handler, /doc
middleware/auth.ts # Bearer JWT + admin shared-secret auth → Actor
routes/*.ts # Thin route namespaces (UserApi, SteamApi, ...)
utils/ # ErrorResponses, Result(), validator wrapping
test/ # Route tests (Vitest/Bun)
Key details
- Auth:
Authorization: Bearer <JWT>verified against@nestri/auth; or thex-nestri-admin-tokenheader (seeADMIN_SHARED_SECRET). - Errors: centralized
VisibleError→ typed JSON responses. - The API worker receives its bindings (
AUTH,HYPERDRIVE,STEAM_API_KEY,ADMIN_SHARED_SECRET) from Alchemy — seealchemy.run.tsat the repo root.
Running
Run via the root Alchemy setup (bun alchemy.run.ts --dev). Needs a Postgres database and an auth worker; see the root README for full dev setup.