diff --git a/repeater/companion/frame_server.py b/repeater/companion/frame_server.py index 4f2da3a..3af96d0 100644 --- a/repeater/companion/frame_server.py +++ b/repeater/companion/frame_server.py @@ -164,3 +164,13 @@ class CompanionFrameServer(_BaseFrameServer): self.companion_hash, channels, ) + + async def stop(self) -> None: + """Persist contacts and channels before stopping (so they survive daemon restart).""" + if self.sqlite_handler: + try: + await self._save_contacts() + await self._save_channels() + except Exception as e: + logger.warning("Failed to persist contacts/channels on stop: %s", e) + await super().stop() diff --git a/repeater/companion/utils.py b/repeater/companion/utils.py index 2606059..6f2d2c0 100644 --- a/repeater/companion/utils.py +++ b/repeater/companion/utils.py @@ -3,6 +3,14 @@ _INVALID_NODE_NAME_CHARS = "\n\r\x00" +def normalize_companion_identity_key(identity_key: str) -> str: + """Strip whitespace and remove optional 0x prefix so fromhex() is consistent across installs.""" + s = identity_key.strip() + if s.lower().startswith("0x"): + s = s[2:].strip() + return s + + def validate_companion_node_name(value: str) -> str: """Validate node_name for config sync: non-empty, max 31 bytes UTF-8, no control chars.""" if not isinstance(value, str): diff --git a/repeater/main.py b/repeater/main.py index df55bdf..1450703 100644 --- a/repeater/main.py +++ b/repeater/main.py @@ -4,7 +4,7 @@ import os import sys import time -from repeater.companion.utils import validate_companion_node_name +from repeater.companion.utils import validate_companion_node_name, normalize_companion_identity_key from repeater.config import get_radio_for_board, load_config, save_config from repeater.config_manager import ConfigManager from repeater.engine import RepeaterHandler @@ -385,6 +385,10 @@ class RepeaterDaemon: sqlite_handler = None if self.repeater_handler and self.repeater_handler.storage: sqlite_handler = self.repeater_handler.storage.sqlite_handler + if not sqlite_handler and companions_config: + logger.warning( + "Companion persistence disabled: no storage (contacts/channels will not survive restart or disconnect)" + ) radio_config = ( self.repeater_handler.radio_config @@ -404,7 +408,7 @@ class RepeaterDaemon: if isinstance(identity_key, str): try: - identity_key_bytes = bytes.fromhex(identity_key) + identity_key_bytes = bytes.fromhex(normalize_companion_identity_key(identity_key)) except ValueError as e: logger.error(f"Companion '{name}' identity_key invalid hex: {e}") continue @@ -567,7 +571,7 @@ class RepeaterDaemon: if isinstance(identity_key, str): try: - identity_key_bytes = bytes.fromhex(identity_key) + identity_key_bytes = bytes.fromhex(normalize_companion_identity_key(identity_key)) except ValueError as e: raise ValueError(f"Companion '{name}' identity_key invalid hex: {e}") from e elif isinstance(identity_key, bytes):