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.
112 lines
7.7 KiB
Markdown
112 lines
7.7 KiB
Markdown
# 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.
|