mirror of
https://github.com/openhop-dev/openhop_repeater.git
synced 2026-10-23 13:32:50 +00:00
134 lines
3.8 KiB
Python
134 lines
3.8 KiB
Python
"""CLI entry for the openHop plugin manager process.
|
|
|
|
Usage:
|
|
openhop-plugin-manager --config /etc/openhop_repeater/config.yaml
|
|
python -m repeater.plugins --plugins-root /var/lib/openhop_repeater/plugins
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import logging
|
|
import signal
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
from typing import Any, Optional
|
|
|
|
from .ipc import PluginIPCServer
|
|
from .manager import PluginManager
|
|
from .runtime import PluginRuntime
|
|
from .storage import (
|
|
PluginStorage,
|
|
resolve_catalogue_url,
|
|
resolve_plugin_socket_path,
|
|
resolve_plugins_root,
|
|
)
|
|
|
|
|
|
def _load_config(config_path: Optional[str]) -> dict[str, Any]:
|
|
if not config_path:
|
|
return {}
|
|
try:
|
|
from repeater.config import load_config
|
|
|
|
return load_config(config_path)
|
|
except Exception:
|
|
# Fall back to raw YAML if full load_config has radio side-effects
|
|
import yaml
|
|
|
|
path = Path(config_path)
|
|
if not path.is_file():
|
|
return {}
|
|
with open(path, encoding="utf-8") as fh:
|
|
data = yaml.safe_load(fh) or {}
|
|
return data if isinstance(data, dict) else {}
|
|
|
|
|
|
def build_arg_parser() -> argparse.ArgumentParser:
|
|
p = argparse.ArgumentParser(description="openHop plugin manager")
|
|
p.add_argument("--config", default=None, help="Path to Repeater config.yaml")
|
|
p.add_argument(
|
|
"--plugins-root",
|
|
default=None,
|
|
help="Override plugin install root (default: {storage_dir}/plugins)",
|
|
)
|
|
p.add_argument(
|
|
"--socket",
|
|
default=None,
|
|
help="Unix domain socket path (default: {storage_dir}/plugin-manager.sock)",
|
|
)
|
|
p.add_argument(
|
|
"--log-level",
|
|
default="INFO",
|
|
help="Logging level (default: INFO)",
|
|
)
|
|
return p
|
|
|
|
|
|
def main(argv: Optional[list[str]] = None) -> int:
|
|
args = build_arg_parser().parse_args(argv)
|
|
logging.basicConfig(
|
|
level=getattr(logging, str(args.log_level).upper(), logging.INFO),
|
|
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
|
)
|
|
logger = logging.getLogger("PluginManagerMain")
|
|
|
|
config = _load_config(args.config)
|
|
plugins_cfg = config.get("plugins") if isinstance(config.get("plugins"), dict) else {}
|
|
if plugins_cfg.get("enabled") is False:
|
|
logger.error("plugins.enabled is false in config; refusing to start")
|
|
return 2
|
|
|
|
if args.plugins_root:
|
|
plugins_root = Path(args.plugins_root).expanduser().resolve()
|
|
else:
|
|
plugins_root = resolve_plugins_root(config)
|
|
|
|
if args.socket:
|
|
socket_path = Path(args.socket).expanduser().resolve()
|
|
else:
|
|
socket_path = resolve_plugin_socket_path(config)
|
|
|
|
storage = PluginStorage(plugins_root)
|
|
runtime = PluginRuntime(storage)
|
|
manager = PluginManager(storage, runtime, catalogue_url=resolve_catalogue_url(config))
|
|
server = PluginIPCServer(socket_path, manager)
|
|
|
|
stop = {"flag": False}
|
|
|
|
def _handle_signal(signum, frame): # noqa: ARG001
|
|
logger.info("Signal %s received, shutting down", signum)
|
|
stop["flag"] = True
|
|
|
|
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
try:
|
|
signal.signal(sig, _handle_signal)
|
|
except (OSError, ValueError) as exc:
|
|
logger.debug("Could not register signal %s: %s", sig, exc)
|
|
|
|
logger.info("Plugin root: %s", plugins_root)
|
|
logger.info("IPC socket: %s", socket_path)
|
|
manager.start()
|
|
server.start()
|
|
|
|
try:
|
|
while not stop["flag"]:
|
|
time.sleep(0.5)
|
|
finally:
|
|
logger.info("Stopping plugin manager")
|
|
try:
|
|
server.stop()
|
|
except Exception as exc:
|
|
logger.debug("IPC stop: %s", exc)
|
|
try:
|
|
manager.stop_all()
|
|
except Exception as exc:
|
|
logger.debug("manager stop_all: %s", exc)
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|