Bind local NATS server to 127.0.0.1 instead of all interfaces
nats-server defaults to binding 0.0.0.0, and the systemd unit didn't
override that - meaning any host that can reach munin's port 4222 could
publish/subscribe to the bot's classifier exchange or JetStream streams
with zero authentication configured. It's only ever used by processes on
the same host (nats_uri=localhost), so restrict it to loopback.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Connects to a real room as a separate test account, addresses the bot by
nick with a few deterministic commands (ping/version/dice), and checks the
responses match expected patterns - a black-box way to confirm a deployed
instance is actually working end to end, not just that its systemd units
are active. Not part of the pytest suite; needs a real XMPP server/room/bot.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fix NATS reconnect handling: retry forever instead of giving up after ~2min
nats-py's default max_reconnect_attempts (60 * 2s) meant a long-enough NATS
outage would leave worker.py's per-plugin consumer threads permanently
stuck with no recovery path - unlike the AsyncBroker path used by
action_worker.py, they had no closed_cb to notice and react to a permanent
disconnect. Setting max_reconnect_attempts=-1 makes nats-py retry
indefinitely instead; confirmed via a local test that a plugin thread
survives a NATS restart and resumes receiving messages afterward without
any restart of its own. Also bumps connect_timeout from nats-py's 2s
default to 10s, since munin (a Pi Zero-class host) observed transient
connect timeouts when ~40 plugin threads dialed in simultaneously at
startup under the tighter default.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add NATS/JetStream as an alternative broker to RabbitMQ
RabbitMQ's Erlang VM baseline costs ~300-500MB RES regardless of actual
traffic, which is wasteful for a small hobby-scale chatbot. Introduces a
runtime toggle (broker_backend = amqp | nats in local_config.ini) so a
deployment can opt into a NATS+JetStream backend instead, while defaulting
to amqp so existing deployments are unaffected.
- New common/broker.py abstraction (SyncBroker/AsyncBroker) with
broker_amqp.py (thin, behavior-preserving wrapper around the existing
pika calls) and broker_nats.py backends.
- Binding-key wildcard matching (some patterns, e.g. fun.py's mid-pattern
"#", have no native NATS subject-algebra equivalent) is handled by
subscribing broadly and filtering in-process via common/routing.py,
lifted from the AMQP-routing simulator already used in integration tests.
- action_processing and plugin_registry go through JetStream for
durability/late-subscriber buffering, matching today's AMQP guarantees.
- Ansible role: broker_backend/nats_uri toggle, optional local NATS server
provisioning (nats_local), wait-for-broker.sh picked conditionally.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fix Choose plugin crash when mampfdb.json is missing
open() raised an uncaught FileNotFoundError at class-definition time,
which is never caught by Worker.callback and crashed the whole worker
process on any deployment that hadn't manually placed the file (e.g.
munin, a fresh clone). Also ship mampfdb.json itself so the food-choice
feature works instead of silently falling back to an empty dict.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add XEP-0410 MUC self-ping to auto-rejoin after silent eviction
A c2s reconnect re-fires session_start and rejoins the rooms, but a
silent MUC eviction (e.g. an s2s flap when the account server restarts)
never touches the c2s stream: the bot stays online and JID-pingable yet
quietly drops out of the room with no way to notice.
Periodically ping our own occupant JID and rejoin on not-acceptable /
item-not-found; treat every other error (and timeouts) as inconclusive
so a flaky s2s link can't cause a rejoin storm.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>