A live restart of the system bus during an upgrade hung every login on a workstation until a reboot. The module owns the bus's packages, declares the bus running with no restart or reload trigger, publishes only curated events (health, services, denials; never traffic) and serves tools to look at both buses.
7.7 KiB
dbus
A machine's message bus (novox/hq ADR 0215). This module holds node-message-bus on every machine,
servers included, because every machine runs a D-Bus system bus. It owns the bus implementation's
packages and declares its system service running. It publishes what matters about the bus on the
mesh's bus, and serves tools to look at the system bus and at the operator's session bus.
What it owns
package dbus-broker |
the bus every machine runs |
package dbus-broker-units |
dbus.service as an alias of dbus-broker.service, for the system and for each user |
package dbus |
the bus's configuration (system.conf, session.conf), dbus.socket, which starts the bus at boot, and libdbus |
service dbus-broker.service |
declared running, with no boot state and no restart or reload trigger (below) |
All four machines were found with dbus-broker 37 behind dbus.service and dbus 1.16.2, from the
distribution. One server also has an old dbus-units package, an empty package that depends on
dbus-broker-units. It is not declared: nothing needs it, and removing it is a choice for its
operator.
The bus is never restarted live
On 2026-10-04 a full upgrade on a workstation restarted the system bus while the upgrade was still running. From then on every login hung, sshd answered nothing and the machine's host stopped reporting, until someone rebooted it at its keyboard. Every program that speaks on the bus (the service manager, logind, the network manager, the keyring, the power module's sleep lock) holds a connection to it. A restart takes all of them away at once, and not all of them come back.
So the module never restarts or reloads the bus, for any change. Its service has no restart-on and
no reload-on, and the host never restarts a service that has neither. A new bus takes effect at
the next boot. dbus_check says when the running bus is older than an installed package, which is
the sign that a reboot is due. The distribution's own upgrade can still restart the bus. This rule
covers what the mesh does, not what pacman does.
Why state: running and no boot. dbus.service is an alias (systemctl is-enabled says
alias), so it cannot be declared enabled: the host would run systemctl enable and read back
something other than enabled. The real unit, dbus-broker.service, reads disabled on three
machines and enabled on one. It needs no enabling. At boot dbus.socket, which the dbus package
links into sockets.target, pulls in dbus.service, and dbus-broker-units ships that name as a
link to dbus-broker.service. On the three machines where it reads disabled, enabling it would
only write a second alias link into /etc, and a module's apply would change something on a running
machine for no gain. The service is declared so that the mesh knows the bus is the module's and says
so when it is not running. A host finding it stopped would start it, which can only help a machine
whose bus is down. ADR 0215 §1 says "running and enabled". On these machines the package's own
socket link is what makes it start at boot.
A policy change a module needs in the future uses the bus's own reload (dbus-broker.service is
Type=notify-reload), which keeps every connection. No module needs one today (below).
Events
The watcher runs beside the tools in the same process, on its own connection to the system bus as the
operator's account. It reads only the bus driver's answers, the driver's NameOwnerChanged signal and
the bus unit's journal.
| event | when | carries |
|---|---|---|
bus.stalled |
the bus does not answer the driver's GetId within 3 s, or cannot be reached |
the reason |
bus.recovered |
a stalled bus answers again | since when it was stalled, and for how long |
bus.restarted |
after reconnecting, the bus's id or the driver's pid has changed within the same boot (after a boot both change, and power says booted) |
the old and new id and pid, the unit |
service.appeared |
a well-known name appears and is still there 10 s later | the name, the owner's pid, process and unit, whether it is activatable |
service.left |
a well-known name is gone and still gone 10 s later | the name and what held it |
policy.denied |
the bus logged a policy denial, at most once per 5 minutes | how many since the last one, and up to 5 distinct examples: the refused message's type, sender, destination, path, interface and member |
Unique names (:1.42) come and go with every client and are never published. A service restarted
within the 10 s, or one that came and went, publishes nothing. Activatable services that exit when
idle, such as hostnamed, do appear and leave, and their events say activatable: true.
Why traffic never leaves the machine. The bus carries secrets from the keyring, notification text and the clipboard. The watcher never becomes a monitor, so it never sees another peer's messages. Its events are built from names, pids, units and the header fields the broker logs with a denial. The log line itself is not passed on. The tests hold every event's body to those fields.
Events the mesh's bus does not take wait in order and go out when it answers again, as power's do.
At most 1000 events wait. When there are more, the oldest are dropped and counted.
The watcher's ping is GetId, not org.freedesktop.DBus.Peer.Ping. The system policy refuses the
peer ping to an account that is not root and logs a denial each time, which the watcher would then
publish.
Tools
| tool | |
|---|---|
dbus_names |
bus: system or session. Every well-known name with its owner's pid, process, user and unit, the activatable names that are not running, and the number of connections |
dbus_introspect |
bus, service, path. The object's interfaces with their methods, properties (type and access, never values) and signals, and its children. Uses --auto-start=no, so looking never starts a service |
dbus_monitor |
bus, seconds (at most 15), optional match rule and names. The headers of what passed (type, sender, destination, path, interface, member, error name), at most 500. Never a body: each line is decoded into the header alone. On the system bus only root may monitor, so it runs through sudo -n |
dbus_check |
the packages are installed. dbus.service is dbus-broker.service and active. The running bus is not older than the installed dbus-broker or dbus (otherwise a reboot is due). Every activatable service file's SystemdService exists. Policy denials in the last hour. The watcher is connected, not stalled, and has nothing stuck |
dbus_health |
the round trip of a ping on the watcher's connection, the connection count (from Debug.Stats through sudo -n, else the unique names), and the bus's unit, pid, start and uptime |
Every command is bounded at 20 s and its output read up to 1 MiB. Lists stop at 500 entries.
Debug.Stats is asked only as root: asked as the account it is refused and logged as a denial.
The session bus is the runtime's DBUS_SESSION_BUS_ADDRESS, else $XDG_RUNTIME_DIR/bus, else
/run/user/<uid>/bus. On a machine where the account has no session, the tools say so.
The check notes, without failing, systemd's own services whose dbus-org.* alias is missing because
they are not enabled, such as resolved, networkd and homed on the workstations. Activating them fails
by design.
Assignment
On every machine, as the first holder of node-message-bus. The module needs nothing set: no
settings, no secrets, no ports.
What comes later
ADR 0215 §4: the seat receives nothing yet. Packages ship their own D-Bus policy and service files, and no module writes one of its own. When one does, that file will be a contribution to this seat, and this module will list the kind and reload the bus's policy for it, without a restart.