From 6cdc6016d72dcfb7530ac38a8fa92232418ac305 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Mon, 5 Oct 2026 11:10:50 +0200 Subject: fix(node): plug an auto-ejected removable root back once its files return A node started with the desktop session runs before the session has mounted its USB drives. The safety net then auto-ejected every removable root and persisted it exactly like an operator's eject, so after each reboot those roots stayed ejected until someone plugged them by hand (seen on a node whose /media drives were mounted a minute after it started). An auto-eject is now stored as such ("auto" in roster.db). At startup and at every reconcile, an auto-ejected root whose path is readable again is checked against a few files the hash cache knows under it, at the same path with the same size and mtime; one found and the root is plugged back and rescanned. An empty mount point or another drive in its place is not recognised and stays ejected. An operator's eject is never undone automatically. Co-Authored-By: Claude Opus 5.5 --- packages/meshbay-node/src/meshbay_node/roster.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) (limited to 'packages/meshbay-node/src/meshbay_node/roster.py') diff --git a/packages/meshbay-node/src/meshbay_node/roster.py b/packages/meshbay-node/src/meshbay_node/roster.py index 0116aaa..eb8c031 100644 --- a/packages/meshbay-node/src/meshbay_node/roster.py +++ b/packages/meshbay-node/src/meshbay_node/roster.py @@ -686,9 +686,11 @@ class Roster: return cls.SETTING_ROOT_EJECTED_PREFIX + fold(root_name) async def set_root_ejected(self, group_id: str, root_name: str, - ejected: bool, set_by: str = "") -> None: - await self.set_setting(group_id, self.root_ejected_key(root_name), - "1" if ejected else "0", set_by) + ejected: bool, set_by: str = "", + auto: bool = False) -> None: + """`auto`: ejected by the safety net, not by the operator.""" + value = ("auto" if auto else "1") if ejected else "0" + await self.set_setting(group_id, self.root_ejected_key(root_name), value, set_by) async def ejected_roots(self, group_id: str) -> set[str]: """ @@ -705,7 +707,17 @@ class Roster: (group_id,)) as cur: rows = await cur.fetchall() return {r["key"][len(prefix):] for r in rows - if r["key"].startswith(prefix) and r["value"] == "1"} + if r["key"].startswith(prefix) and r["value"] in ("1", "auto")} + + async def auto_ejected_roots(self, group_id: str) -> set[str]: + """The folded names of the roots the safety net ejected, a subset of the above.""" + prefix = self.SETTING_ROOT_EJECTED_PREFIX + async with self._db.execute( + "SELECT key, value FROM group_settings WHERE group_id = ?", + (group_id,)) as cur: + rows = await cur.fetchall() + return {r["key"][len(prefix):] for r in rows + if r["key"].startswith(prefix) and r["value"] == "auto"} async def get_setting(self, group_id: str, key: str, default: str | None = None) -> str | None: -- cgit v1.2.3