Read-only status integration
Enabling it
EJABBERD_ENABLED = True
EJABBERD_HOST = "localhost" # or a HUB_HOSTS name -- see "One shared server" below
EJABBERD_DOMAIN = "your-xmpp-domain" # the vhost ejabberd serves, e.g. "pyobs.example.org"
EJABBERD_API_URL = "http://127.0.0.1:5281/api"
A module’s XMPP identity is its comm.user config key, resolved the same way this app
already resolves acl: blocks (so it works whether comm: is defined locally, via
{include}, or via a YAML anchor merge key). A module with no comm: block at all
(e.g. a pure HTTP module) is skipped entirely everywhere below – there’s nothing for it to
connect to, so there’s no “should be connected but isn’t” mismatch worth surfacing for it.
Where it surfaces
Dashboard – a summary tile (connected / registered counts, node status as a tooltip) alongside the existing Total/Running/Stopped/RAM/CPU tiles, plus a small filled-green “connected” / outlined-amber “not connected” icon per module row. The tile’s denominator is this installation’s own modules that have a
comm_userand aren’t deactivated – not ejabberd’s fleet-wide registered-account count, which can include unrelated accounts (admin, other roles) that have nothing to do with any module here.Module detail page (Overview tab) – connected-since/IP/connection type if live, or last-seen (with the actual disconnect reason, if any) if not, or “not a registered account” if
comm.userdoesn’t correspond to a real ejabberd account at all.
Both are gated on the module’s own running status: a stopped module never shows a
“connected” state, even if another module happens to share its comm.user and is
currently connected under that identity (a real, supported configuration – e.g. a _test
copy of a module reusing a real module’s identity for testing).
Data layer
The primary mechanism is ejabberd’s HTTP admin API, mod_http_api – not ejabberdctl
subprocess calls – since it’s roughly 50-60x faster per call (hits the already-running
node directly instead of booting a fresh Erlang VM per invocation). ejabberdctl is kept
as a documented fallback for hosts that haven’t done the ejabberd-side setup below yet.
Command |
Returns |
Used for |
|---|---|---|
|
Node status string |
Dashboard: is the XMPP backbone itself healthy |
|
A bare integer (registered/online users, uptime) |
Dashboard summary tile |
|
List of connected sessions (JID, IP, connection type, …) |
Cross-referencing against modules for the “connected” indicator |
|
List of registered account names |
Sanity-checking |
|
Same shape as one |
Module page: is this module’s identity connected, since when, from where |
|
Last-seen timestamp + status, or a disconnect reason, or “not found” |
Module page: “last connected 3h ago (stream reset by peer)” |
|
Whether an account is registered at all |
Module page: flag a |
ejabberd-side configuration
Add an HTTP listener with mod_http_api, and a permissions grant limited to the
read-only commands above:
listen:
-
port: 5281
ip: "127.0.0.1" # loopback only -- see security note below
module: ejabberd_http
request_handlers:
/api: mod_http_api # add to an *existing* listener's request_handlers if one's
# already on this port -- ejabberd allows one listener per port
modules:
mod_http_api: {}
api_permissions:
"console commands":
from: [ejabberd_ctl]
who: all
what: "*"
"pyobs-web-admin readonly":
from: [mod_http_api]
who:
access:
allow:
- acl: loopback
what:
- "status"
- "stats"
- "connected_users_info"
- "registered_users"
- "user_sessions_info"
- "get_last"
- "check_account"
Reload ejabberd’s config afterward (ejabberdctl reload_config, or a restart if that
doesn’t pick up a new listener). The what: list is a deliberate whitelist – leave it
as-is; mod_http_api can also expose account-management commands
(register/unregister/change_password) that must never be reachable through this
read-only grant (see XMPP user management for how those are handled instead).
Security model
Access is IP-based, not credential-based – the only gate is acl: loopback in
api_permissions; there’s no username/password or bearer-token layer on this endpoint at
all. This is a deliberate, tested choice, not an oversight: both an OAuth bearer token and
HTTP Basic Auth were tried and abandoned (OAuth failed with a missing dependency in one
tested ejabberd build; Basic Auth kept returning an unrelated authorization error despite
valid credentials). What this does and doesn’t protect against:
Does protect against any request arriving from outside the machine, over the network – confirmed by testing from a real non-loopback address, which was rejected even for commands in the explicit whitelist.
Does not protect against any other local process or user account on the same machine – the ACL can’t distinguish “pyobs-web-admin specifically” from “anything else on this box that can reach 127.0.0.1:5281.” This is an accepted tradeoff for a dedicated, single-purpose observatory control host, not appropriate for a shared or multi-tenant one.