06576b406c
Go flag parsing stops at the separate boolean value before ambient-token. Use -ambient-enabled=value and pin the deployed argv contract discovered during live V-691 verification. Owner explicitly requested direct commits to master.
224 lines
11 KiB
YAML
224 lines
11 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:
|
|
context: .
|
|
# the zone is declared once, here. The image points /etc/localtime at it so
|
|
# a caller reading the system zone agrees with one reading TZ (V-545).
|
|
args:
|
|
TZ: Europe/Samara
|
|
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
|
|
# the voice wire, for mavwaked and mavenclient on workpc (V-515).
|
|
#
|
|
# LOOPBACK ONLY, and that is the whole security argument. internal/voice
|
|
# is plaintext with no auth: its own server doc says production binds
|
|
# inside the wg tunnel, "the wg layer IS the L0 floor". workpc is not a wg
|
|
# peer, it is on wlan0. So the tunnel is ssh instead, terminated on this
|
|
# loopback address, and nothing new is on the LAN. Anyone who could reach
|
|
# a LAN-bound port here could push audio and hear his facts read back.
|
|
# SurfaceVoice caps acts at L0; it does not cap reading.
|
|
#
|
|
# Host 9100 is Vikunja's MCP, hence 9110. The container side stays 9100
|
|
# so mavweb keeps reaching mavend:9100 by name.
|
|
ports: ["127.0.0.1:9110:9100"]
|
|
|
|
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.
|
|
# -ambient-enabled turns on POST /api/ambient (Vikunja #126): the phone posts
|
|
# notification text, mavweb keeps only a meeting time. The explicit enabled
|
|
# flag prevents an unset token from looking like an intentional dark state.
|
|
# Both values come from the gitignored .env docker compose reads for
|
|
# interpolation, NOT from an env_file — flags are interpolated before any
|
|
# service env exists.
|
|
# Weakness worth naming: mavweb takes this as a flag, so it is visible in
|
|
# `ps` inside this container, unlike the zenmoney and IMAP secrets which are
|
|
# read from files.
|
|
command: ["mavweb", "-addr", ":9201", "-voice", "mavend:9100", "-core", "/run/maven/mavend.sock",
|
|
"-ambient-enabled=${MAVEN_AMBIENT_ENABLED:-false}",
|
|
"-ambient-token", "${MAVEN_AMBIENT_TOKEN:-}",
|
|
"-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"]
|
|
# Money tracking (Vikunja #125) is OFF: it needs a zenmoney token,
|
|
# which mavpoll reads from a FILE so it never appears in `ps`, in
|
|
# this file, or in shell history. To enable, mount the token and
|
|
# append: "-zenmoney-token-file", "/run/secrets/zenmoney.token"
|
|
# (optionally "-zenmoney-interval", "1h"). Core never sees the
|
|
# token — the poller writes facts(kind=env, source=poll:zenmoney)
|
|
# and mavend only reads those back when he asks.
|
|
depends_on: [mavend]
|
|
volumes:
|
|
- sockets:/run/maven
|
|
# - ./deploy/zenmoney.token:/run/secrets/zenmoney.token:ro
|
|
|
|
# The mail reader (Vikunja #246) is OFF and commented out: it needs an IMAP
|
|
# account, and there is none on this box. mavmaild reads the password from a
|
|
# FILE so it never appears in `ps`, in this file, or in shell history — the
|
|
# same rule mavpoll follows for the zenmoney token. Core never sees the
|
|
# password: the reader hands core message text on one IPC method, and core
|
|
# writes what the model extracts as task CANDIDATES he reviews on /tasks.
|
|
# Nothing here can create a reminder, so a misread mail cannot fire.
|
|
#
|
|
# To enable: write the password to deploy/imap.password (0600, gitignored),
|
|
# add an "email": {} block to deploy/mavend.json, and uncomment this service.
|
|
# mavmaild:
|
|
# <<: *image
|
|
# command: ["mavmaild", "-socket", "/run/maven/mavend.sock",
|
|
# "-imap", "imap.example.org:993",
|
|
# "-user", "kami@example.org",
|
|
# "-password-file", "/run/secrets/imap.password",
|
|
# "-mailbox", "INBOX",
|
|
# "-interval", "15m",
|
|
# "-state", "/var/lib/mavmaild/mail-seen.json"]
|
|
# depends_on: [mavend]
|
|
# volumes:
|
|
# - sockets:/run/maven
|
|
# # Its OWN volume, not dbdata. The whole point of a separate reader is
|
|
# # that a compromise on either side does not reach the other, and dbdata
|
|
# # is the encrypted database. The reader needs one JSON file of UIDs and
|
|
# # gets a volume that holds nothing else, so neither can be restored from
|
|
# # a backup of the other.
|
|
# - maildata:/var/lib/mavmaild
|
|
# - ./deploy/imap.password:/run/secrets/imap.password:ro
|
|
|
|
# The calendar reader (Vikunja #644) is OFF and commented out: it needs a
|
|
# CalDAV account, and there is none on this box. It was built, listed in
|
|
# `make build`, and deployed nowhere, which is the worst of the three states —
|
|
# this block records the decision instead.
|
|
#
|
|
# What its absence costs, so the cost is visible from here:
|
|
# - Agenda questions route correctly and answer from nothing. Stage 0 sends
|
|
# "что у меня сегодня" to IntentQuery (V-498) and the `calendar` query
|
|
# source reads facts(kind=env, source=caldav:*) that nobody writes.
|
|
# - The nudge gate loses a suppressor. loop.State.CalendarBusy is fed by
|
|
# those same facts, so "do not nag mid-meeting" is permanently false.
|
|
#
|
|
# Core never sees the CalDAV password: the reader polls the collection itself
|
|
# and hands core one fact per event over WriteFact. Nothing here can create a
|
|
# reminder, so a misread event cannot fire.
|
|
#
|
|
# The password is read from a FILE, so it never appears in `ps`, in this file,
|
|
# or in shell history — the same rule mavpoll and mavmaild follow.
|
|
#
|
|
# To enable: write the password to deploy/caldav.password (0600, gitignored),
|
|
# point -url at the collection, and uncomment this service. No mavend.json
|
|
# block is needed — events arrive over IPC as facts. -render-url is optional
|
|
# and OFF here: it publishes Maven's own reminders back as events, and it must
|
|
# not name the collection -url reads, or the poller reads its own writes back
|
|
# in (checkRenderTarget refuses that). It takes -render-pass-file, and falls
|
|
# back to this password when that is not given.
|
|
# mavcaldav:
|
|
# <<: *image
|
|
# command: ["mavcaldav", "-socket", "/run/maven/mavend.sock",
|
|
# "-url", "http://localhost:5232/kami/personal",
|
|
# "-user", "kami",
|
|
# "-pass-file", "/run/secrets/caldav.password",
|
|
# "-interval", "5m"]
|
|
# depends_on: [mavend]
|
|
# volumes:
|
|
# - sockets:/run/maven
|
|
# - ./deploy/caldav.password:/run/secrets/caldav.password:ro
|
|
|
|
volumes:
|
|
dbdata:
|
|
sockets:
|
|
# maildata — the mail reader's seen-UID file, and nothing else. See mavmaild.
|
|
maildata:
|
|
|
|
networks:
|
|
default:
|
|
ecosystem:
|
|
external: true # created by deploy/ecosystem/docker-compose.yml
|
|
name: ecosystem
|