[verified] fix(gateway): accept user systemd private socket during preflight
This commit is contained in:
parent
df0e97a168
commit
37d107e03d
2 changed files with 68 additions and 12 deletions
|
|
@ -830,6 +830,22 @@ def _user_dbus_socket_path() -> Path:
|
|||
return Path(xdg) / "bus"
|
||||
|
||||
|
||||
def _user_systemd_private_socket_path() -> Path:
|
||||
"""Return the per-user systemd private socket path (regardless of existence)."""
|
||||
xdg = os.environ.get("XDG_RUNTIME_DIR") or f"/run/user/{os.getuid()}"
|
||||
return Path(xdg) / "systemd" / "private"
|
||||
|
||||
|
||||
def _user_systemd_socket_ready() -> bool:
|
||||
"""Return True when user-scope systemd has a reachable control socket.
|
||||
|
||||
Some distros expose only the per-user systemd private socket even when the
|
||||
D-Bus session bus socket is absent. ``systemctl --user`` can still work in
|
||||
that configuration, so preflight checks must treat either socket as valid.
|
||||
"""
|
||||
return _user_dbus_socket_path().exists() or _user_systemd_private_socket_path().exists()
|
||||
|
||||
|
||||
def _ensure_user_systemd_env() -> None:
|
||||
"""Ensure DBUS_SESSION_BUS_ADDRESS and XDG_RUNTIME_DIR are set for systemctl --user.
|
||||
|
||||
|
|
@ -853,28 +869,29 @@ def _ensure_user_systemd_env() -> None:
|
|||
|
||||
|
||||
def _wait_for_user_dbus_socket(timeout: float = 3.0) -> bool:
|
||||
"""Poll for the user D-Bus socket to appear, up to ``timeout`` seconds.
|
||||
"""Poll for the user systemd runtime socket(s), up to ``timeout`` seconds.
|
||||
|
||||
Linger-enabled user@.service can take a second or two to spawn the socket
|
||||
after ``loginctl enable-linger`` runs. Returns True once the socket exists.
|
||||
Linger-enabled user@.service can take a second or two to spawn its control
|
||||
socket(s) after ``loginctl enable-linger`` runs. Returns True once either
|
||||
the user D-Bus socket or the per-user systemd private socket exists.
|
||||
"""
|
||||
import time
|
||||
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
if _user_dbus_socket_path().exists():
|
||||
if _user_systemd_socket_ready():
|
||||
_ensure_user_systemd_env()
|
||||
return True
|
||||
time.sleep(0.2)
|
||||
return _user_dbus_socket_path().exists()
|
||||
return _user_systemd_socket_ready()
|
||||
|
||||
|
||||
def _preflight_user_systemd(*, auto_enable_linger: bool = True) -> None:
|
||||
"""Ensure ``systemctl --user`` will reach the user D-Bus session bus.
|
||||
"""Ensure ``systemctl --user`` will reach the user-scope systemd instance.
|
||||
|
||||
No-op when the bus socket is already there (the common case on desktops
|
||||
and linger-enabled servers). On fresh SSH sessions where the socket is
|
||||
missing:
|
||||
No-op when the user D-Bus socket or per-user systemd private socket is
|
||||
already there (the common case on desktops and linger-enabled servers). On
|
||||
fresh SSH sessions where both are missing:
|
||||
|
||||
* If linger is already enabled, wait briefly for user@.service to spawn
|
||||
the socket.
|
||||
|
|
@ -888,8 +905,7 @@ def _preflight_user_systemd(*, auto_enable_linger: bool = True) -> None:
|
|||
systemd operations and surface the message to the user.
|
||||
"""
|
||||
_ensure_user_systemd_env()
|
||||
bus_path = _user_dbus_socket_path()
|
||||
if bus_path.exists():
|
||||
if _user_systemd_socket_ready():
|
||||
return
|
||||
|
||||
import getpass
|
||||
|
|
@ -903,7 +919,7 @@ def _preflight_user_systemd(*, auto_enable_linger: bool = True) -> None:
|
|||
# Linger is on but socket still missing — unusual; fall through to error.
|
||||
_raise_user_systemd_unavailable(
|
||||
username,
|
||||
reason="User D-Bus socket is missing even though linger is enabled.",
|
||||
reason="User systemd control sockets are missing even though linger is enabled.",
|
||||
fix_hint=(
|
||||
f" systemctl start user@{os.getuid()}.service\n"
|
||||
" (may require sudo; try again after the command succeeds)"
|
||||
|
|
|
|||
|
|
@ -14,6 +14,26 @@ from gateway.restart import (
|
|||
)
|
||||
|
||||
|
||||
class TestUserSystemdPrivateSocketPreflight:
|
||||
def test_preflight_accepts_private_socket_without_dbus_bus(self, monkeypatch):
|
||||
monkeypatch.setattr(gateway_cli, "_ensure_user_systemd_env", lambda: None)
|
||||
monkeypatch.setattr(gateway_cli, "_user_dbus_socket_path", lambda: Path("/tmp/missing-bus"))
|
||||
monkeypatch.setattr(gateway_cli, "_user_systemd_private_socket_path", lambda: Path("/tmp/private-socket"))
|
||||
monkeypatch.setattr(Path, "exists", lambda self: str(self) == "/tmp/private-socket")
|
||||
|
||||
gateway_cli._preflight_user_systemd(auto_enable_linger=False)
|
||||
|
||||
def test_wait_for_user_dbus_socket_accepts_private_socket(self, monkeypatch):
|
||||
calls = []
|
||||
monkeypatch.setattr(gateway_cli, "_ensure_user_systemd_env", lambda: calls.append("env"))
|
||||
monkeypatch.setattr(gateway_cli, "_user_dbus_socket_path", lambda: Path("/tmp/missing-bus"))
|
||||
monkeypatch.setattr(gateway_cli, "_user_systemd_private_socket_path", lambda: Path("/tmp/private-socket"))
|
||||
monkeypatch.setattr(Path, "exists", lambda self: str(self) == "/tmp/private-socket")
|
||||
|
||||
assert gateway_cli._wait_for_user_dbus_socket(timeout=0.1) is True
|
||||
assert calls == ["env"]
|
||||
|
||||
|
||||
class TestSystemdServiceRefresh:
|
||||
def test_systemd_install_repairs_outdated_unit_without_force(self, tmp_path, monkeypatch):
|
||||
unit_path = tmp_path / "hermes-gateway.service"
|
||||
|
|
@ -1105,6 +1125,10 @@ class TestPreflightUserSystemd:
|
|||
gateway_cli, "_user_dbus_socket_path",
|
||||
lambda: type("P", (), {"exists": lambda self: True})(),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
gateway_cli, "_user_systemd_private_socket_path",
|
||||
lambda: type("P", (), {"exists": lambda self: False})(),
|
||||
)
|
||||
# Should not raise, no subprocess calls needed.
|
||||
gateway_cli._preflight_user_systemd()
|
||||
|
||||
|
|
@ -1114,6 +1138,10 @@ class TestPreflightUserSystemd:
|
|||
gateway_cli, "_user_dbus_socket_path",
|
||||
lambda: type("P", (), {"exists": lambda self: False})(),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
gateway_cli, "_user_systemd_private_socket_path",
|
||||
lambda: type("P", (), {"exists": lambda self: False})(),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
gateway_cli, "get_systemd_linger_status", lambda: (False, ""),
|
||||
)
|
||||
|
|
@ -1142,6 +1170,10 @@ class TestPreflightUserSystemd:
|
|||
gateway_cli, "_user_dbus_socket_path",
|
||||
lambda: type("P", (), {"exists": lambda self: False})(),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
gateway_cli, "_user_systemd_private_socket_path",
|
||||
lambda: type("P", (), {"exists": lambda self: False})(),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
gateway_cli, "get_systemd_linger_status",
|
||||
lambda: (None, "loginctl not found"),
|
||||
|
|
@ -1159,6 +1191,10 @@ class TestPreflightUserSystemd:
|
|||
gateway_cli, "_user_dbus_socket_path",
|
||||
lambda: type("P", (), {"exists": lambda self: False})(),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
gateway_cli, "_user_systemd_private_socket_path",
|
||||
lambda: type("P", (), {"exists": lambda self: False})(),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
gateway_cli, "get_systemd_linger_status", lambda: (True, ""),
|
||||
)
|
||||
|
|
@ -1177,6 +1213,10 @@ class TestPreflightUserSystemd:
|
|||
gateway_cli, "_user_dbus_socket_path",
|
||||
lambda: type("P", (), {"exists": lambda self: False})(),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
gateway_cli, "_user_systemd_private_socket_path",
|
||||
lambda: type("P", (), {"exists": lambda self: False})(),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
gateway_cli, "get_systemd_linger_status", lambda: (False, ""),
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue