""" MeshBay Node configuration. Config file: ~/.config/meshbay/node.toml All values have sensible defaults and can be overridden by env vars prefixed with MESHBAY_ (e.g. MESHBAY_HUB_URL). """ import os from dataclasses import dataclass, field from pathlib import Path try: import tomllib # Python 3.11+ except ImportError: import tomli as tomllib # type: ignore[no-redef] DEFAULT_CONFIG_PATH = Path.home() / ".config" / "meshbay" / "node.toml" EXAMPLE_CONFIG = """\ # MeshBay Node configuration # See: https://meshbay.org/docs/node-config [hub] url = "http://meshbay.org" username = "myusername" # password stored separately via keystore [node] port = 19000 ui_port = 18000 shared_dirs = ["/home/user/MeshBay"] [group] id = "" # set after joining a group name = "" [keystore] # unlock_file = "~/.config/meshbay/unlock.key" # or set MESHBAY_UNLOCK_KEY env var """ @dataclass class HubConfig: url: str = "http://meshbay.org" username: str = "" password: str = "" # loaded from keystore or env; never written to TOML @dataclass class NodeConfig: port: int = 19000 ui_port: int = 18000 shared_dirs: list[str] = field(default_factory=list) @dataclass class GroupConfig: id: str = "" name: str = "" @dataclass class KeystoreConfig: path: Path = field(default_factory=lambda: DEFAULT_CONFIG_PATH.parent / "keystore.enc") unlock_file: Path | None = None @dataclass class Config: hub: HubConfig = field(default_factory=HubConfig) node: NodeConfig = field(default_factory=NodeConfig) group: GroupConfig = field(default_factory=GroupConfig) keystore: KeystoreConfig = field(default_factory=KeystoreConfig) def load_config(path: Path = DEFAULT_CONFIG_PATH) -> Config: """ Load config from TOML file. Missing file returns defaults. Env vars override file values. """ cfg = Config() if path.exists(): raw = tomllib.loads(path.read_text()) hub = raw.get("hub", {}) cfg.hub.url = hub.get("url", cfg.hub.url) cfg.hub.username = hub.get("username", cfg.hub.username) nd = raw.get("node", {}) cfg.node.port = nd.get("port", cfg.node.port) cfg.node.ui_port = nd.get("ui_port", cfg.node.ui_port) cfg.node.shared_dirs = nd.get("shared_dirs", cfg.node.shared_dirs) grp = raw.get("group", {}) cfg.group.id = grp.get("id", cfg.group.id) cfg.group.name = grp.get("name", cfg.group.name) ks = raw.get("keystore", {}) if "path" in ks: cfg.keystore.path = Path(ks["path"]).expanduser() if "unlock_file" in ks: cfg.keystore.unlock_file = Path(ks["unlock_file"]).expanduser() # Env var overrides if url := os.environ.get("MESHBAY_HUB_URL"): cfg.hub.url = url if user := os.environ.get("MESHBAY_USERNAME"): cfg.hub.username = user if pwd := os.environ.get("MESHBAY_PASSWORD"): cfg.hub.password = pwd if port := os.environ.get("MESHBAY_PORT"): cfg.node.port = int(port) return cfg def write_example_config(path: Path = DEFAULT_CONFIG_PATH) -> None: """Write an example config file if none exists.""" if not path.exists(): path.parent.mkdir(parents=True, exist_ok=True) path.write_text(EXAMPLE_CONFIG)