Squashed integration of the user-facing message audit for this surface set. Full per-finding receipts: /tmp/ux-audit/lanes/*-receipt.md (campaign artifacts).
52 lines
2.0 KiB
Python
52 lines
2.0 KiB
Python
"""User-facing copy for ``hermes gateway start/stop/restart`` failures on systemd hosts.
|
|
|
|
``hermes_cli/gateway.py`` is a facade; this sibling owns the small exception -> guidance table so
|
|
the most common Linux service failures (``systemctl`` exited non-zero, or there is no ``systemctl``
|
|
at all) end as a next step instead of a traceback.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
|
|
|
|
class SystemctlUnavailableError(RuntimeError):
|
|
"""``systemctl`` is not installed (Alpine, minimal containers, some WSL setups)."""
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__("systemctl is not available on this system")
|
|
|
|
|
|
_JOURNAL_HINT = 'journalctl --user -u hermes-gateway --since "5 min ago"'
|
|
|
|
_SYSTEMCTL_FAILED_LINES = (
|
|
"Could not {verb} the gateway service; systemd reported an error.",
|
|
"See why with `hermes gateway status --deep` or `{journal}`.",
|
|
"To reinstall the service run `hermes gateway install --force`.",
|
|
)
|
|
|
|
_NO_SYSTEMCTL_LINES = (
|
|
"This system has no systemd, so Hermes cannot install a background service here.",
|
|
"Run the gateway directly with `hermes gateway run` (keep it alive with tmux or screen).",
|
|
)
|
|
|
|
|
|
def _verb_for(exc: subprocess.CalledProcessError) -> str:
|
|
cmd = exc.cmd if isinstance(exc.cmd, (list, tuple)) else str(exc.cmd).split()
|
|
for token in cmd:
|
|
if token in ("start", "stop", "restart"):
|
|
return token
|
|
return "start"
|
|
|
|
|
|
def explain_service_failure(exc: BaseException) -> list[str] | None:
|
|
"""Lines to print for a systemd service failure escaping the gateway command, or None
|
|
when *exc* is not one this module knows how to explain (callers re-raise)."""
|
|
if isinstance(exc, SystemctlUnavailableError):
|
|
return list(_NO_SYSTEMCTL_LINES)
|
|
if isinstance(exc, subprocess.CalledProcessError):
|
|
lines = [line.format(verb=_verb_for(exc), journal=_JOURNAL_HINT) for line in _SYSTEMCTL_FAILED_LINES]
|
|
lines.append(f"Details: {exc}")
|
|
return lines
|
|
return None
|