08889cad88
Telegram was the only way off this box, and it is not a direct path: it
needs api.telegram.org, a socks relay on the host and a matching ufw rule.
Each of those three has failed once, and when they do a sev4 nudge has
nowhere to go. ntfy shares none of them.
The spare is the smaller half of it. The routing table already sends
sev3-away nudges and away reminders to ntfy and to nothing else, so with no
block configured those two routes hit a nil sink in DispatchNudge and
DispatchReminder and are skipped — no log line, no delivery_attempts row.
An away reminder is worse than dropped: out stays empty, so MarkReminder
never runs and it re-fires every tick without ever being delivered.
Owner's call, 07-08-2026: ntfy.kvmx.ru, topic maven.
The sink now takes a bearer token, which is what that server wants and what
it could not do before. ntfy scopes a token to one topic and to write-only,
so a popped sink can push to the maven topic and cannot read it back. Basic
auth stays for a server with no tokens; configuring both is refused rather
than resolved by guessing.
Config keys got json tags. docs/operations.md has documented this block as
base_url/topic since before it existed, and the untagged struct would only
have answered to BaseURL/Topic — the documented config would have parsed
into an empty one.
The token is a ${NTFY_TOKEN} expansion from the gitignored
deploy/telegram.env, beside the telegram secrets. TestDeployConfigLoads now
fails if the block goes missing, because deleting it is how you turn the
reach off and the two silent routes are what that costs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMNNEkYx1mZFtHNrFk7uqb
205 lines
6.9 KiB
Markdown
205 lines
6.9 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
cd "$ROOT"
|
|
go build ./cmd/mavend/
|
|
go build ./cmd/mavsttd/ # needs CGO (whisper.cpp)
|
|
go build ./cmd/mavttsd/ # pure Go
|
|
```
|
|
|
|
## mavend — daemon (core)
|
|
|
|
```bash
|
|
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).
|
|
|
|
```json
|
|
{
|
|
"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.
|
|
|
|
`${NTFY_TOKEN}` and the `${TELEGRAM_*}` vars are expanded from `deploy/telegram.env`, which is gitignored. Copy `deploy/telegram.env.example` and fill it in. Mint a scoped token rather than reusing an admin one. It needs write access to the `maven` topic and nothing else:
|
|
|
|
```sh
|
|
ntfy access maven maven write-only
|
|
ntfy token add --expires=never maven
|
|
```
|
|
|
|
Deleting the `ntfy` block turns the reach off, and that is not a no-op. The routing table sends sev3-away nudges and away reminders to ntfy and nowhere else. With no sink wired they hit a nil and vanish, leaving no log line and no `delivery_attempts` row (V-649).
|
|
|
|
## mavsttd — STT worker (optional, remote whisper.cpp)
|
|
|
|
Requires `LD_LIBRARY_PATH` to include deps/lib (for libwhisper.so, libggml-vulkan.so).
|
|
|
|
```bash
|
|
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).
|
|
|
|
```bash
|
|
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.
|
|
|
|
```bash
|
|
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):
|
|
|
|
```bash
|
|
./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`:
|
|
|
|
```bash
|
|
./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.
|
|
|
|
```bash
|
|
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)
|
|
|
|
```bash
|
|
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)
|
|
|
|
```bash
|
|
cd "$ROOT"
|
|
./mavend -config mavend.json # voice enabled, no stt/tts/embedder config → all stubs
|
|
```
|
|
|
|
## Run all tests
|
|
|
|
```bash
|
|
cd "$ROOT"
|
|
go test ./internal/router/ ./internal/delivery/... ./cmd/mavend/ ./cmd/mavsttd/ ./cmd/mavttsd/
|
|
```
|
|
|
|
## Benchmark
|
|
|
|
```bash
|
|
cd "$ROOT"
|
|
go test -bench=. ./internal/router/ ./cmd/mavsttd/ ./cmd/mavttsd/
|
|
```
|