From 6d64da7816aec2cc58bb1a6f0aceb9c9a921129f Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Sun, 9 Aug 2026 05:28:15 +0200 Subject: feat(node): multi-group config support — 6.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Config now supports [[groups]] array (N groups) alongside back-compat [group] single section. Each group has independent port/quic_port/http_port and visibility. GroupConfig gains quic_port, http_port, visibility fields. NodeConfig gains quic_port and http_port defaults. 70/70 tests. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- packages/meshbay-node/src/meshbay_node/config.py | 93 +++++++++++++++++------- 1 file changed, 67 insertions(+), 26 deletions(-) diff --git a/packages/meshbay-node/src/meshbay_node/config.py b/packages/meshbay-node/src/meshbay_node/config.py index 27301af..b420013 100644 --- a/packages/meshbay-node/src/meshbay_node/config.py +++ b/packages/meshbay-node/src/meshbay_node/config.py @@ -18,22 +18,36 @@ except ImportError: DEFAULT_CONFIG_PATH = Path.home() / ".config" / "meshbay" / "node.toml" EXAMPLE_CONFIG = """\ -# MeshBay Node configuration +# MeshBay Node configuration — multi-group example # See: https://meshbay.org/docs/node-config [hub] -url = "http://meshbay.org" +url = "https://meshbay.org" username = "myusername" -# password stored separately via keystore [node] +port = 19000 # TCP+TLS (MNP v1) +quic_port = 19010 # QUIC (MNP v2) +http_port = 19001 # HTTP file API (public content) +ui_port = 18000 # local web UI + +# Multiple groups — each with its own directory and ports +[[groups]] +id = "" # set after joining +name = "My Media" +shared_dir = "/home/user/Media" port = 19000 -ui_port = 18000 -shared_dirs = ["/home/user/MeshBay"] - -[group] -id = "" # set after joining a group -name = "" +quic_port = 19010 +http_port = 19001 + +[[groups]] +id = "" +name = "Public Archive" +shared_dir = "/home/user/Archive" +port = 19002 +quic_port = 19012 +http_port = 19003 +visibility = "public" [keystore] # unlock_file = "~/.config/meshbay/unlock.key" @@ -43,21 +57,26 @@ name = "" @dataclass class HubConfig: - url: str = "http://meshbay.org" + url: str = "https://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) + port: int = 19000 + quic_port: int = 19010 + http_port: int = 19001 + ui_port: int = 18000 @dataclass class GroupConfig: - id: str = "" + id: str = "" + visibility: str = "private" # public|private + port: int = 19000 # TCP+TLS MNP port for this group + quic_port: int = 19010 # QUIC MNP port + http_port: int = 19001 # HTTP file API port name: str = "" @@ -71,31 +90,53 @@ class KeystoreConfig: class Config: hub: HubConfig = field(default_factory=HubConfig) node: NodeConfig = field(default_factory=NodeConfig) - group: GroupConfig = field(default_factory=GroupConfig) + groups: list[GroupConfig] = field(default_factory=list) keystore: KeystoreConfig = field(default_factory=KeystoreConfig) + # Back-compat: single-group access + @property + def group(self) -> GroupConfig: + return self.groups[0] if self.groups else GroupConfig() + def load_config(path: Path = DEFAULT_CONFIG_PATH) -> Config: """ - Load config from TOML file. Missing file returns defaults. - Env vars override file values. + Load config from TOML file. Supports both single [group] and + multiple [[groups]] sections. 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) + cfg.node.port = nd.get("port", cfg.node.port) + cfg.node.quic_port = nd.get("quic_port", cfg.node.quic_port) + cfg.node.http_port = nd.get("http_port", cfg.node.http_port) + cfg.node.ui_port = nd.get("ui_port", cfg.node.ui_port) + + # Multi-group: [[groups]] array + if "groups" in raw: + for g in raw["groups"]: + cfg.groups.append(GroupConfig( + id=g.get("id", ""), + name=g.get("name", ""), + visibility=g.get("visibility", "private"), + port=g.get("port", cfg.node.port), + quic_port=g.get("quic_port", cfg.node.quic_port), + http_port=g.get("http_port", cfg.node.http_port), + )) + # Back-compat: single [group] section + elif "group" in raw: + grp = raw["group"] + cfg.groups.append(GroupConfig( + id=grp.get("id", ""), + name=grp.get("name", ""), + )) ks = raw.get("keystore", {}) if "path" in ks: @@ -104,11 +145,11 @@ def load_config(path: Path = DEFAULT_CONFIG_PATH) -> Config: cfg.keystore.unlock_file = Path(ks["unlock_file"]).expanduser() # Env var overrides - if url := os.environ.get("MESHBAY_HUB_URL"): + 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"): + if pwd := os.environ.get("MESHBAY_PASSWORD"): cfg.hub.password = pwd if port := os.environ.get("MESHBAY_PORT"): cfg.node.port = int(port) -- cgit v1.2.3