Files
Maven/docker-compose.yml
kami 691c3660d2 Bind mavweb to loopback and make the step-up fail-open loud and overridable
The /tools enable action takes name+cmd from form fields and calls
core.EnableTool, so it defines arbitrary argv that internal/tool then executes.
Its step-up gate read `session != nil && !session.IsStepUp()`, and
stepUpSession is nil unless both -webauthn-origin and -webauthn-rpid are set —
so with neither flag the gate was skipped entirely. compose passed neither and
published 9201 on every host interface, while /ptt proxies to the voice server
unauthenticated, so a caller could enable a tool, trigger it, and answer its
own confirm turn. internal/tool's boundary reasoning ("a compromised router
can't grant itself a capability") held; the outer boundary it depends on was an
unwritten deployment assumption.

The fail-open itself stays: gating on a session that can never be asserted
would 403 permanently, and that reasoning is sound. What was missing is the
compensating control.

- compose publishes 127.0.0.1:9201 so reaching the UI requires the wg tunnel by
  construction rather than by convention. Verified no other service reaches
  mavweb by host-published port; mavpoll is host-networked but only dials
  netdata and kuma.
- stepUpOK() replaces the two inline gates in handleTools and handleRevert, so
  one decision point covers both surfaces.
- -require-stepup (default false, behaviour byte-for-byte unchanged) fails those
  actions closed when step-up cannot be asserted.
- A startup warning names both unguarded surfaces when stepUpSession is nil,
  in fail-open and fail-closed variants.

Also repoints one doc comment at DESIGN.md, since it shared a hunk with the
warning block.

The committed kuma key is deliberately left for a separate change: the old
value is in git history forever, so rotation means a genuinely new key, not a
re-commit under a variable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X5JApcrCRVGmqrxnhynSik
2026-07-30 23:39:07 +04:00

117 lines
4.7 KiB
YAML

name: maven
# One image (built once), one container per daemon. Only mavend holds the key
# and the db volume; the modules mount just the shared socket dir + models.
# IPC stays unix-domain over the shared `sockets` volume — no code change from
# the bare-metal setup, only paths move to /run/maven.
x-image: &image
# NOT "maven" — that's Apache Maven on Docker Hub and compose will happily
# pull it, giving every container `mvn-entrypoint.sh` and exit 127.
image: mavenai:latest
# build on EVERY service (same image name ⇒ built once) so `docker compose
# build <anyservice>` actually rebuilds. With build on only one service, the
# others silently no-op and you deploy a stale binary.
build: .
pull_policy: never # only ever the locally-built image
restart: unless-stopped
# local time for clock/date replies AND quiet-hours evaluation. Change to
# your zone; needs tzdata in the image (installed in the Dockerfile).
environment:
- TZ=Europe/Samara
services:
mavend:
<<: *image
command: ["mavend", "-config", "/opt/maven/config/mavend.json"]
# both nets: `default` keeps the `mavend` DNS name the other daemons reach
# (mavweb -> mavend:9100); `ecosystem` reaches nexus/praxis/hexis by name.
networks: [default, ecosystem]
# the key lives ONLY here. deploy/db_key.env holds MAVEN_DB_KEY=<base64-32B>.
env_file:
- ./deploy/db_key.env
- ./deploy/telegram.env
volumes:
- dbdata:/var/lib/maven # encrypted db at rest
- sockets:/run/maven # IPC socket dir
- ./deploy/mavend.json:/opt/maven/config/mavend.json:ro
- ./models:/opt/maven/models:ro
- /mnt/hdd1/llms:/opt/maven/models/llm:ro # LFM gguf library
# the LFM engine (llama-server) offloads onto the AMD iGPU (RADV RENOIR,
# Ryzen 5 5600U) via Vulkan — same device + render gid as mavsttd, which
# uses the same driver for whisper. Without these Vulkan
# enumerates zero devices and llama-server silently falls back to CPU.
devices:
- "/dev/dri:/dev/dri"
group_add:
- "993" # host 'render' gid owning /dev/dri/renderD128 (getent group render)
# the decrypted working copy lives in RAM (see db_tmpfs in mavend.json).
tmpfs:
- /dev/shm
mavsttd:
<<: *image
command: ["mavsttd", "-socket", "/run/maven/stt.sock", "-model", "/opt/maven/models/stt/ggml-small.bin"]
depends_on: [mavend]
# whisper uses libggml-vulkan (RADV on the host's AMD GPU) → needs the
# render node AND membership in the group that owns it, or Vulkan enumerates
# zero devices and falls back to CPU.
devices:
- "/dev/dri:/dev/dri"
group_add:
- "993" # host 'render' gid owning /dev/dri/renderD128 (getent group render)
volumes:
- sockets:/run/maven
- ./models:/opt/maven/models:ro
mavttsd:
<<: *image
command: ["mavttsd", "-socket", "/run/maven/tts.sock",
"-piper", "/opt/maven/piper/piper",
"-model", "/opt/maven/models/tts/ru_RU-irina-medium.onnx",
"-espeak_data", "/opt/maven/piper/espeak-ng-data"]
depends_on: [mavend]
volumes:
- sockets:/run/maven
- ./models:/opt/maven/models:ro
mavweb:
<<: *image
# voice.bind is 0.0.0.0:9100 in deploy/mavend.json so mavweb can reach it
# cross-container. Verified 2026-07-06.
command: ["mavweb", "-addr", ":9201", "-voice", "mavend:9100", "-core", "/run/maven/mavend.sock",
"-nexus", "http://nexus:9740", "-praxis", "http://praxis:8989", "-hexis", "http://hexis:9741"]
depends_on: [mavend]
# loopback-only on purpose: /tools defines+executes arbitrary argv and
# mavweb has no in-process auth of its own without -webauthn-origin/-rpid.
# Reaching the UI therefore requires the wg tunnel (or the local nginx) by
# construction, not by convention. The other daemons talk to mavweb over
# the compose network, not this published port.
ports: ["127.0.0.1:9201:9201"]
# ecosystem: reach the siblings by name for the read-only /ecosystem panel;
# default: keep resolving mavend:9100 for voice + the IPC socket peers.
networks: [default, ecosystem]
volumes:
- sockets:/run/maven
mavpoll:
<<: *image
network_mode: host
command: ["mavpoll", "-socket", "/run/maven/mavend.sock",
"-netdata", "http://127.0.0.1:19999",
"-kuma", "http://127.0.0.1:3001/metrics",
"-kuma-key", "uk5_mavpoll-key"]
depends_on: [mavend]
volumes:
- sockets:/run/maven
volumes:
dbdata:
sockets:
networks:
default:
ecosystem:
external: true # created by deploy/ecosystem/docker-compose.yml
name: ecosystem