9d58922462
The push half accepts an @channelusername and the intake half cannot: an
inbound update names its chat by number, so an @-name matches nothing. The
check lived in NewPoller, which wireTelegramIntake logs and returns from, so a
box configured that way booted clean with a dead intake half and a working push
half. Nothing looked broken from the chat.
ValidateIntakeChatID moves the rule where config validation can reach it, the
same shape validateNetScan uses. It is stricter than the old prefix test: any
non-digit is refused, not just a leading @. An empty token or chat id still
means telegram is not wired, because an unset ${TELEGRAM_*} expands to empty
and that must not fail a box with no bot.
deploy/mavend.json turns intake on. The chat id on this box is numeric.
The onCallback comment claimed every path answers the callback. The fromOwner
early return does not, and silence toward a stranger is correct, so the comment
was what was wrong.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
250 lines
11 KiB
JSON
250 lines
11 KiB
JSON
{
|
|
"db_path": "/var/lib/maven/maven.db.enc",
|
|
"db_tmpfs": "/dev/shm/maven-plain.db",
|
|
"db_key_env": "MAVEN_DB_KEY",
|
|
"socket_path": "/run/maven/mavend.sock",
|
|
"state_dir": "/var/lib/maven",
|
|
|
|
"//disabled_rules": [
|
|
"Nudge rules that are not wired at all. Names come from loop.DefaultRules:",
|
|
"water, meal, break, service_down, netdata_critical.",
|
|
"service_down is back on: mavpoll now writes one fact per kuma monitor",
|
|
"(service_down:<name>), so the nudge names the service and pausing a monitor",
|
|
"in kuma silences that monitor. It is also edge-triggered, so a service that",
|
|
"stays down is one nudge, not one every fifteen minutes."
|
|
],
|
|
"disabled_rules": [],
|
|
|
|
"phraser": {
|
|
"model_path": "/opt/maven/models/llm/qwen3/Qwen3-1.7B-UD-Q4_K_XL.gguf",
|
|
"bin_path": "llama-server",
|
|
"n_gpu_layers": 99,
|
|
"n_ctx": 4096,
|
|
"cache_ram_mib": 512,
|
|
"timeout": "60s",
|
|
"llm_nudges": false
|
|
},
|
|
|
|
"telegram": {
|
|
"bot_token": "${TELEGRAM_BOT_TOKEN}",
|
|
"chat_id": "${TELEGRAM_CHAT_ID}",
|
|
|
|
"//proxy": [
|
|
"api.telegram.org is not reachable directly from this box, so every send",
|
|
"timed out. The relay is the x-ui socks inbound on the host, port 10808;",
|
|
"192.168.240.1 is the maven_default bridge gateway, which is how a",
|
|
"container addresses the host. mavend is on that network.",
|
|
"This needs a matching ufw rule or the container's SYN is dropped:",
|
|
" ufw allow from 192.168.240.0/20 to any port 10808 proto tcp"
|
|
],
|
|
"proxy": "socks5://192.168.240.1:10808",
|
|
"//intake": [
|
|
"Read the chat as well as write to it (V-637). The poller long-polls",
|
|
"getUpdates through the same relay and accepts chat_id as the only",
|
|
"sender. Deleting this key turns inbound off again.",
|
|
"chat_id must be numeric here or the daemon refuses to start: an inbound",
|
|
"update names its chat by number, so an @-name would match nothing."
|
|
],
|
|
"intake": true
|
|
},
|
|
|
|
"//workstation": [
|
|
"The big model on the desk PC (workpc, 7900 GRE 16GB), fronted by",
|
|
"mavgpud on port 8080. It runs gemma-4-12b and it is preferred over the",
|
|
"resident Qwen3-1.7B for routing and replies whenever the card is free.",
|
|
"The machine is never assumed up: it sleeps, and the card is often held by",
|
|
"a CPT run, in which case mavgpud answers 503 and Maven falls back to the",
|
|
"resident model without saying so. Deleting this block restores exactly",
|
|
"the behaviour homesrv had before it existed.",
|
|
"Addressed by LAN address, not container name: mavgpud runs on another",
|
|
"machine and there is no shared docker network to name it on."
|
|
],
|
|
"workstation": {
|
|
"url": "http://192.168.1.105:8080",
|
|
"probe": "15s",
|
|
"timeout": "90s"
|
|
},
|
|
|
|
"//search": [
|
|
"The live web, searched after his own notes and before Kiwix. Only the",
|
|
"query string leaves the box — never a note, a fact, the persona block or",
|
|
"the history — and a question about him never reaches here at all.",
|
|
"The instance must have `json` in search.formats (settings.yml); a stock",
|
|
"SearXNG answers 403 to format=json and every search then fails. It is",
|
|
"addressed by container name, so it needs the same maven_default",
|
|
"attachment kiwix has, and it must listen on 9563: 8080 is taken several",
|
|
"times over on this box. No instance reachable ⇒ she falls through to the",
|
|
"ZIMs and never says the search failed."
|
|
],
|
|
"search": {
|
|
"url": "http://searxng:9563",
|
|
"max_results": 4,
|
|
"snippet_runes": 1500,
|
|
"language": "auto",
|
|
"timeout": "8s"
|
|
},
|
|
|
|
"//kiwix": [
|
|
"The offline encyclopedia, searched after his own notes and before anything",
|
|
"on the network. kiwix-server publishes 8034 on loopback only, so a container",
|
|
"cannot reach it by address; it is attached to the maven_default network",
|
|
"instead and addressed by container name. That attachment is imperative and",
|
|
"does not survive recreating the kiwix stack — make it declarative there:",
|
|
" networks: [default, maven_default] # maven_default: external: true",
|
|
"The book is the catalog name from the /content/... href in",
|
|
"/catalog/v2/entries, not the display title. Others on the box:",
|
|
"ifixit_en_all_2025-06, devdocs_en_ansible_2025-10."
|
|
],
|
|
"kiwix": {
|
|
"url": "http://kiwix-server:8080",
|
|
"book": "wikipedia_en_all_maxi_2026-02",
|
|
"book_ru": "wikipedia_ru_all_maxi_2026-02",
|
|
"max_results": 5,
|
|
"snippet_runes": 1500
|
|
},
|
|
|
|
"//morning_routines": [
|
|
"The daily checklist (Vikunja #280). Each item is done when its fact_key",
|
|
"gets a non-voided fact inside the window, so 'выпил воды' closes water and",
|
|
"nothing has to be ticked by hand. nudge_at fires once, at the end of the",
|
|
"window, and only for what is still open. Weekdays empty = every day."
|
|
],
|
|
"morning_routines": [
|
|
{
|
|
"name": "утро",
|
|
"window_start": "08:00",
|
|
"window_end": "11:00",
|
|
"nudge_at": "10:30",
|
|
"severity": 1,
|
|
"items": [
|
|
{ "key": "medicine", "fact_key": "medicine", "label": "лекарство" },
|
|
{ "key": "water", "fact_key": "water", "label": "вода" },
|
|
{ "key": "pets", "fact_key": "pets", "label": "покормить кота" }
|
|
]
|
|
}
|
|
],
|
|
|
|
"//feeds": [
|
|
"RSS reading (Vikunja #258). Every item lands as a note with source",
|
|
"rss:<name>, which is also what puts entries in the intake journal that",
|
|
"/events reads. Only the feed URL leaves the box.",
|
|
"This is a starting pair, not a curated set — trim or extend it."
|
|
],
|
|
"feeds": {
|
|
"poll_interval": "30m",
|
|
"max_items": 5,
|
|
"max_age": "24h",
|
|
"sources": [
|
|
{ "name": "lwn", "url": "https://lwn.net/headlines/newrss", "category": "технологии" },
|
|
{ "name": "archlinux", "url": "https://archlinux.org/feeds/news/", "category": "технологии" }
|
|
]
|
|
},
|
|
|
|
"//crawl": [
|
|
"Reading a web page (Vikunja #259). on_demand answers 'посмотри <URL>'.",
|
|
"No allow_hosts, so any public host he names is readable; private",
|
|
"addresses are refused unconditionally by internal/webfetch and do not",
|
|
"need listing. Setting allow_hosts here would also narrow on-demand,",
|
|
"which is the point of leaving it empty."
|
|
],
|
|
"crawl": {
|
|
"on_demand": true,
|
|
"timeout": "10s",
|
|
"max_runes": 4000
|
|
},
|
|
|
|
"digest": {
|
|
"enabled": true,
|
|
"window": "30m",
|
|
"max_items": 5,
|
|
"severity_ceiling": 2
|
|
},
|
|
|
|
"pattern_proposals": {
|
|
"notify": false,
|
|
"cooldown": "24h"
|
|
},
|
|
|
|
"mcp": {
|
|
"timeout": "15s",
|
|
"servers": [
|
|
{
|
|
"name": "vikunja",
|
|
"url": "http://192.168.1.104:9100/mcp",
|
|
"allow_private": true,
|
|
"allow_tools": ["list_projects", "list_tasks", "get_task_details", "create_task"],
|
|
"max_tools": 6,
|
|
"enabled": false
|
|
}
|
|
]
|
|
},
|
|
|
|
"smarthome": {
|
|
"provider": "homeassistant",
|
|
"url": "http://192.168.1.50:8123",
|
|
"token": "${HA_TOKEN}",
|
|
"domains": ["light", "switch", "sensor"],
|
|
"max_entities": 40,
|
|
"timeout": "10s",
|
|
"refresh": "15m",
|
|
"enabled": false
|
|
},
|
|
|
|
"netscan": {
|
|
"subnets": ["192.168.1.0/24"],
|
|
"ports": [22, 80, 443, 8080],
|
|
"timeout": "400ms",
|
|
"rate": 100,
|
|
"max_hosts": 256,
|
|
"enabled": true
|
|
},
|
|
|
|
"nexus": { "url": "http://nexus:9740" },
|
|
"praxis": { "url": "http://praxis:8989" },
|
|
"hexis": { "url": "http://hexis:9741" },
|
|
|
|
"voice": {
|
|
"enabled": true,
|
|
"bind": "0.0.0.0:9100",
|
|
"lang": "ru",
|
|
"stt": { "socket": "/run/maven/stt.sock", "lang": "ru" },
|
|
"tts": { "socket": "/run/maven/tts.sock", "lang": "ru" },
|
|
"embedder": {
|
|
"model_path": "/opt/maven/models/embedder/multilingual-e5-small/model_quantized.onnx",
|
|
"tokenizer_path": "/opt/maven/models/embedder/multilingual-e5-small/tokenizer.json",
|
|
"lib_path": "/opt/maven/lib/libonnxruntime.so"
|
|
},
|
|
"llm_router": true,
|
|
"query_min_score": 0.55,
|
|
"query_min_margin": 0.008,
|
|
"clarify_max_attempts": 3,
|
|
"tool_timeout": "30s",
|
|
"tools": [
|
|
{ "name": "status", "cmd": ["systemctl", "status"], "scope": "homelab", "destructive": false,
|
|
"aliases": ["статус", "покажи статус", "проверь статус"] },
|
|
{ "name": "ps", "cmd": ["docker", "ps"], "scope": "homelab", "destructive": false,
|
|
"aliases": ["статус докера", "лог докера", "покажи запущенные контейнеры", "покажи контейнеры", "список контейнеров", "что запущено"] },
|
|
{ "name": "uptime", "cmd": ["uptime"], "scope": "homelab", "destructive": false,
|
|
"aliases": ["покажи uptime", "аптайм", "как работает сервер", "сколько работает сервер"] },
|
|
{ "name": "disk", "cmd": ["df", "-h"], "scope": "homelab", "destructive": false,
|
|
"aliases": ["сколько места на диске", "сколько свободного места на диске", "место на диске", "покажи диск"] },
|
|
{ "name": "memory", "cmd": ["free", "-h"], "scope": "homelab", "destructive": false,
|
|
"aliases": ["свободная память", "сколько оперативной памяти свободно", "покажи память"] },
|
|
{ "name": "logs", "cmd": ["journalctl", "-n", "50", "-u"], "scope": "homelab", "destructive": false,
|
|
"aliases": ["покажи логи", "логи", "лог"] },
|
|
{ "name": "restart", "cmd": ["systemctl", "restart"], "scope": "homelab", "destructive": true,
|
|
"aliases": ["перезапусти", "перезагрузи", "рестарт"] },
|
|
{ "name": "stop", "cmd": ["systemctl", "stop"], "scope": "homelab", "destructive": true,
|
|
"aliases": ["останови", "останови сервис"] },
|
|
{ "name": "start", "cmd": ["systemctl", "start"], "scope": "homelab", "destructive": true,
|
|
"aliases": ["запусти", "запусти сервис"] },
|
|
{ "name": "docker-restart", "cmd": ["docker", "restart"], "scope": "homelab", "destructive": true,
|
|
"aliases": ["перезапусти контейнер", "перезагрузи контейнер"] },
|
|
{ "name": "docker-stop", "cmd": ["docker", "stop"], "scope": "homelab", "destructive": true,
|
|
"aliases": ["останови контейнер"] },
|
|
{ "name": "reboot", "cmd": ["systemctl", "reboot"], "scope": "homelab", "destructive": true,
|
|
"aliases": ["перезагрузи сервер", "перезагрузи хост"] }
|
|
]
|
|
}
|
|
}
|