Files
esh-pfi-infrastructure/stacks/muninn-gate/compose.yaml
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

71 lines
3.4 KiB
YAML

# muninn-gate — WG-internal HTTP front door for the Muninn ingestion queue (#377).
# Deployed on 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.
#
# Image is built out-of-band (the Gitea read token rides as a BuildKit secret, so
# it never lands in a layer) — see README.md:
# DOCKER_BUILDKIT=1 docker build --secret id=gitea_pw,src=<token> -t muninn-gate:0.0.14 .
#
# Real config (with bearer-key secrets) lives on the server at
# /opt/docker/conf/muninn-gate/muninn-gate.yaml (gitignored); repo carries the
# redacted conf/muninn-gate.example.yaml.
services:
muninn-gate:
image: muninn-gate:0.0.14
container_name: muninn-gate
restart: unless-stopped
# Run as the ingestion-owner uid (vh, 1000:1000) so the gate can WRITE the
# queue (ingestion dir is 1000:1000 0755). Overrides the image's build user;
# non-root, least-privilege (no root needed — binds :8090, reads config +
# staging, writes only ingestion). The mounted config is chowned 1000:1000 0600.
user: "1000:1000"
# WG-internal bind straight to the host WG address; the launcher owns the bind
# (the app's `bind` field is observability-only). Host networking so --host/--port
# land directly on corviduo-dev.
network_mode: host
command:
- uvicorn
- muninn_gate.entrypoint:app_factory
- --factory
- --host
- "10.250.50.152"
- --port
- "8090"
environment:
MUNINN_GATE_CONFIG: /app/config/muninn-gate.yaml
volumes:
# ingestion_root = /data/state/ingestion — the SAME volume the personal muninn
# watcher uses, byte-identical path in both containers (absolute-path agreement).
# NOTE (hardening candidate): whole state volume per muninn-dev's spec; a subpath
# mount of just `ingestion` -> /data/state/ingestion would be tighter (gate only
# needs RW on ingestion). Flag to muninn-dev before adopting.
- worldtree-personal_worldtree-state:/data/state
# single-writer mounted config (bearer-key secrets), read-only:
- /opt/docker/conf/muninn-gate/muninn-gate.yaml:/app/config/muninn-gate.yaml:ro
# shared staging root — SAME absolute path as the watcher's bind, read-only
# (gate only resolves + reads staging; never writes there):
- /mnt/muninn-staging/mimir-inbox:/mnt/muninn-staging/mimir-inbox:ro
healthcheck:
# /ping is the one anonymous route; returns {"service":"ok"}. NB /health must
# NOT be the liveness probe — it is always-200 by design even when the watcher
# is down, so it would never restart the gate (it would wrongly signal the
# watcher's container).
test: ["CMD", "python", "-c", "import urllib.request,sys; sys.exit(0 if b'ok' in urllib.request.urlopen('http://10.250.50.152:8090/ping', timeout=3).read() else 1)"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
labels:
- homepage.group=Worldtree
- homepage.name=Muninn Gate
- homepage.icon=mdi-gate-arrow-right
- homepage.description=Muninn ingestion queue front door (#377)
- homepage.href=http://10.250.50.152:8090/ping
volumes:
# The worldtree-personal muninn watcher's state volume — external, managed by the
# worldtree-personal compose. We attach to it read/write for the ingestion queue.
worldtree-personal_worldtree-state:
external: true