Overview
Two code paths in services/systemd_manager.py disagree about whose job it is to
supply XDG_RUNTIME_DIR for rootless systemctl --user over a non-interactive SSH
session. build_unit_state_command supplies it explicitly; systemctl_action does
not, and silently relies on the session already having it.
Details
Use Case / Steps:
- Connect to a host whose sshd session does not register a logind session (no
pam_systemd, so no XDG_RUNTIME_DIR is exported into the environment).
- Start, stop, restart, or save (daemon-reload + restart) a user-scope quadlet
from the UI.
- The command fails with
Failed to connect to user scope bus via local transport: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not defined.
Expected vs Actual:
- Expected: every rootless
systemctl --user this app issues makes the same
assumption about its environment, and states it in one place.
- Actual: unit-state polling works while every lifecycle action fails, on the
same host, over the same connection.
Details:
services/systemd_manager.py:18 defines
ROOTLESS_ENV_PREFIX = 'XDG_RUNTIME_DIR=/run/user/$(id -u)', with a comment
explaining that rootless systemctl --user over non-interactive SSH needs it.
:42, in build_unit_state_command, prefixes it onto the systemctl --user show
it builds.
:127, in systemctl_action, sets cmd_prefix = "systemctl --user" with no
prefix at all. That covers start, stop, restart, status and
daemon-reload.
This was proven live rather than inferred. During #284's CI rounds, one image
configuration left sshd without pam_systemd. In that run
test_rootless_session_is_live passed, because it builds the prefix itself,
while all 17 tests going through systemctl_action errored on exactly the message
above.
Scope:
Not urgent: a normal host runs pam_systemd, exports the variable, and both paths
work. The defect is that one module holds two different assumptions, so the
environment contract is only satisfied by luck. The narrow fix is to have
systemctl_action use ROOTLESS_ENV_PREFIX when scope == 'user', which makes the
two paths agree and costs nothing on a host that already sets the variable.
Found by the podman suite in #284. Recorded in docs/TESTING_TODO.md.
Overview
Two code paths in
services/systemd_manager.pydisagree about whose job it is tosupply
XDG_RUNTIME_DIRfor rootlesssystemctl --userover a non-interactive SSHsession.
build_unit_state_commandsupplies it explicitly;systemctl_actiondoesnot, and silently relies on the session already having it.
Details
Use Case / Steps:
pam_systemd, so noXDG_RUNTIME_DIRis exported into the environment).from the UI.
Failed to connect to user scope bus via local transport: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not defined.Expected vs Actual:
systemctl --userthis app issues makes the sameassumption about its environment, and states it in one place.
same host, over the same connection.
Details:
services/systemd_manager.py:18definesROOTLESS_ENV_PREFIX = 'XDG_RUNTIME_DIR=/run/user/$(id -u)', with a commentexplaining that rootless
systemctl --userover non-interactive SSH needs it.:42, inbuild_unit_state_command, prefixes it onto thesystemctl --user showit builds.
:127, insystemctl_action, setscmd_prefix = "systemctl --user"with noprefix at all. That covers
start,stop,restart,statusanddaemon-reload.This was proven live rather than inferred. During #284's CI rounds, one image
configuration left sshd without
pam_systemd. In that runtest_rootless_session_is_livepassed, because it builds the prefix itself,while all 17 tests going through
systemctl_actionerrored on exactly the messageabove.
Scope:
Not urgent: a normal host runs
pam_systemd, exports the variable, and both pathswork. The defect is that one module holds two different assumptions, so the
environment contract is only satisfied by luck. The narrow fix is to have
systemctl_actionuseROOTLESS_ENV_PREFIXwhenscope == 'user', which makes thetwo paths agree and costs nothing on a host that already sets the variable.
Found by the podman suite in #284. Recorded in
docs/TESTING_TODO.md.