Komodo now watches the t5de org (Gitea webhook → its t5de-push action, defined in t5de-infra): it builds and deploys a commit to staging once this CI passes on main, and to prod for vX.Y.Z tags. So the deploy job and .gitea/scripts/deploy.py go, and this repo no longer needs to know how it's deployed. Co-Authored-By: Claude Opus 5.5 <[email protected]>
t5de-catalog
T5DE's catalog server: it stores community products' files by sha256, serves them, and registers uploads with T5DE's hub (git.t5de.dev/t5de/t5de-hub). Creators upload from the hub's website straight to a catalog, so large files never pass through the hub; T5DE's proxy then downloads the files from the catalogs the hub lists. Anyone can run a catalog and ask the hub to approve it.
The contract (tokens, this API, the hub's API for catalogs) is t5de-hub's docs/products.md; the design and the
reasons are in its docs/design.md ("Products and catalogs", "Catalogs (t5de-catalog)").
Unofficial: not made by or affiliated with IMVU.
How it works
- Uploads (
POST /api/v1/uploads): the creator's browser sends the product as a zip (Create Mode's.chkn, files at the root) with an upload token from the hub. The catalog checks the token (signed by the hub, for this catalog), checks the files by t5de-api's pack rules (names, limits counted while reading,index.xmlwithProductRatingGA or AP), stores each file under its sha256, and registers the revision with the hub (POST /api/v1/catalog/revisions,Authorization: Catalog <id>:<key>). Only once the hub accepts it is the revision recorded as hosted; files of a refused upload stay on disk but aren't served. - Files (
GET /api/v1/files/<sha256>): immutable, so cached for a year (ETagis the hash; ranges work). A file used only by adult revisions needs an access token from the hub withadult: true, and isn't cached by shared caches. - Tokens are EdDSA (Ed25519) JWTs; the hub's keys come from
HUB_URL/.well-known/t5de-keys.json, cached and fetched again when a token names an unknown key (at most every 30 s). - Storage:
CATALOG_DATA/catalog.db(SQLite: the revisions hosted and which files belong to them) andCATALOG_DATA/files/<first 2 hex>/<sha256>, written whole (temp file, then rename).CATALOG_DATA/tmpholds uploads being checked and is emptied at start.
API
GET /health |
{ok: true} |
POST /api/v1/uploads |
Authorization: Bearer <t5de-upload token>; multipart: file (the zip), notes (required for a new revision of an existing product). 201 {product, revision}. Errors are {error, message}: 400 bad files or form (bad_files, notes_required, bad_request), 401 bad token (bad_token), 413 over 300 MiB (too_large), 502 the hub refused (its error code, e.g. unchanged) or couldn't be reached (hub_unavailable). CORS for WEB_ORIGIN. |
GET /api/v1/files/<sha256> |
the file; 401/403 for adult-only files without an adult access token; 404 if not hosted |
GET /api/v1/revisions/<product>/<revision> |
{product, revision, rating, files} for revisions hosted here |
Running
Register the catalog on the hub's website (POST /api/v1/catalogs) to get its ID and key, and have a hub
moderator approve it. Then, for a hub running locally:
HUB_URL=http://localhost:4101 \
CATALOG_ID=<id> CATALOG_KEY=<key> \
CATALOG_PUBLIC_URL=http://localhost:4102 \
WEB_ORIGIN=http://localhost:4100 \
CATALOG_DATA=./data \
go run ./cmd/t5de-catalog
| Variable | |
|---|---|
CATALOG_ADDR |
listen address (default 127.0.0.1:4102) |
CATALOG_DATA |
data folder (default ./data) |
CATALOG_PUBLIC_URL |
the catalog's public URL, as registered on the hub (logged) |
HUB_URL |
the hub's API; also the tokens' issuer (required) |
CATALOG_ID, CATALOG_KEY |
from registering on the hub (required) |
WEB_ORIGIN |
the hub website's origin, allowed to upload from browsers |
It logs to standard error and stops cleanly on Ctrl+C or SIGTERM, finishing requests in progress. Put it behind
a TLS-terminating reverse proxy (or a CDN for /api/v1/files/) in production.
With Docker:
docker build -t t5de-catalog .
docker run -p 4102:4102 -v catalog-data:/data -e HUB_URL=... -e CATALOG_ID=... -e CATALOG_KEY=... \
-e CATALOG_PUBLIC_URL=... -e WEB_ORIGIN=... t5de-catalog
Building
go test ./...
CGO_ENABLED=0 go build -trimpath -o dist/t5de-catalog ./cmd/t5de-catalog
The SQLite driver is pure Go (modernc.org/sqlite), so builds are static. t5de-api comes from git.t5de.dev
directly: set GOPRIVATE=git.t5de.dev.