From c2c49182e6d4f1f1ef3ed3ba5e8699912f9a27bd Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Sat, 19 Sep 2026 01:02:05 +0200 Subject: feat(node): per-file upload ceiling is an operator setting, default 8 GB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Was a 4 GB constant in webrtc_server.py, the same on a small board and on a machine holding a library. Now max_upload_gb in node.toml, on the Node page and via `meshbay-node transfers max-size`, read from the transport context per chunk so a change reaches an upload already running. MESHBAY_DESIGN.md §6.4; §15.3 records a defect found beside it. Co-Authored-By: Claude Opus 5 --- packages/meshbay-node/src/meshbay_node/daemon.py | 47 +++++++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) (limited to 'packages/meshbay-node/src/meshbay_node/daemon.py') diff --git a/packages/meshbay-node/src/meshbay_node/daemon.py b/packages/meshbay-node/src/meshbay_node/daemon.py index 26f73fc..1525205 100644 --- a/packages/meshbay-node/src/meshbay_node/daemon.py +++ b/packages/meshbay-node/src/meshbay_node/daemon.py @@ -300,6 +300,7 @@ class NodeDaemon: "pair_ttl_hours": nd.pair_ttl_hours, "device_request_ttl_minutes": nd.device_request_ttl_minutes, "max_concurrent_streams": nd.max_concurrent_streams, + "max_upload_gb": nd.max_upload_gb, "transcode_incompatible_video": nd.transcode_incompatible_video, "stun_servers": nd.stun_servers if nd.stun_servers else list(DEFAULT_STUN_SERVERS), "ice_interfaces": nd.ice_interfaces, @@ -539,6 +540,7 @@ class NodeDaemon: max_concurrent_streams=self._config.node.max_concurrent_streams, max_concurrent_downloads=self._config.node.max_concurrent_downloads, max_concurrent_uploads=self._config.node.max_concurrent_uploads, + max_upload_gb=self._config.node.max_upload_gb, transcode_incompatible_video=self._config.node.transcode_incompatible_video, stun_servers=self._config.node.stun_servers or None, ) @@ -1971,9 +1973,10 @@ def main() -> None: "| chat status|rotate|encrypt-history|prune " "| denylist show|clear " "| stun list|add|remove|reset " - "| transfers show|set|per-member: live transfer " - "slots, the node-wide caps, and how many one " - "member may run at once in a group " + "| transfers show|set|max-size|per-member: live " + "transfer slots, the node-wide caps, the largest " + "single upload, and how many one member may run " + "at once in a group " "| reload: re-read node.toml (hot; systemd or the " "loopback API) | restart-daemon: restart the node " "(systemd unit, the Windows autostart launcher, or the " @@ -1991,13 +1994,14 @@ def main() -> None: "init|rotate for gek; " "list|rm for file; rematch for video; show|clear for " "denylist; list|add|remove|reset for stun; " - "show|set|per-member for transfers; " + "show|set|max-size|per-member for transfers; " "install|remove|start|stop|status for autostart and " "for service") parser.add_argument("target", nargs="?", help="username for member invite|revoke|unpin; group name " "for group add; file id for file rm; identifier for " - "denylist clear; download cap for transfers set") + "denylist clear; download cap for transfers set; " + "size in GB for transfers max-size") parser.add_argument("value", nargs="?", help="the second value where a verb takes two: the " "upload cap for transfers set") @@ -2733,6 +2737,14 @@ def main() -> None: for kind, pool in out.get("pools", {}).items(): print(f" {kind:<9} {pool['in_use']}/{pool['cap']} in use, " f"{pool['queued']} queued (node-wide)") + # From the daemon, not from node.toml: a change made on the Node + # page is live before it is written back, and the number to print + # is the one being enforced. The local file is the fallback rather + # than a literal, so there is no second copy of the default here. + settings = _daemon_api(cfg, "/api/node-settings") + gb = settings.get("max_upload_gb") or cfg.node.max_upload_gb + print(f"\n largest single upload: {gb:g} GB per file " + f"(meshbay-node transfers max-size )") # Per group, because that is the cap that decides how many one # person runs at once — and it is not the node-wide number. An # operator raising `transfers set 8 8` and still seeing two at a @@ -2782,6 +2794,29 @@ def main() -> None: f"(applied now, and kept in node.toml)") return + if sub == "max-size": + # The largest single file a member may upload here. Not a + # concurrency cap like `set` — it is the one limit that bounds what + # a member writes to the operator's disk, which is why it lives + # beside them rather than under a verb of its own. + if not args.target: + print("usage: meshbay-node transfers max-size ") + sys.exit(1) + try: + gb = float(args.target) + except ValueError: + print("error: the size must be a number of GB (e.g. 8, or 0.5)") + sys.exit(1) + if gb <= 0: + print("error: a ceiling of zero is not 'unlimited'; it would " + "refuse every upload. Make the root read-only instead.") + sys.exit(1) + _daemon_api(cfg, "/api/node-settings", method="PUT", + body={"max_upload_gb": gb}) + print(f"largest single upload: {gb:g} GB per file " + f"(applied now, and kept in node.toml)") + return + if sub == "per-member": # How many transfers ONE member may run at once in this group. Not # the same knob as `set`, which is the machine's total — and the @@ -2815,7 +2850,7 @@ def main() -> None: return print("usage: meshbay-node transfers show|set |" - "per-member [--group NAME]") + "max-size |per-member [--group NAME]") sys.exit(1) if args.command == "file": -- cgit v1.2.3