fix(cli): tolerate systems without systemctl in upgrade/doctor flows

Bug: On systems without systemd (Unraid OS 7.2.4, BSD, macOS containers,
and other non-systemd Linux hosts), running `muxplex upgrade` or
`muxplex update` crashed immediately with:

  FileNotFoundError: [Errno 2] No such file or directory: 'systemctl'

The check ran unconditionally before any install step, so the upgrade
aborted without even attempting to fetch the new version. Introduced
in v0.6.0.

Fix: Add a module-level `_have_systemctl() -> bool` helper to cli.py
(and service.py) that gates every systemd-specific operation behind
`shutil.which("systemctl") is not None`.

Call sites guarded in cli.py (upgrade() function):
  1. systemctl --user is-active muxplex  (stop-before-upgrade check)
  2. systemctl --user stop muxplex       (pre-install service stop)
  3. Service file regeneration step      (service_install() call)
  4. systemctl --user is-enabled muxplex (post-install restart check)
  5. systemctl --user daemon-reload      (post-install daemon reload)
  6. systemctl --user start muxplex      (post-install service start)

Behaviour on no-systemd systems:
  - Skips the is-active check (treats as unmanaged; prints skip note).
  - Skips the stop step.
  - Still performs the uv/pip install.
  - Skips service-file regeneration (prints skip note).
  - Skips the daemon-reload / start steps.
  - Prints: '! systemd not detected — restart muxplex manually to pick
    up the new version' with the running PID if pgrep finds one.
  - Still runs `muxplex doctor` for verification (no systemd required).

doctor() change:
  - On non-darwin platforms with no systemctl, now prints:
    '! Service: systemd not available on this platform'
    instead of silently doing nothing or crashing.

service.py change:
  - Public API functions (service_install, service_uninstall,
    service_start, service_stop, service_restart, service_status,
    service_logs) now check _have_systemctl() on the Linux path.
  - When absent, print a clear, friendly error pointing the user to
    `muxplex serve` instead of letting subprocess raise FileNotFoundError.

Tests added (muxplex/tests/test_cli.py, +8 tests):
  - test_have_systemctl_helper_exists
  - test_have_systemctl_returns_bool
  - test_upgrade_no_systemctl_runs_to_completion (regression)
  - test_upgrade_no_systemctl_prints_skip_note
  - test_upgrade_no_systemctl_prints_manual_restart_note
  - test_upgrade_with_systemctl_runs_systemd_commands
  - test_doctor_no_systemctl_shows_graceful_message
  - test_doctor_no_systemctl_does_not_crash
This commit is contained in:
Brian Krabach
2026-05-17 12:26:20 -07:00
parent 794e329938
commit d7d07ec07e
10 changed files with 392 additions and 73 deletions
Generated
+1 -1
View File
@@ -332,7 +332,7 @@ wheels = [
[[package]]
name = "muxplex"
version = "0.4.6"
version = "0.6.0"
source = { editable = "." }
dependencies = [
{ name = "aiofiles" },