Files
esh-pfi-infrastructure/stacks/muninn-gate/README.md
T
vh 786462ac9c feat(muninn-gate): WG-internal Muninn ingestion front door stack (#377)
Deployed on corviduo-dev, co-located with the worldtree-personal muninn
watcher. ingestion_root=/data/state/ingestion (shared state volume, byte-
identical to the watcher); runs as uid 1000 to write the queue; staging
bound :ro at the ratified /mnt/muninn-staging/mimir-inbox (local placeholder
until the shared mount + mimir-inbox writer land). Boot verified: /ping
{"service":"ok"}, /health watcher.running=true (byte-identity proven).

Image built out-of-band with the Gitea read token as a BuildKit secret.
Real config (bearer keys) lives on-server at /opt/docker/conf 0600.
2026-07-30 21:33:29 -07:00

57 lines
2.9 KiB
Markdown

# muninn-gate
WG-internal HTTP front door for the Muninn ingestion queue (Worldtree #377).
Path-addressed submit, list/status, cancel/retry, watcher-liveness health.
Upstream: `vh/muninn-gate` (no Dockerfile there — this stack owns containerization).
## Where it runs
**corviduo-dev (10.250.50.152)**, co-located with the worldtree-personal muninn
watcher (`worldtree-personal-worldtree-muninn-1`). infra-ops-managed stack,
separate from the worldtree CI/CD compose. Bind: `10.250.50.152:8090`, `network_mode: host`.
## Key wiring (why it's shaped this way)
- **`ingestion_root: /data/state/ingestion`** — the `worldtree-personal_worldtree-state`
volume, mounted at `/data/state`. Byte-identical path to the watcher's view; the
dispatch records absolute paths and both processes read each other's.
Acceptance: `/health``watcher.running: true` **proves** this byte-identity
(gate is reading the heartbeat the watcher writes). `no_heartbeat` with the
watcher up = root mismatch.
- **`user: "1000:1000"`** — the ingestion dir is `vh:vh 0755`; the gate must run as
uid 1000 to write the queue (non-root, least-privilege). Overrides the image user.
- **staging** — `/mnt/muninn-staging/mimir-inbox`, same absolute path bound `:ro` in
BOTH the gate and the watcher (worldtree-dev's b162). Currently a LOCAL placeholder
dir; becomes the shared mount when mimir-inbox (the writer) lands. **No gate-side
check can tell a real share from an empty dir** (docker fabricates a missing bind
source) — the guard is operational: confirm the host mount exists before wiring/
repointing a bind. Repoint needs a restart (config is read once at boot).
- **health probe uses `/ping`, not `/health`** — `/health` is always-200 by design
(watcher-down is report *content*, not a status code), so it must never be the
liveness probe or it would restart the wrong container.
## Config (single-writer, infra-ops)
Real config with bearer-key secrets lives on the server at
`/opt/docker/conf/muninn-gate/muninn-gate.yaml` (`1000:1000`, `0600`, gitignored).
Redacted schema: `conf/muninn-gate.example.yaml`. Schema is CLOSED — any unknown
field is a boot failure. Keys are flat-scope (`read | submit | control`, no
inheritance); `name` is the non-secret `submitted_by` identity.
## Build + deploy
Image builds out-of-band — the Gitea read token (for `muninn-dispatch==0.1.4` from
the internal index) rides as a BuildKit secret, never a layer:
```bash
DOCKER_BUILDKIT=1 docker build --secret id=gitea_pw,src=<gitea-read-token> -t muninn-gate:0.0.14 .
docker compose up -d # after installing the config to /opt/docker/conf/muninn-gate/
```
## Status
Booted + healthy 2026-07-31; `/ping` + `/health` (watcher:running:true,
ingestion_root_writable:true) verified. **SUBMIT deferred** — returns `not_found`
against the placeholder staging until the real shared mount + mimir-inbox writer
exist; then repoint + the one-file path-agreement probe + the real acceptance run.