From baa5ce3e1a420658f65634e38006c526df851c6d Mon Sep 17 00:00:00 2001 From: damocles Date: Wed, 24 Jun 2026 00:06:07 +0200 Subject: [PATCH] gui: one shared dbus session bus for the gui session (#1906) --- docs/gotchas.md | 13 +++++++++ nix/templates/weston-vnc.nix | 54 ++++++++++++++++++++++++++++++++---- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/docs/gotchas.md b/docs/gotchas.md index 45271765..b5fdb56d 100644 --- a/docs/gotchas.md +++ b/docs/gotchas.md @@ -270,6 +270,19 @@ connects to the compositor at `127.0.0.1:`. wayland client sharing the `/run/gui/wayland-0` socket). Wayland clients in the agent's config (e.g. bitburner electron) must run as the same user with `XDG_RUNTIME_DIR=/run/gui`. +- **One shared D-Bus session bus (`gui-dbus.service`)**: a single + persistent `dbus-daemon --session` bound at `/run/gui/bus`, run as the + agent user, ordered `before weston.service` (it shares the same + `RuntimeDirectory=gui`, creating the dir first). Chromium/electron via + ozone refuse to map an `xdg_toplevel` without a reachable session bus + ("Failed to connect to the bus" → binds `xdg_wm_base` then destroys it + = invisible window even though CDP works). The fix is **not** to wrap + each client in its own `dbus-run-session` (a private throwaway bus per + process — that's a *separate* session, defeating the one-session + model); it's this one shared bus, whose address is exported as + `DBUS_SESSION_BUS_ADDRESS=unix:path=/run/gui/bus` via + `systemd.globalEnvironment` so weston, the harness and every GUI client + inherit it. - **Fixed Wayland socket name (`--socket=wayland-0`)**: weston is launched with `--socket=wayland-0` so the socket path is deterministic. `harness-base.nix` exports `WAYLAND_DISPLAY=wayland-0` diff --git a/nix/templates/weston-vnc.nix b/nix/templates/weston-vnc.nix index bd68cdf6..909bdf08 100644 --- a/nix/templates/weston-vnc.nix +++ b/nix/templates/weston-vnc.nix @@ -55,9 +55,11 @@ in `Type = "simple"` so a misconfigured weston degrades to a restart loop instead of blocking `nixos-container update`. - Weston and the wayland client run as the agent's own non-root - user (`hyperhive.user.name`), sharing one user session with a - fixed `XDG_RUNTIME_DIR=/run/gui`. + Weston, the wayland client and the agent harness run as the + agent's own non-root user (`hyperhive.user.name`), sharing one + session: a fixed `XDG_RUNTIME_DIR=/run/gui`, one wayland display, + and one D-Bus session bus at `/run/gui/bus` (gui-dbus.service), + so GUI clients need no private `dbus-run-session`. ''; }; @@ -100,9 +102,48 @@ in session sufficient pam_permit.so ''; + # One shared D-Bus *session* bus for the whole GUI session, bound at + # /run/gui/bus and owned by the agent user. Wayland GUI clients + # (chromium / electron via ozone) refuse to map a toplevel without a + # reachable session bus ("Failed to connect to the bus" -> binds + # xdg_wm_base then destroys it = invisible window, though CDP still + # works). Running ONE persistent bus here -- instead of each client + # wrapping itself in `dbus-run-session` (a private throwaway bus per + # process) -- keeps weston, the agent harness and the GUI client in a + # single session: one user, one XDG_RUNTIME_DIR, one wayland display, + # one bus. The address is exported via globalEnvironment below so + # every unit in the container inherits it. + systemd.services.gui-dbus = { + description = "Shared D-Bus session bus for the GUI session"; + wantedBy = [ "multi-user.target" ]; + before = [ "weston.service" ]; + serviceConfig = { + Type = "simple"; + User = userName; + Group = userName; + # Share /run/gui with weston; ordered `before` weston so this + # creates + chowns the dir first. Preserve across restarts so + # weston + clients don't lose the dir holding their sockets. + RuntimeDirectory = "gui"; + RuntimeDirectoryMode = "0700"; + RuntimeDirectoryPreserve = "yes"; + Environment = "XDG_RUNTIME_DIR=/run/gui"; + # dbus-daemon unlinks a stale path socket before binding, so a + # restart re-binds /run/gui/bus cleanly. + ExecStart = "${pkgs.dbus}/bin/dbus-daemon --session --nofork --nopidfile --address=unix:path=/run/gui/bus"; + SyslogIdentifier = "gui-dbus"; + Restart = "on-failure"; + RestartSec = "2s"; + }; + }; + systemd.services.weston = { description = "Weston Wayland compositor (VNC backend)"; - after = [ "network.target" ]; + after = [ + "network.target" + "gui-dbus.service" + ]; + wants = [ "gui-dbus.service" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "simple"; @@ -155,10 +196,13 @@ in # to systemd service units. Without these vars a service starting # a Wayland client would either fail to connect (libwayland falls # back to a headless display) or open a second compositor — VNC - # shows a blank desktop. + # shows a blank desktop. DBUS_SESSION_BUS_ADDRESS points every unit + # at the one shared session bus (gui-dbus.service above) so GUI + # clients use it instead of spawning a private `dbus-run-session`. systemd.globalEnvironment = { WAYLAND_DISPLAY = "wayland-0"; XDG_RUNTIME_DIR = "/run/gui"; + DBUS_SESSION_BUS_ADDRESS = "unix:path=/run/gui/bus"; }; # weston on the agent's interactive PATH so claude can run Wayland