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
62 lines
2.3 KiB
Go
62 lines
2.3 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
// TestDeployConfigLoads parses the file the box actually runs on.
|
|
//
|
|
// Every other test in this package builds its own JSON, so a key renamed in one
|
|
// place and not the other would go unnoticed until the daemon refused to start.
|
|
// This one reads deploy/mavend.json through the same Load the daemon calls, so
|
|
// a config change and a code change have to agree here or the suite is red.
|
|
//
|
|
// The ${VAR} expansions come from a gitignored deploy/telegram.env that is not
|
|
// present in CI. An unset var expands to the empty string, which is exactly the
|
|
// "not configured" state every block already has to handle, so the parse is
|
|
// still meaningful without the secrets.
|
|
func TestDeployConfigLoads(t *testing.T) {
|
|
path := filepath.Join("..", "..", "deploy", "mavend.json")
|
|
if _, err := os.Stat(path); err != nil {
|
|
t.Skipf("no deploy config at %s: %v", path, err)
|
|
}
|
|
cfg, err := Load(path)
|
|
if err != nil {
|
|
t.Fatalf("Load(%s): %v", path, err)
|
|
}
|
|
|
|
// Spot-check the settings whose absence would be a silent behaviour change
|
|
// rather than a startup error.
|
|
if cfg.Phraser == nil {
|
|
t.Fatal("deploy config has no phraser block")
|
|
}
|
|
if cfg.Phraser.NGpuLayers == 0 {
|
|
t.Error("phraser.n_gpu_layers is 0 — llama-server would run CPU-only, " +
|
|
"because nothing in this package defaults that field")
|
|
}
|
|
if cfg.Voice == nil || !cfg.Voice.Enabled {
|
|
t.Fatal("deploy config does not enable voice")
|
|
}
|
|
if !cfg.Voice.UseLLMRouter() {
|
|
t.Error("deploy config turned the LLM router off")
|
|
}
|
|
if cfg.Voice.RouterThreshold <= 0 {
|
|
t.Error("router threshold did not get its default")
|
|
}
|
|
|
|
// The second reach (V-649). Deleting this block is how you turn ntfy off,
|
|
// so its absence has to be loud: sev3-away nudges and away reminders route
|
|
// to ntfy and to nothing else, and a nil sink drops them with no log and no
|
|
// outbox row. The token is a ${VAR} that CI cannot resolve, so this checks
|
|
// the wiring and not the credential.
|
|
if cfg.Ntfy == nil {
|
|
t.Fatal("deploy config has no ntfy block — sev3-away and away reminders " +
|
|
"would have nowhere to land, and would vanish silently rather than fail")
|
|
}
|
|
if cfg.Ntfy.BaseURL == "" || cfg.Ntfy.Topic == "" {
|
|
t.Errorf("ntfy block is incomplete: base_url=%q topic=%q", cfg.Ntfy.BaseURL, cfg.Ntfy.Topic)
|
|
}
|
|
}
|