1ee9a930d3
compose set TZ=Europe/Samara and Go read it, so clock replies and quiet hours were already local. But /etc/localtime in the image pointed at Etc/UTC, so a caller asking the system zone instead of the environment answered UTC. The reminder path shells out to python dateparser, which is such a caller. TZ is now a build arg on the runtime stage. It points the symlink, writes /etc/timezone and sets ENV TZ, so the image is local on its own. Compose passes the zone it already declares, so the zone stays written in one place.
171 lines
7.8 KiB
YAML
171 lines
7.8 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
|
|
|
|
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-token turns on POST /api/ambient (Vikunja #126): the phone posts
|
|
# notification text, mavweb keeps only a meeting time. Empty ⇒ no route at
|
|
# all, which is what a missing MAVEN_AMBIENT_TOKEN gives. The value comes
|
|
# 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-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
|
|
|
|
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
|