From af5e52a43070fb016f85320ae53100e5424828ec Mon Sep 17 00:00:00 2001 From: Ivan Date: Wed, 15 Apr 2026 06:53:07 -0500 Subject: [PATCH] feat(bot): update bot configuration management by adding support for custom config paths and reticulum config directories; update bot templates to accept new parameters --- meshchatx/src/backend/bot_handler.py | 35 ++++++++++++++++++++++++++ meshchatx/src/backend/bot_process.py | 28 ++++++++++++++++++--- meshchatx/src/backend/bot_templates.py | 33 +++++++++++++++++++++--- 3 files changed, 89 insertions(+), 7 deletions(-) diff --git a/meshchatx/src/backend/bot_handler.py b/meshchatx/src/backend/bot_handler.py index d804722..87aa8db 100644 --- a/meshchatx/src/backend/bot_handler.py +++ b/meshchatx/src/backend/bot_handler.py @@ -17,6 +17,11 @@ class BotHandler: def __init__(self, identity_path, config_manager=None): self.identity_path = os.path.abspath(identity_path) self.config_manager = config_manager + self.bot_reticulum_config_dir = os.path.abspath( + os.path.expanduser( + os.environ.get("MESHCHAT_BOT_RETICULUM_CONFIG_DIR", "~/.reticulum"), + ), + ) self.bots_dir = os.path.join(self.identity_path, "bots") os.makedirs(self.bots_dir, exist_ok=True) self.running_bots = {} @@ -36,6 +41,14 @@ class BotHandler: for entry in self.bots_state: if "storage_dir" in entry: entry["storage_dir"] = os.path.abspath(entry["storage_dir"]) + if "bot_config_dir" in entry and entry["bot_config_dir"]: + entry["bot_config_dir"] = os.path.abspath( + os.path.expanduser(entry["bot_config_dir"]), + ) + if "reticulum_config_dir" in entry and entry["reticulum_config_dir"]: + entry["reticulum_config_dir"] = os.path.abspath( + os.path.expanduser(entry["reticulum_config_dir"]), + ) except FileNotFoundError: self.bots_state = [] except Exception: @@ -156,6 +169,8 @@ class BotHandler: "template_id": template_id, "name": name or f"{template_id.title()} Bot", "storage_dir": bot_storage_dir, + "bot_config_dir": os.path.join(bot_storage_dir, "config"), + "reticulum_config_dir": self.bot_reticulum_config_dir, "enabled": True, "pid": None, } @@ -164,6 +179,10 @@ class BotHandler: bot_storage_dir = entry["storage_dir"] entry["template_id"] = template_id entry["name"] = name or entry.get("name") or f"{template_id.title()} Bot" + if not entry.get("bot_config_dir"): + entry["bot_config_dir"] = os.path.join(bot_storage_dir, "config") + if not entry.get("reticulum_config_dir"): + entry["reticulum_config_dir"] = self.bot_reticulum_config_dir entry["enabled"] = True os.makedirs(bot_storage_dir, exist_ok=True) @@ -177,6 +196,10 @@ class BotHandler: entry["name"], "--storage", bot_storage_dir, + "--config-path", + entry["bot_config_dir"], + "--reticulum-config-dir", + entry["reticulum_config_dir"], ] proc = subprocess.Popen(cmd, cwd=bot_storage_dir) # noqa: S603 @@ -299,6 +322,18 @@ class BotHandler: if not storage_dir: return None + bot_config_dir = entry.get("bot_config_dir") + if bot_config_dir: + id_path_bot_cfg = os.path.join(bot_config_dir, "identity") + if os.path.exists(id_path_bot_cfg): + return id_path_bot_cfg + + reticulum_config_dir = entry.get("reticulum_config_dir") + if reticulum_config_dir: + id_path_shared = os.path.join(reticulum_config_dir, "identity") + if os.path.exists(id_path_shared): + return id_path_shared + # LXMFy stores identity in the 'config' subdirectory by default id_path = os.path.join(storage_dir, "config", "identity") if os.path.exists(id_path): diff --git a/meshchatx/src/backend/bot_process.py b/meshchatx/src/backend/bot_process.py index 047c48f..725b3ac 100644 --- a/meshchatx/src/backend/bot_process.py +++ b/meshchatx/src/backend/bot_process.py @@ -20,15 +20,35 @@ def main(): parser.add_argument("--template", required=True, choices=TEMPLATE_MAP.keys()) parser.add_argument("--name", required=True) parser.add_argument("--storage", required=True) + parser.add_argument("--config-path", default=None) + parser.add_argument( + "--reticulum-config-dir", + default=os.environ.get( + "MESHCHAT_BOT_RETICULUM_CONFIG_DIR", + os.path.expanduser("~/.reticulum"), + ), + ) args = parser.parse_args() os.makedirs(args.storage, exist_ok=True) - os.chdir(args.storage) + + config_path = args.config_path + if config_path: + config_path = os.path.abspath(os.path.expanduser(config_path)) + else: + config_path = os.path.join(os.path.abspath(args.storage), "config") + os.makedirs(config_path, exist_ok=True) + reticulum_config_dir = os.path.abspath(os.path.expanduser(args.reticulum_config_dir)) + os.makedirs(reticulum_config_dir, exist_ok=True) BotCls = TEMPLATE_MAP[args.template] - # LXMFy hardcodes its config directory to os.path.join(os.getcwd(), 'config'). - # By chdir'ing into args.storage, we ensure 'config' and data are kept within that folder. - bot_instance = BotCls(name=args.name, storage_path=args.storage, test_mode=False) + bot_instance = BotCls( + name=args.name, + storage_path=args.storage, + test_mode=False, + config_path=config_path, + reticulum_config_dir=reticulum_config_dir, + ) # Optional immediate announce for reachability with contextlib.suppress(Exception): diff --git a/meshchatx/src/backend/bot_templates.py b/meshchatx/src/backend/bot_templates.py index 5814f2d..b7afba7 100644 --- a/meshchatx/src/backend/bot_templates.py +++ b/meshchatx/src/backend/bot_templates.py @@ -19,7 +19,14 @@ class StoppableBot: class EchoBotTemplate(StoppableBot): - def __init__(self, name="Echo Bot", storage_path=None, test_mode=False): + def __init__( + self, + name="Echo Bot", + storage_path=None, + test_mode=False, + config_path=None, + reticulum_config_dir=None, + ): super().__init__() self.bot = LXMFBot( @@ -29,6 +36,8 @@ class EchoBotTemplate(StoppableBot): first_message_enabled=True, test_mode=test_mode, storage_path=storage_path, + config_path=config_path, + reticulum_config_dir=reticulum_config_dir, ) self.setup_commands() self.setup_message_handlers() @@ -97,7 +106,14 @@ class EchoBotTemplate(StoppableBot): class NoteBotTemplate(StoppableBot): - def __init__(self, name="Note Bot", storage_path=None, test_mode=False): + def __init__( + self, + name="Note Bot", + storage_path=None, + test_mode=False, + config_path=None, + reticulum_config_dir=None, + ): super().__init__() self.bot = LXMFBot( @@ -107,6 +123,8 @@ class NoteBotTemplate(StoppableBot): storage_type="json", storage_path=storage_path or "data/notes", test_mode=test_mode, + config_path=config_path, + reticulum_config_dir=reticulum_config_dir, ) self.setup_commands() @@ -176,7 +194,14 @@ class NoteBotTemplate(StoppableBot): class ReminderBotTemplate(StoppableBot): - def __init__(self, name="Reminder Bot", storage_path=None, test_mode=False): + def __init__( + self, + name="Reminder Bot", + storage_path=None, + test_mode=False, + config_path=None, + reticulum_config_dir=None, + ): super().__init__() self.bot = LXMFBot( @@ -186,6 +211,8 @@ class ReminderBotTemplate(StoppableBot): storage_type="sqlite", storage_path=storage_path or "data/reminders.db", test_mode=test_mode, + config_path=config_path, + reticulum_config_dir=reticulum_config_dir, ) self.setup_commands() self.bot.scheduler.add_task(