From 9ce23a77d522ea9069b1cc4779ce6c78a980fbeb Mon Sep 17 00:00:00 2001 From: Rightup Date: Fri, 4 Sep 2026 21:30:29 +0100 Subject: [PATCH] feat(plugins): add plugin manager service installation and restart logic --- repeater/web/update_endpoints.py | 47 +++++++++++++++++++++++++ tests/test_update_endpoints_unit.py | 53 +++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/repeater/web/update_endpoints.py b/repeater/web/update_endpoints.py index 624165c..5a4cafe 100644 --- a/repeater/web/update_endpoints.py +++ b/repeater/web/update_endpoints.py @@ -915,6 +915,52 @@ def _disable_legacy_services() -> None: subprocess.run([_SYSTEMCTL_BIN, "daemon-reload"], check=False) # nosec B603 +def _ensure_plugin_manager_service() -> None: + """Install and enable the plugin-manager service unit when present.""" + service_unit = "/etc/systemd/system/openhop-plugin-manager.service" + candidates = [ + "/root/openhop-repeater/openhop-plugin-manager.service", + "/opt/openhop_repeater/openhop-plugin-manager.service", + "/opt/openhop_repeater/openhop-repeater/openhop-plugin-manager.service", + "/root/scripts/openhop-plugin-manager.service", + "/opt/openhop_repeater/openhop-repeater/openhop-plugin-manager.service", + ] + + source = next((path for path in candidates if os.path.isfile(path)), None) + if source and not os.path.isfile(service_unit): + _state.append_line(f"[pyMC updater] Installing plugin-manager service from {source}") + try: + os.makedirs(os.path.dirname(service_unit), exist_ok=True) + shutil.copy2(source, service_unit) + except OSError as exc: + logger.warning(f"[Update] Could not install plugin-manager unit: {exc}") + return + + if not os.path.isfile(service_unit): + return + + _state.append_line("[pyMC updater] Ensuring openhop-plugin-manager service is enabled") + for cmd in ( + [_SYSTEMCTL_BIN, "daemon-reload"], + [_SYSTEMCTL_BIN, "enable", "openhop-plugin-manager"], + ): + subprocess.run(cmd, check=False, capture_output=True, text=True) # nosec B603 + + restart = subprocess.run( + [_SYSTEMCTL_BIN, "restart", "openhop-plugin-manager"], + check=False, + capture_output=True, + text=True, + ) # nosec B603 + if restart.returncode != 0: + subprocess.run( + [_SYSTEMCTL_BIN, "start", "openhop-plugin-manager"], + check=False, + capture_output=True, + text=True, + ) # nosec B603 + + def _do_install() -> None: channel = _state.channel @@ -1023,6 +1069,7 @@ def _do_install() -> None: if success: _cleanup_stale_dist_info() + _ensure_plugin_manager_service() _state.append_line("[pyMC updater] Restarting service in 3 seconds…") time.sleep(3) restart_ok = False diff --git a/tests/test_update_endpoints_unit.py b/tests/test_update_endpoints_unit.py index 2c7ab68..625e3ef 100644 --- a/tests/test_update_endpoints_unit.py +++ b/tests/test_update_endpoints_unit.py @@ -440,6 +440,53 @@ def test_do_install_wrapper_success_then_restart_failure(isolated_state, monkeyp assert "restart failed" in (st.error_message or "") +def test_do_install_restarts_plugin_manager_service_when_present(isolated_state, monkeypatch): + st = isolated_state + st.channel = "main" + st.latest_version = "4.0.0" + + monkeypatch.setattr(ue.os, "geteuid", lambda: 1000) + monkeypatch.setattr(ue, "is_buildroot", lambda: False) + monkeypatch.setattr(ue, "_cleanup_stale_dist_info", lambda *args, **kwargs: None) + monkeypatch.setattr(ue.time, "sleep", lambda _s: None) + monkeypatch.setattr( + ue.os.path, + "isfile", + lambda p: ( + p + in { + "/usr/local/bin/pymc-do-upgrade", + "/etc/systemd/system/openhop-plugin-manager.service", + } + ), + ) + monkeypatch.setattr("repeater.service_utils.restart_service", lambda: (True, "ok")) + + calls = [] + + def fake_run(cmd, **kwargs): + calls.append(cmd) + return SimpleNamespace(returncode=0, stdout="", stderr="") + + class _Proc: + def __init__(self, cmd): + self.cmd = cmd + self.stdout = ["ok\n"] + self.returncode = 0 + + def wait(self): + return None + + monkeypatch.setattr(ue.subprocess, "Popen", lambda cmd, **kwargs: _Proc(cmd)) + monkeypatch.setattr(ue.subprocess, "run", fake_run) + + ue._do_install() + + assert any(cmd[:3] == ["/bin/systemctl", "enable", "openhop-plugin-manager"] for cmd in calls) + assert any(cmd[:3] == ["/bin/systemctl", "restart", "openhop-plugin-manager"] for cmd in calls) + assert st.state == "complete" + + def test_do_install_wrapper_success_container_path(isolated_state, monkeypatch): st = isolated_state st.channel = "main" @@ -523,6 +570,12 @@ def _capturing_popen(monkeypatch, calls): monkeypatch.setattr(ue.subprocess, "Popen", lambda cmd, **kwargs: _Proc(cmd, kwargs.get("env"))) + def fake_run(cmd, **kwargs): + calls.append({"cmd": cmd, "env": kwargs.get("env", {}), "run": True}) + return SimpleNamespace(returncode=0, stdout="", stderr="") + + monkeypatch.setattr(ue.subprocess, "run", fake_run) + def test_do_install_root_does_not_force_a_predicted_version(isolated_state, monkeypatch): st = isolated_state