From e4efc8045d753633c3acc06f59ed75c904636d03 Mon Sep 17 00:00:00 2001 From: Lloyd Date: Tue, 5 May 2026 17:17:12 +0100 Subject: [PATCH] feat: implement resolve_storage_dir for consistent storage paths --- repeater/config.py | 34 +++++++++++++++++++ .../data_acquisition/storage_collector.py | 9 ++--- repeater/web/api_endpoints.py | 23 +++---------- repeater/web/http_server.py | 3 +- 4 files changed, 43 insertions(+), 26 deletions(-) diff --git a/repeater/config.py b/repeater/config.py index d31e8fa..8bdb028 100644 --- a/repeater/config.py +++ b/repeater/config.py @@ -9,6 +9,30 @@ import yaml logger = logging.getLogger("Config") +def resolve_storage_dir( + config: Dict[str, Any], + *, + config_path: Optional[str] = None, + default: str = "/var/lib/pymc_repeater", +) -> Path: + + storage_dir_cfg = ( + config.get("storage", {}).get("storage_dir") + or config.get("storage_dir") + or default + ) + + storage_dir = Path(str(storage_dir_cfg)).expanduser() + if not storage_dir.is_absolute(): + if config_path: + base_dir = Path(config_path).expanduser().resolve().parent + storage_dir = (base_dir / storage_dir).resolve() + else: + storage_dir = storage_dir.resolve() + + return storage_dir + + def get_node_info(config: Dict[str, Any]) -> Dict[str, Any]: """ Extract node name, radio configuration, and MQTT settings from config. @@ -65,6 +89,16 @@ def load_config(config_path: Optional[str] = None) -> Dict[str, Any]: except Exception as e: raise RuntimeError(f"Failed to load configuration from {config_path}: {e}") from e + storage_dir = resolve_storage_dir(config, config_path=config_path) + if "storage" not in config or not isinstance(config.get("storage"), dict): + config["storage"] = {} + config["storage"]["storage_dir"] = str(storage_dir) + + if config.get("storage_dir"): + logger.warning( + "Deprecated config key 'storage_dir' detected; prefer 'storage.storage_dir'." + ) + if "mesh" not in config: config["mesh"] = {} diff --git a/repeater/data_acquisition/storage_collector.py b/repeater/data_acquisition/storage_collector.py index 319fc36..bc0a5b2 100644 --- a/repeater/data_acquisition/storage_collector.py +++ b/repeater/data_acquisition/storage_collector.py @@ -6,6 +6,8 @@ from datetime import datetime from pathlib import Path from typing import Any, Dict, Optional +from repeater.config import resolve_storage_dir + from .mqtt_handler import MeshCoreToMqttPusher from .rrdtool_handler import RRDToolHandler from .sqlite_handler import SQLiteHandler @@ -21,12 +23,7 @@ class StorageCollector: self.glass_publish_callback = None self._pending_tasks = set() - storage_dir_cfg = ( - config.get("storage", {}).get("storage_dir") - or config.get("storage_dir") - or "/var/lib/pymc_repeater" - ) - self.storage_dir = Path(storage_dir_cfg) + self.storage_dir = resolve_storage_dir(config) self.storage_dir.mkdir(parents=True, exist_ok=True) self.sqlite_handler = SQLiteHandler(self.storage_dir) diff --git a/repeater/web/api_endpoints.py b/repeater/web/api_endpoints.py index cbadbb0..397d0f0 100644 --- a/repeater/web/api_endpoints.py +++ b/repeater/web/api_endpoints.py @@ -15,7 +15,7 @@ from repeater.companion.identity_resolve import ( find_companion_index, heal_companion_empty_names, ) -from repeater.config import update_unscoped_flood_policy +from repeater.config import resolve_storage_dir, update_unscoped_flood_policy from repeater.service_utils import get_buildroot_image_info from .auth.middleware import require_auth @@ -337,12 +337,7 @@ class APIEndpoints: import json # Check config-based location first, then development location - storage_dir_cfg = ( - self.config.get("storage", {}).get("storage_dir") - or self.config.get("storage_dir") - or "/var/lib/pymc_repeater" - ) - config_dir = Path(storage_dir_cfg) + config_dir = resolve_storage_dir(self.config, config_path=self._config_path) installed_path = config_dir / "radio-settings.json" dev_path = os.path.join(os.path.dirname(__file__), "..", "..", "radio-settings.json") @@ -387,12 +382,7 @@ class APIEndpoints: import json # Check config-based location first, then development location - storage_dir_cfg = ( - self.config.get("storage", {}).get("storage_dir") - or self.config.get("storage_dir") - or "/var/lib/pymc_repeater" - ) - config_dir = Path(storage_dir_cfg) + config_dir = resolve_storage_dir(self.config, config_path=self._config_path) installed_path = config_dir / "radio-presets.json" dev_path = os.path.join(os.path.dirname(__file__), "..", "..", "radio-presets.json") @@ -448,12 +438,7 @@ class APIEndpoints: import json - storage_dir_cfg = ( - self.config.get("storage", {}).get("storage_dir") - or self.config.get("storage_dir") - or "/var/lib/pymc_repeater" - ) - config_dir = Path(storage_dir_cfg) + config_dir = resolve_storage_dir(self.config, config_path=self._config_path) installed_path = config_dir / "radio-settings.json" dev_path = os.path.join(os.path.dirname(__file__), "..", "..", "radio-settings.json") hardware_file = str(installed_path) if installed_path.exists() else dev_path diff --git a/repeater/web/http_server.py b/repeater/web/http_server.py index 2067313..aab8a49 100644 --- a/repeater/web/http_server.py +++ b/repeater/web/http_server.py @@ -13,6 +13,7 @@ import cherrypy_cors from pymc_core.protocol.utils import PAYLOAD_TYPES, ROUTE_TYPES from repeater import __version__ +from repeater.config import resolve_storage_dir from repeater.data_acquisition import SQLiteHandler from .api_endpoints import APIEndpoints @@ -261,7 +262,7 @@ class HTTPStatsServer: logger.info(f"JWT handler initialized (token expiry: {jwt_expiry_minutes} minutes)") # Initialize API token manager - storage_dir = self.config.get("storage", {}).get("storage_dir", ".") + storage_dir = resolve_storage_dir(self.config, config_path=self.config_path) # Ensure storage directory exists os.makedirs(storage_dir, exist_ok=True)