Skip to content

systemctl_action issues a bare systemctl --user, unlike build_unit_state_command right beside it #285

Description

@karoltheguy

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:

  1. 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).
  2. Start, stop, restart, or save (daemon-reload + restart) a user-scope quadlet
    from the UI.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions