Files
Maven/docs/operations.md
claude 35c6ff5a71 Make delivery and integration failures explicit
Persist reminder presentations and retry state, atomically complete collapsed deliveries, fall back across away reaches, and block permanent failures visibly (V-715, V-678). Fail closed when enabled integrations lack credentials and keep remote arms explicitly dark (V-691). Give mavweb one sanitized, request-correlated error contract (V-689). Owner explicitly requested direct commits to master.
2026-08-13 02:50:59 +04:00

7.5 KiB

Start Commands

Last verified: 2026-08-07 @ a4630b9. Living doc: correct it in place, do not append.

All commands assume ROOT=/home/kami/apps/Maven and the local Go toolchain at $ROOT/deps/go/go/bin/go.

Prerequisites

export ROOT=/home/kami/apps/Maven
export CGO_CFLAGS="-I$ROOT/deps/include -I$ROOT/deps/whisper.cpp/ggml/include"
export CGO_LDFLAGS="-L$ROOT/deps/lib -Wl,-rpath,$ROOT/deps/lib"
export LD_LIBRARY_PATH="$ROOT/deps/lib"
export PATH="$ROOT/deps/go/go/bin:$PATH"

Build everything

cd "$ROOT"
go build ./cmd/mavend/
go build ./cmd/mavsttd/   # needs CGO (whisper.cpp)
go build ./cmd/mavttsd/   # pure Go

mavend — daemon (core)

cd "$ROOT"
./mavend -config mavend.json

Config path: ~/.config/maven/mavend.json. Full example with all options.

The phraser.model_path below is an example — point it at whatever GGUF you have locally. The deployed value lives in deploy/mavend.json, currently Qwen3.5-0.8B.Q4_K_M.gguf; the target is the CPT'd Qwen3-1.7B (#122).

{
  "db_path": "/home/kami/.local/share/maven/maven.db",
  "socket_path": "/run/user/1000/maven/mavend.sock",
  "tick_interval": "60s",
  "repeat_interval": "5m",
  "ntfy": {
    "base_url": "https://ntfy.kvmx.ru",
    "topic": "maven",
    "token": "${NTFY_TOKEN}"
  },
  "phraser": {
    "model_path": "/mnt/hdd1/llms/Qwen3-Maven-1.7B-Q8_0.gguf",
    "bin_path": "/usr/local/bin/llama-server",
    "n_gpu_layers": -1
  },
  "voice": {
    "enabled": true,
    "bind": "127.0.0.1:9100",
    "lang": "ru",
    "embedder": {
      "model_path": "models/embedder/model.onnx",
      "tokenizer_path": "models/embedder/tokenizer.json",
      "lib_path": "deps/onnxruntime-linux-x64-1.17.1/lib/libonnxruntime.so.1.17.1"
    }
  }
}

Omit the embedder block entirely to use the deterministic HashEmbedder floor (no ML, no ONNX runtime dependency). Useful for testing or low-resource setups.

deploy/telegram.env.example is the canonical inventory for every deployed secret, including values whose destination is the root .env, the workpc CW2 environment file, or deploy/db_key.env. Copy values only to the destination named beside them; never commit the populated files. Maven expands the homesrv sink, workstation, and Home Assistant variables from deploy/telegram.env.

Every written integration block is either explicitly disabled or live. A live Telegram, ntfy, LAN workstation model/STT, Home Assistant, ambient, CW2, or encrypted-database configuration with an empty credential fails startup. This keeps a missing env file from quietly becoming fallback behavior. The deployed ntfy is currently disabled: true, and the workstation model arm is model_disabled: true; the separately credentialed CW2 STT arm remains live. Remove a dark-state flag only after provisioning that arm's credential.

Mint a scoped ntfy token rather than reusing an admin one. It needs write access to the maven topic and nothing else:

ntfy access maven maven write-only
ntfy token add --expires=never maven

disabled: true keeps a documented ntfy block dark. When enabled, reminders try ntfy and fall through to Telegram; delivery attempts record each reach.

mavsttd — STT worker (optional, remote whisper.cpp)

Requires LD_LIBRARY_PATH to include deps/lib (for libwhisper.so, libggml-vulkan.so).

cd "$ROOT"
export LD_LIBRARY_PATH="$ROOT/deps/lib"
./mavsttd -socket /run/user/$UID/maven/stt.sock -model models/stt/ggml-small.bin

Without -model it runs as a stub (deterministic, no ML).

mavttsd — TTS worker (optional, remote Piper)

Requires LD_LIBRARY_PATH to include deps/piper (for Piper's espeak-ng).

cd "$ROOT"
export LD_LIBRARY_PATH="$ROOT/deps/piper"
./mavttsd -socket /run/user/$UID/maven/tts.sock -piper deps/piper/piper -model models/tts/ru_RU-irina-medium.onnx -espeak_data deps/piper/espeak-ng-data

Without -piper it runs as a stub.

-lexicon deploy/tts-lexicon.json adds the pronunciation dictionary: a flat JSON object of name to Russian spelling, applied to the text just before piper reads it. It is how Vikunja is said as a word rather than spelled out, and how SearXNG and homesrv are said at all. Off unless the flag is set; a path that is set and unreadable stops mavttsd rather than letting it say names wrong in silence. Adding a name needs a restart of mavttsd and nothing else.

mavweb — PWA voice bridge (WebSocket ↔ TCP)

No CGo, no deps; builds with stock Go.

cd "$ROOT"
go build ./cmd/mavweb/
./mavweb -addr :9200 -voice 127.0.0.1:9100

To also receive proactive nudges in-app, pass the ntfy WebSocket subscribe URL (the PWA connects to it directly; the auth token stays server-side config):

./mavweb -addr :9200 -voice 127.0.0.1:9100 \
  -ntfy 'wss://ntfy.kvmx.ru/maven/ws?auth=<base64-token>'

<base64-token> is a read-capable ntfy access token, base64url-encoded (ntfy's browser-WS auth: Bearer tk_... can't set a header, so ntfy takes it as the ?auth= query param). Without -ntfy, the PWA stays voice-only.

presence-signal ingest (-core)

Pass mavend's IPC socket so mavweb can feed presence via /api/signal:

./mavweb -addr :9200 -voice 127.0.0.1:9100 \
  -core /run/user/1000/maven/mavend.sock
  • page_heartbeat (weak, τ=4min) — the PWA auto-pings every 30s. Nothing to do.
  • desk_active (strongest, τ=8min) — a workstation signal (hyprland), so it can't be a homesrv module. Run scripts/desk-active.sh on the PC via a systemd-user timer, gated by hypridle (see the script header). Posts over wg.
  • wg_handshake (coarse, τ=20min) — still unfed; it's homesrv-local (wg show latest-handshakes), a natural small poller to add next.

Allowlisted keys only; without -core, /api/signal returns 503 and presence stays cold-start away.

Open http://10.42.0.1:9200/ (or http://voice.kvmx.ru:9200/) on your phone from inside the WireGuard tunnel. Tap & hold to speak; release to send; the reply plays automatically.

mavpoll — env poller (netdata + uptime-kuma → facts)

Thin adapter: reads netdata alarms + kuma monitor status and writes env facts through core's IPC socket. This is what makes service_down (sev4) and netdata_critical (sev3) rules fire on real data. Runs on homesrv where both services live — hit them on localhost, not the public .kvmx.ru names.

cd "$ROOT"
go build ./cmd/mavpoll/
# netdata only (kuma disabled until its API key exists):
./mavpoll -socket /run/user/$UID/maven/mavend.sock -netdata http://127.0.0.1:19999
# with kuma: create an API key in Kuma → Settings → API Keys, then:
./mavpoll -socket /run/user/$UID/maven/mavend.sock \
  -netdata http://127.0.0.1:19999 \
  -kuma http://127.0.0.1:3001/metrics -kuma-key <API_KEY>

Writes only on value change (append-only, no per-tick churn). service_down aggregates any monitor reading 0 as "down"; per-service granularity is a later add. netdata -timeout/-interval tunable; defaults 8s / 60s.

nginx (optional)

sudo cp cmd/mavweb/nginx.conf /etc/nginx/sites-available/voice.kvmx.ru
sudo ln -sf /etc/nginx/sites-available/voice.kvmx.ru /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Quick smoke test (stubs, no models)

cd "$ROOT"
./mavend -config mavend.json   # voice enabled, no stt/tts/embedder config → all stubs

Run all tests

cd "$ROOT"
go test ./internal/router/ ./internal/delivery/... ./cmd/mavend/ ./cmd/mavsttd/ ./cmd/mavttsd/

Benchmark

cd "$ROOT"
go test -bench=. ./internal/router/ ./cmd/mavsttd/ ./cmd/mavttsd/