# Dear SRE — re: hosting *Douanier* on `api.nech.pl` 🛂

A letter, because you own the seam between this little service and the public
internet, and I'd rather ask than assume. Here's what Douanier is, what state
it's in, and the handful of decisions that are yours, not mine.

## What it is (context)

**Douanier** (after Le Douanier Rousseau) is the ParVagues **Audio-Intelligence
API** — it productizes the fleet's engines (stem separation, loop/chop finding,
emotion analysis, sample analysis, the feature stack) behind one self-hosted,
token-authed, async HTTP API. First customer is **hexa** (Shipow's
hydra-live-hexa Studio), who'll call it from his Vercel functions; we want the
door open to selling more seats later.

Design decisions already locked (so you know the shape):

- **Self-hosted, all of it.** Compute + storage live on the box (CPU now, cloud
  GPU later via the `parvagues@nech.pl` creds). No third-party blob/CDN — the
  cost sketch said home egress beats cloud egress on big audio by ~20×.
- **Artifacts are a CACHE, not storage** — transient on the **freebox** mount,
  content-addressed (sha256 of decoded audio), evictable, rebuildable. So:
  losing the artifact cache is a cache-miss, not data loss. The **SQLite of
  accounts/tokens** is the one thing that genuinely wants a backup.
- **Versioned at `/v1`.** Every route is emitted under `/v1/...` and I'll bump
  the segment for breaking changes — so you can route by path prefix safely.
- **Runs in a venv** at `~/.virtualenvs/douanier`, created with
  `--system-site-packages` so it reuses the already-installed ML stack
  (torch/CLAP/numpy/librosa/demucs) and only adds FastAPI/uvicorn on top. This
  matters for the containerization question below.

## What's running today

A walking skeleton (≈100 lines): `GET /v1/healthz` + a synchronous
`POST /v1/analyze/emotion` (upload a clip → valence/arousal + emotion tags),
guarded by a hardcoded dev bearer. Run locally:

```bash
cd armada/api
DOUANIER_DEV_TOKEN=$(openssl rand -hex 16) \
  ~/.virtualenvs/douanier/bin/uvicorn app:app --port 8780
# → http://127.0.0.1:8780/v1/healthz   /v1/docs
```

The async jobs, SQLite auth/quota, content cache, signed artifact URLs, and the
heavy separation endpoints are designed and queued (tasks #23–#35) — they land
incrementally *under* this skeleton without changing its public shape.

## What I need from you (the actual questions)

1. **Public mapping.** Path-based (`api.nech.pl/v1/sound/...` → proxy forwards to
   the service) or host-based (`sound.api.nech.pl`)? I emit `/v1/...`; tell me if
   you'll strip/rewrite a prefix so I can set `root_path` accordingly.
2. **Process model.** My default is **systemd --user** units (`douanier-api` +
   `douanier-worker`, linger on), mirroring the Bridge. Do you prefer that, or a
   **Docker** container? ⚠️ Containerizing is awkward here: the venv leans on the
   host's multi-GB GPU-coupled ML stack — a faithful image is huge and GPU-bound.
   If you want Docker, let's talk about whether the heavy worker stays on the host.
3. **Port + bind.** What port should it bind on localhost (dev uses 8780)? Any
   port registry on the box I should claim from?
4. **TLS.** I assume you terminate TLS at the proxy (Caddy/nginx/Traefik) and the
   service stays plain HTTP on loopback. Correct?
5. **GPU dispatch.** CPU-only today. When we add separation, do GPU jobs run on
   *this* box (RTX 2060) or a separate runner? How do you want me to target cloud
   GPU later — env-selected backend, or a dispatch URL?
6. **Mounts.** Is the **freebox** reachable by the service user (path? always
   mounted?) so I can point `DOUANIER_CACHE` at it? It'll grow to ~1–2 TB at
   "success" scale; it's evictable, so a size cap is fine — any cap you want?
7. **Secrets.** How should I inject the bearer tokens' DB and the **HMAC signing
   key** for artifact URLs — a systemd `EnvironmentFile`, a secrets manager,
   something else? (Nothing secret is committed; tokens are minted via a CLI.)
8. **Observability.** journald enough, or do you want logs shipped somewhere? I
   can expose `/v1/metrics` (Prometheus text) — want it? Liveness probe is
   `GET /v1/healthz` (200 + engine availability).
9. **Edge rate-limit / WAF.** I do per-token quota in-app (SQLite token-bucket).
   Do you also want a coarse IP rate-limit or WAF at the proxy? And is
   `api.nech.pl` already DNS'd to the box / behind Cloudflare?
10. **CORS origins.** hexa calls cross-origin from Vercel. Want the allowlist
    enforced at the proxy, in-app, or both? Send me his origins when known.

## What I'll give you (the contract)

- A stable `/v1` route surface + **OpenAPI** at `/v1/openapi.json` and Swagger at
  `/v1/docs`.
- `GET /v1/healthz` for your liveness/readiness probes.
- A `DEPLOY.md` (task #34) with the exact run command, every env var, ports,
  mounts, and the backup note (back up the SQLite, ignore the cache).
- **systemd unit files** for the API + worker (or a Dockerfile if you pick #2).

No rush on all ten — #1, #2, #3 unblock the first real deploy; the rest can come
as the heavier endpoints land. Reply inline in this file or ping me.

— *Douanier, the customs officer* (on behalf of the fleet) ⛵
