- Tests: all-three-set → ok; one-missing → error; nil → ok - Log message on HashEmbedder fallback in voice.go - Document floor mode in START.md Co-Authored-By: opencode <opencode@anthropic.com>
5.5 KiB
Start Commands
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:
{
"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"
},
"phraser": {
"model_path": "/mnt/hdd1/llms/LFM2.5/LFM2.5-1.2B-Instruct-Q4_K_M.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.
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.
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.shon 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/