diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-19 14:01:38 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-19 14:01:38 +0200 |
| commit | d2495a2c4b89fbbfc18cefec83ae96cabdd745e2 (patch) | |
| tree | 463e4d11b1c742475aac07155818c9c64a1e43e5 /packages/meshbay-node/src/meshbay_node/ops.py | |
| parent | f8223293a211a87c92b1fed80f5ca53660f6b26c (diff) | |
| parent | 933daccbcfde7705413d3a10db87d910c650ed42 (diff) | |
| download | meshbay-d2495a2c4b89fbbfc18cefec83ae96cabdd745e2.tar.gz | |
Merge origin/main: the operator's upload ceiling beside the disk-thread work
One conflict, in §15.3's open list, and it was two changes agreeing rather than
disagreeing: this side removed the rows for the third-party search bound and the
node-announcement bound because both are now built (AV27, AV28), while the other
side kept them and added a new one. Resolved by keeping what is genuinely still
open — per-device revocation having no CLI — and leaving the two closed.
`webrtc_server.py` merged without conflict but the two sides met inside one
function: `_upload_chunk` gained the operator's `max_upload_gb` ceiling from
there and the per-group lock and `off_disk` calls from here. Read back rather
than trusted: the operator's ceiling now sits inside the critical section that
keeps chunk ordering, and the unlink beside it goes to the disk thread with
everything else.
2893 passed. The twelve `test_sticky_header.py[firefox]` setup errors are the
open Firefox on this machine, as before.
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/ops.py')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/ops.py | 85 |
1 files changed, 50 insertions, 35 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/ops.py b/packages/meshbay-node/src/meshbay_node/ops.py index 98f67c5..ca1b87d 100644 --- a/packages/meshbay-node/src/meshbay_node/ops.py +++ b/packages/meshbay-node/src/meshbay_node/ops.py @@ -40,6 +40,7 @@ from meshbay_common.crypto import ( from meshbay_node.config import DEFAULT_CONFIG_PATH from meshbay_common.join import ROLE_MEMBER, ROLE_OPERATOR from meshbay_node.roots import RootError, RootSet, off_disk +from meshbay_node.roster import Roster log = logging.getLogger(__name__) @@ -623,17 +624,11 @@ async def list_groups(state: dict) -> dict: members = await roster.list_members() has_operator = any(m["role"] == "operator" and m["status"] == "active" for m in members) - from meshbay_node.config import DEFAULT_STUN_SERVERS - nd = config.node if config else None - defaults = { - "invite_ttl_hours": nd.invite_ttl_hours if nd else 168, - "pair_ttl_hours": nd.pair_ttl_hours if nd else 24, - "device_request_ttl_minutes": nd.device_request_ttl_minutes if nd else 60, - "max_concurrent_streams": nd.max_concurrent_streams if nd else 8, - "transcode_incompatible_video": nd.transcode_incompatible_video if nd else True, - "stun_servers": nd.stun_servers if nd and nd.stun_servers else list(DEFAULT_STUN_SERVERS), - "ice_interfaces": nd.ice_interfaces if nd else [], - } + from meshbay_node.config import node_settings_defaults + # No config (a test, an unconfigured node) falls back to NodeConfig()'s own + # values rather than to numbers repeated here, which is the copy this used + # to be: it was missing three settings and reported them as null. + defaults = node_settings_defaults(config.node if config else None) if roster: settings = await roster.node_settings(defaults) else: @@ -1288,23 +1283,33 @@ async def clear_denylist(state: dict, *, subject: str = "") -> dict: # ── Node settings ──────────────────────────────────────────────────────────── +# What `set_node_settings` accepts, and how each value is validated. A module +# constant so a test can hold its key set against `Roster.node_setting_keys()`: +# this is the third list of the same settings, and the first two had already +# drifted apart once — the reader's defaults covered fewer settings than the +# resolver answered for, which is how node.toml's transfer pools came to be +# parsed and then ignored. The kinds here are the *writer's* validation and +# deliberately not the resolver's coercions. +NODE_SETTING_WRITERS: dict[str, tuple[str, str]] = { + "invite_ttl_hours": ("int", Roster.SETTING_INVITE_TTL), + "pair_ttl_hours": ("int", Roster.SETTING_PAIR_TTL), + "device_request_ttl_minutes": ("int", Roster.SETTING_DEVICE_TTL), + "max_concurrent_streams": ("int", Roster.SETTING_MAX_STREAMS), + "max_concurrent_downloads": ("int", Roster.SETTING_MAX_DOWNLOADS), + "max_concurrent_uploads": ("int", Roster.SETTING_MAX_UPLOADS), + "max_upload_gb": ("size", Roster.SETTING_MAX_UPLOAD_GB), + "transcode_incompatible_video": ("bool", Roster.SETTING_TRANSCODE), + "stun_servers": ("stun_list", Roster.SETTING_STUN_SERVERS), + "ice_interfaces": ("list", Roster.SETTING_ICE_INTERFACES), +} + + async def get_node_settings(state: dict) -> dict: """Return current effective node settings.""" - from meshbay_node.config import DEFAULT_STUN_SERVERS + from meshbay_node.config import node_settings_defaults roster = _roster(state) config = _config(state) - nd = config.node - defaults = { - "invite_ttl_hours": nd.invite_ttl_hours, - "pair_ttl_hours": nd.pair_ttl_hours, - "device_request_ttl_minutes": nd.device_request_ttl_minutes, - "max_concurrent_streams": nd.max_concurrent_streams, - "max_concurrent_downloads": nd.max_concurrent_downloads, - "max_concurrent_uploads": nd.max_concurrent_uploads, - "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, - } + defaults = node_settings_defaults(config.node) if roster: return await roster.node_settings(defaults) return defaults @@ -1316,17 +1321,7 @@ async def set_node_settings(state: dict, settings: dict) -> dict: nd = config.node conf_path = Path(state.get("config_path") or DEFAULT_CONFIG_PATH) - allowed_keys = { - "invite_ttl_hours": ("int", roster.SETTING_INVITE_TTL), - "pair_ttl_hours": ("int", roster.SETTING_PAIR_TTL), - "device_request_ttl_minutes": ("int", roster.SETTING_DEVICE_TTL), - "max_concurrent_streams": ("int", roster.SETTING_MAX_STREAMS), - "max_concurrent_downloads": ("int", roster.SETTING_MAX_DOWNLOADS), - "max_concurrent_uploads": ("int", roster.SETTING_MAX_UPLOADS), - "transcode_incompatible_video": ("bool", roster.SETTING_TRANSCODE), - "stun_servers": ("stun_list", roster.SETTING_STUN_SERVERS), - "ice_interfaces": ("list", roster.SETTING_ICE_INTERFACES), - } + allowed_keys = NODE_SETTING_WRITERS set_by = state.get("node_user_id", "") updated = {} @@ -1344,6 +1339,22 @@ async def set_node_settings(state: dict, settings: dict) -> dict: setattr(nd, key, v) await roster.set_node_setting(setting_key, str(v), set_by) updated[key] = v + elif kind == "size": + # A quantity, not a count: half a gigabyte is a legitimate ceiling + # on a small disk, so this one is not run through the `int` branch + # above, whose floor of 1 would round it to "refuse everything". + # bool before float, as config.py does it: `true` is not 1 GB. + if isinstance(value, bool): + raise OpError(f"{key} must be a number") + try: + fv = float(value) + except (TypeError, ValueError): + raise OpError(f"{key} must be a number") + if fv <= 0: + raise OpError(f"{key} must be greater than zero") + setattr(nd, key, fv) + await roster.set_node_setting(setting_key, repr(fv), set_by) + updated[key] = fv elif kind == "bool": v = bool(value) setattr(nd, key, v) @@ -1383,6 +1394,10 @@ async def set_node_settings(state: dict, settings: dict) -> dict: "max_concurrent_downloads"), max_concurrent_uploads=updated.get( "max_concurrent_uploads")) + if "max_upload_gb" in updated: + webrtc = state.get("webrtc") + if webrtc is not None: + webrtc.set_capacity(max_upload_gb=updated["max_upload_gb"]) if "stun_servers" in updated: webrtc = state.get("webrtc") if webrtc and hasattr(webrtc, '_stun'): |