API 1.7.0
Self-hosting and operations
The registry API/worker are separate from the static website. Deployment uses PostgreSQL and private S3-compatible object storage. Render/R2 are defaults, not protocol requirements.
Local Docker setup
In a Tabs source checkout, copy apps/exchange/.env.example to a private .env and replace placeholders. Register a GitHub OAuth application with the registry /auth/github/callback URL. Use a unique URL-safe PostgreSQL password, a private object bucket, and operator-owned terms. Never commit .env or signing keys.
cd tabs-main/apps/exchange
cp .env.example .env
# Edit .env before proceeding; keep submissions disabled.
docker compose config
docker compose up --build -d
curl --fail http://localhost:8787/healthz
Docker must be running; a configuration check is not a live deployment test. The same-origin registry /extensions page supports authenticated private browsing; /publisher is its account and submission portal. The static marketing site is a public presentation client, not a cross-site session portal. Configure HTTPS at the reverse proxy before handling real accounts or tokens.
Compose uses PostgreSQL and pinned SeaweedFS 4.48 with persistent private objects. Mandatory S3 credentials prevent anonymous access. Only the S3 port is exposed on loopback for operator backup; management/filer ports are not published. An authenticated bucket-readiness check gates startup. This single-node setup is not highly available; replication and production restore drills remain operator responsibilities.
Private instances
Set EXCHANGE_VISIBILITY=private and EXCHANGE_ALLOWED_GITHUB_IDS to an explicit comma-separated numeric GitHub ID allowlist. Keep it separate from reviewer IDs. Allowlist removal invalidates registry access. Read tokens protect catalog/assets/download/signed-metadata transport; publish tokens are namespace-bound.
Signing and launch gates
Keep EXCHANGE_PUBLISHING_ENABLED=false until terms, OAuth, storage, operator ownership, scanning, signing, and client trust-root distribution are ready. Signing keys remain outside the API/worker. Export the exact approved targets, sign root/targets/snapshot/timestamp with an established offline TUF tool, and submit the staged metadata to the verification gate:
bun run tuf:export-targets
# Review the exported map and sign offline. Never invent signatures.
bun run tuf:publish /absolute/offline-signed-metadata
bun run verify:objects
Set EXCHANGE_TUF_BOOTSTRAP_ROOT_SHA256 from an independently verified initial root. Distribute that root and digest to clients out of band. Subsequent root rotations must satisfy the previous and new thresholds. No official production keys are provisioned by these tools.
Recovery and monitoring
Freeze writes by stopping API and worker. Export PostgreSQL with pg_dump -Fc and mirror the private bucket into a separate restricted backup directory. Record the trusted root identity, metadata versions, and object-verification report with the snapshot. Back up offline signing keys separately, with controlled access. Restore to a fresh database and private bucket, run pg_restore and object verification, and prove an independently pinned client can install before accepting writes.
The portable JSON backup deliberately excludes sessions, OAuth state, and access tokens. A full PostgreSQL restore contains old token hashes: revoke restored sessions and tokens before reconnecting users, so disaster recovery cannot resurrect revoked credentials. Never restore stale signed metadata to bypass client rollback protection; issue newer signed roles when recovering.
Monitor scan backlog, worker heartbeat, metadata expiry, API errors, and storage integrity. Drill revocation and dual-threshold root rotation against a throwaway client before production. For upgrades, freeze writes, capture a tested recovery set, deploy the matching API/worker version, run migrations, verify health and signed client behavior, then resume. The repository docs/exchange.md contains the full maintenance and recovery command sequences.
Abuse limits
Uploads are at most 25 MiB, with two concurrent uploads per API process and a two-minute deadline. Catalog pages contain at most 100 records. Namespace and invitation actions require current terms and membership. Operators must configure reverse-proxy request/account limits and alerting before enabling a public service; the registry does not claim distributed rate limiting. Do not increase limits to work around abusive submissions.
Private responses must not enter shared caches. Package hosting costs belong to the operator even though marketplace access is free.