fix(gateway): don't dead-end setup wizard when only system-scope unit is installed#20905
Conversation
… is installed The setup wizard dropped non-root users at a bare shell prompt when trying to start a system-scope gateway service. Previously _require_root_for_system_service called sys.exit(1), which the wizard's `except Exception` guards cannot catch (SystemExit is a BaseException). Users with a pre-existing /etc/systemd/system unit (e.g. from an earlier `sudo hermes setup` run) hit this whenever they re-ran `hermes setup` as a regular user. - Convert _require_root_for_system_service to raise a typed SystemScopeRequiresRootError (RuntimeError subclass) instead of sys.exit(1). The direct CLI path (`hermes gateway install|start|stop| restart|uninstall` without sudo) still exits 1 cleanly via a new catch at the top of gateway_command, matching the existing UserSystemdUnavailableError pattern. - Add _system_scope_wizard_would_need_root() pre-check and _print_system_scope_remediation() helper. Both setup wizards (hermes_cli/setup.py and hermes_cli/gateway.py::gateway_setup) now detect the dead-end before prompting and print actionable guidance: either `sudo systemctl start <service>` this time, or uninstall the system unit and install a per-user one. - Defense-in-depth: all 5 wizard prompt sites also catch SystemScopeRequiresRootError and fall back to the remediation helper if the pre-check is bypassed (race, etc.). Tests: 12 new tests in TestSystemScopeRequiresRootError, TestSystemScopeWizardPreCheck, TestSystemScopeRemediationOutput, and TestGatewayCommandCatchesSystemScopeError covering the exception contract, pre-check matrix (root vs non-root, system-only vs user-present vs none vs explicit system=True), remediation output for each action, and the direct-CLI exit-1 path.
🚨 CRITICAL Supply Chain Risk DetectedThis PR contains a pattern that has been used in real supply chain attacks. A maintainer must review the flagged code carefully before merging. 🚨 CRITICAL: Install-hook file added or modifiedThese files can execute code during package installation or interpreter startup. Files: Scanner only fires on high-signal indicators: .pth files, base64+exec/eval combos, subprocess with encoded commands, or install-hook files. Low-signal warnings were removed intentionally — if you're seeing this comment, the finding is worth inspecting. |
🔎 Lint report:
|
Summary
The setup wizard no longer drops users at a bare shell prompt when a system-scope systemd unit exists and the user isn't root.
Root cause:
_require_root_for_system_servicecalledsys.exit(1). The wizard'sexcept Exceptionguards don't catchSystemExit(it's aBaseException), so the whole process died mid-setup. Users who had previously runsudo hermes setup(or equivalent) hit this every time they later ranhermes setupas a regular user — no explanation, just a shell prompt.Changes
SystemScopeRequiresRootError(RuntimeError)with a clean__str__so format strings render the message (not the(msg, action)tuple)._require_root_for_system_serviceraises instead ofsys.exit.gateway_commandcatches the new error and exits 1 — preserves the directhermes gateway start --systemCLI path unchanged._system_scope_wizard_would_need_root()pre-check and_print_system_scope_remediation()helper.gateway_setup()now check the guard first and fall back to the remediation printer in a defensiveexcept SystemScopeRequiresRootErrorbranch.setup_gateway(), plus the post-install "Start the service now?" sub-branch.Validation
hermes setupwizard as non-root with system unit presentsys.exit(1)→ shell prompthermes gateway start --systemas non-roothermes setupas rootTest results:
scripts/run_tests.sh tests/hermes_cli/test_gateway_service.py tests/hermes_cli/test_gateway.py tests/hermes_cli/test_setup.py tests/hermes_cli/test_setup_reconfigure.py tests/hermes_cli/test_setup_noninteractive.py— 186/186 passing, 12 new.E2E (non-root +
/etc/systemd/system/hermes-gateway.servicepresent) now prints:Reported by a user whose terminal dropped to a bare shell after answering
Yto "Start the gateway service?" with only aSystem gateway start requires root. Re-run with sudo.line above it.