summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src/meshbay_node/daemon.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-22 16:40:54 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-22 16:40:54 +0200
commit9c136a0e37add42f5d0c8675a797969cfe690de0 (patch)
treed5dfd3a7e2289a769dc987f086eec3f475054f85 /packages/meshbay-node/src/meshbay_node/daemon.py
parent2dbd70484e65ae57e18c323381136077cd00adad (diff)
downloadmeshbay-9c136a0e37add42f5d0c8675a797969cfe690de0.tar.gz
fix: first-run wizard reliability and node startup performance
Node daemon no longer blocks startup on slow directory scans — initial indexing runs in the background so the node reaches "running" immediately after transports are up. Fixes the wizard failing to detect the node when large USB/NAS roots take minutes to scan. Also: wizard key-linking deadlock resolved (main.js links during poll), invite form stays in DOM during reconnects (disabled instead of destroyed), pairing code bridges to renderer, and firewall docs for LAN casting added. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/daemon.py')
-rw-r--r--packages/meshbay-node/src/meshbay_node/daemon.py64
1 files changed, 35 insertions, 29 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/daemon.py b/packages/meshbay-node/src/meshbay_node/daemon.py
index e11a654..5fc70fe 100644
--- a/packages/meshbay-node/src/meshbay_node/daemon.py
+++ b/packages/meshbay-node/src/meshbay_node/daemon.py
@@ -241,14 +241,13 @@ class NodeDaemon:
gek=gek,
on_change=self._on_index_change,
)
- await indexer.start()
+ await indexer.start(defer_scan=True)
self._indexers.append(indexer)
self._state["indexes"][group_cfg.id] = indexer.index
self._state["indexers"][group_cfg.id] = indexer
- log.info("Indexing group %s: %s (%d files)",
+ log.info("Group %s configured: %s (scan deferred)",
group_cfg.name,
- ", ".join(f"{r.name}={r.path}" for r in roots),
- indexer.index.count)
+ ", ".join(f"{r.name}={r.path}" for r in roots))
groups_ctx[group_cfg.id] = {
"gek": gek,
@@ -268,8 +267,8 @@ class NodeDaemon:
}
if not groups_ctx:
- log.error("No valid groups configured — exiting")
- return
+ log.warning("No groups configured yet — admin UI and hub "
+ "connection stay up; attach a group to go live")
# 5. Chat stores (one SQLite DB per group)
for gid in groups_ctx:
@@ -290,14 +289,14 @@ class NodeDaemon:
denylist = self._denylist
# 6. WebRTC transport (browser clients)
- first = next(iter(groups_ctx.values()))
+ first = next(iter(groups_ctx.values()), None)
if WEBRTC_AVAILABLE:
self._webrtc = WebRTCTransport(
sk_node=keys.sk_ed25519,
hub_pk_pem=session.hub_pk_pem,
- gek=first["gek"],
- roots=first["roots"],
- index=first["index"],
+ gek=first["gek"] if first else None,
+ roots=first["roots"] if first else None,
+ index=first["index"] if first else None,
groups=groups_ctx,
denylist=denylist,
max_concurrent_streams=self._config.node.max_concurrent_streams,
@@ -307,6 +306,7 @@ class NodeDaemon:
# _group_ctx(). Assigning the first group's store transport-wide
# sent every group's chat to one database and served it back to
# members of every other group (finding H1).
+ self._webrtc._ctx["groups"] = groups_ctx
self._webrtc._ctx["hub_ws"] = _WsSender(hub)
self._webrtc._ctx["node_user_id"] = session.user_id
self._webrtc._ctx["audit_store"] = self._audit_store
@@ -342,14 +342,15 @@ class NodeDaemon:
self._quic_server = QuicChunkServer(
sk_node=keys.sk_ed25519,
hub_pk_pem=session.hub_pk_pem,
- gek=first["gek"],
- roots=first["roots"],
- index=first["index"],
+ gek=first["gek"] if first else None,
+ roots=first["roots"] if first else None,
+ index=first["index"] if first else None,
host="::",
port=self._config.node.quic_port,
groups=groups_ctx,
denylist=denylist,
)
+ self._quic_server._ctx["groups"] = groups_ctx
await self._quic_server.start()
log.info("QUIC server on port %d (%d groups)",
self._config.node.quic_port, len(groups_ctx))
@@ -434,18 +435,23 @@ class NodeDaemon:
"yes" if self._webrtc else "no",
"yes" if self._quic_server else "no")
- # 11. Initial swarm registration — PUBLIC groups only.
- # Finding H7: registering every group's hashes hands the hub a content
- # fingerprint of every private file on the node, which is exactly the
- # metadata the "hub stores no content metadata" claim rules out. It also
- # lets anyone confirm whether a known file exists in the network.
- endpoint = f"webrtc:{self._config.node.quic_port}"
- for gctx in groups_ctx.values():
- if gctx.get("visibility") != "public":
- continue
- hashes = [e.id for e in gctx["index"].entries]
- if hashes:
- asyncio.ensure_future(self._register_swarm(hashes, endpoint))
+ # 11. Background initial scan — files appear progressively.
+ async def _bg_scan(indexer, name, gctx):
+ await indexer.initial_scan()
+ log.info("Background scan complete for %s: %d files",
+ name, indexer.index.count)
+ # Swarm registration for public groups (after files are known).
+ if gctx.get("visibility") == "public":
+ endpoint = f"webrtc:{self._config.node.quic_port}"
+ hashes = [e.id for e in gctx["index"].entries]
+ if hashes:
+ await self._register_swarm(hashes, endpoint)
+
+ for idx, group_cfg in zip(self._indexers, self._config.groups):
+ gctx = groups_ctx.get(group_cfg.id)
+ if gctx:
+ self._tasks.append(asyncio.create_task(
+ _bg_scan(idx, group_cfg.name, gctx)))
# 12. Wait for shutdown
stop_event = asyncio.Event()
@@ -486,7 +492,7 @@ class NodeDaemon:
log.error("Reload failed, keeping the running config: %s", e)
return
- groups_ctx = self._state.get("groups_ctx") or {}
+ groups_ctx = self._state.get("groups_ctx", {})
hosted = set(groups_ctx)
incoming = {g.id for g in fresh.groups if g.id}
@@ -647,7 +653,7 @@ class NodeDaemon:
log.warning(
"Node key not linked. Open the admin UI, copy this "
"node's key, and paste it in Settings > Link Node on "
- "%s. Retrying in 30s...",
+ "%s. Retrying in 5s...",
self._config.hub.url,
)
else:
@@ -655,10 +661,10 @@ class NodeDaemon:
log.warning(
"Hub rejected the node credentials for user %r. "
"Register that account on %s first, then link this "
- "node's key. Retrying in 30s...",
+ "node's key. Retrying in 5s...",
self._config.hub.username, self._config.hub.url,
)
- await asyncio.sleep(30)
+ await asyncio.sleep(5)
else:
raise
except Exception as e: