feat: update dependencies to RNS 1.4.0 and LXMF 1.1.0

This commit is contained in:
Ivan 2026-07-20 12:57:26 -05:00
parent 98a5dd7c50
commit c0b0ddffd7
No known key found for this signature in database
34 changed files with 362 additions and 69 deletions

View file

@ -50,9 +50,9 @@ runs:
if command -v python >/dev/null 2>&1; then
py=python
fi
if "$py" -m pip install --user 'rns>=1.3.9' 2>/dev/null; then
if "$py" -m pip install --user 'rns>=1.4.0' 2>/dev/null; then
:
elif "$py" -m pip install --user --break-system-packages 'rns>=1.3.9' 2>/dev/null; then
elif "$py" -m pip install --user --break-system-packages 'rns>=1.4.0' 2>/dev/null; then
:
else
echo "WARNING: could not install rnid via pip (tree verify may be skipped)" >&2

View file

@ -20,6 +20,7 @@ All notable changes to this project will be documented in this file.
- LXMFy 1.6.5 vendor refresh, wasmtime, mutation test tasks
- Network visualiser WebGL + WASM renderer (vis-network fallback) and Settings renderer preference
- Interfaces: internal mode, recursive path requests, announces-from-internal, discovery location command, and Backbone fast-flapping options (RNS 1.3.7 to 1.3.9)
- Dependencies: **RNS** 1.4.0 and **LXMF** 1.1.0, with local propagation node controls for sequential stamp validation, static-peer bypass, max inbound syncs, transfer size reporting, and inbound delivery cancel
### Changed

View file

@ -13,7 +13,7 @@ def pythonTempDir = new File(buildDir, "python-tmp")
def vendorWheelDir = new File(rootProject.projectDir, "vendor")
def lxstPatchedWheel = new File(rootProject.projectDir, "vendor/lxst-0.4.8-py3-none-any.whl")
def bleakPatchedWheel = new File(rootProject.projectDir, "vendor/bleak-3.0.2-py3-none-any.whl")
def rnsPatchedWheel = new File(rootProject.projectDir, "vendor/rns-1.3.9-py3-none-any.whl")
def rnsPatchedWheel = new File(rootProject.projectDir, "vendor/rns-1.4.0-py3-none-any.whl")
def allAndroidAbis = ["arm64-v8a", "x86_64", "armeabi-v7a"]
def selectedAndroidAbis = (
project.findProperty("meshchatxAbis")
@ -341,7 +341,7 @@ chaquopy {
// shim, usb4a context bridge, and able BLE stack so USB/BT/BLE RNode can
// run. RNode over TCP needs neither native module and always works.
install rnsPatchedWheel.absolutePath
install "lxmf>=1.0.1"
install "lxmf>=1.1.0"
install "numpy==1.26.2"
install "chaquopy-libcodec2==1.2.0"
install "pycodec2==4.1.1"

Binary file not shown.

View file

@ -3851,7 +3851,7 @@ class ReticulumMeshChat:
if isinstance(stats, dict)
else _numeric(getattr(router, "propagation_per_sync_limit", 0))
)
return {
result = {
"is_running": is_running,
"identity_hash": ctx.identity.hash.hex(),
"destination_hash": destination_hash,
@ -3890,7 +3890,21 @@ class ReticulumMeshChat:
else getattr(router, "propagation_stamp_cost", 0)
),
),
"inbound_delivery_count": 0,
"max_inbound_syncs": int(
getattr(router, "propagation_max_inbound_syncs", 0) or 0,
),
"sequential_validation": bool(
getattr(router, "propagation_sequential_validation", True),
),
"static_peers_bypass_sequential": not bool(
getattr(router, "propagation_static_peer_sequential", False),
),
}
if hasattr(router, "inbound_count"):
with contextlib.suppress(Exception):
result["inbound_delivery_count"] = int(router.inbound_count() or 0)
return result
def _get_reticulum_section(self):
try:
@ -12494,16 +12508,24 @@ class ReticulumMeshChat:
router = self.message_router
current_state = None
current_progress = 0.0
transfer_size = None
if router is not None:
try:
current_state = router.propagation_transfer_state
current_progress = router.propagation_transfer_progress
transfer_size = getattr(
router,
"propagation_transfer_size",
None,
)
# COMPLETE is terminal, so reset to idle so the UI does not keep
# looking "busy" after a finished auto/manual sync.
if current_state == router.PR_COMPLETE:
with contextlib.suppress(Exception):
router.propagation_transfer_state = router.PR_IDLE
router.propagation_transfer_progress = 0.0
if hasattr(router, "propagation_transfer_size"):
router.propagation_transfer_size = None
except Exception:
pass
sync_metrics = self._collect_propagation_sync_metrics()
@ -12521,6 +12543,13 @@ class ReticulumMeshChat:
last_result = None
if current_state is None:
current_state = 0
transfer_size_bytes = None
if isinstance(transfer_size, (int, float)) and transfer_size > 0:
transfer_size_bytes = int(transfer_size)
inbound_delivery_count = 0
if router is not None and hasattr(router, "inbound_count"):
with contextlib.suppress(Exception):
inbound_delivery_count = int(router.inbound_count() or 0)
return web.json_response(
{
"propagation_node_status": {
@ -12528,6 +12557,8 @@ class ReticulumMeshChat:
current_state,
),
"progress": progress_pct,
"transfer_size_bytes": transfer_size_bytes,
"inbound_delivery_count": inbound_delivery_count,
"messages_received": last_result,
"messages_stored": sync_metrics["messages_stored"],
"delivery_confirmations": sync_metrics[
@ -12578,6 +12609,32 @@ class ReticulumMeshChat:
},
)
@routes.post("/api/v1/lxmf/propagation-node/cancel-inbound")
async def propagation_node_cancel_inbound(request):
router = self.message_router
if router is None or not hasattr(router, "cancel_all_inbound"):
return web.json_response(
{
"message": "Inbound delivery cancellation is unavailable.",
},
status=503,
)
try:
cancelled = int(router.cancel_all_inbound() or 0)
except Exception as exc:
return web.json_response(
{
"message": f"Failed to cancel inbound deliveries: {exc}",
},
status=500,
)
return web.json_response(
{
"message": f"Cancelled {cancelled} inbound deliveries",
"cancelled": cancelled,
},
)
@routes.post("/api/v1/lxmf/propagation-node/stop")
async def propagation_node_stop(request):
self.config.lxmf_local_propagation_node_enabled.set(False)
@ -18655,6 +18712,34 @@ class ReticulumMeshChat:
if self.config.lxmf_local_propagation_node_enabled.get():
self.message_router.announce_propagation_node()
if "lxmf_propagation_sequential_validation" in data:
value = self._parse_bool(
data["lxmf_propagation_sequential_validation"],
)
self.config.lxmf_propagation_sequential_validation.set(value)
if self.message_router is not None:
self.message_router.propagation_sequential_validation = value
if "lxmf_propagation_static_peers_bypass_sequential" in data:
value = self._parse_bool(
data["lxmf_propagation_static_peers_bypass_sequential"],
)
self.config.lxmf_propagation_static_peers_bypass_sequential.set(value)
if self.message_router is not None:
self.message_router.propagation_static_peer_sequential = not value
if "lxmf_propagation_max_inbound_syncs" in data:
value = self._coerce_int(data["lxmf_propagation_max_inbound_syncs"])
if value is None:
value = self.config.lxmf_propagation_max_inbound_syncs.get()
if value < 1:
value = 1
elif value > 64:
value = 64
self.config.lxmf_propagation_max_inbound_syncs.set(value)
if self.message_router is not None:
self.message_router.propagation_max_inbound_syncs = value
# update auto sync interval
if "lxmf_preferred_propagation_node_auto_sync_interval_seconds" in data:
value = self._coerce_int(
@ -20896,6 +20981,9 @@ class ReticulumMeshChat:
"lxmf_user_icon_background_colour": ctx.config.lxmf_user_icon_background_colour.get(),
"lxmf_inbound_stamp_cost": ctx.config.lxmf_inbound_stamp_cost.get(),
"lxmf_propagation_node_stamp_cost": ctx.config.lxmf_propagation_node_stamp_cost.get(),
"lxmf_propagation_sequential_validation": ctx.config.lxmf_propagation_sequential_validation.get(),
"lxmf_propagation_static_peers_bypass_sequential": ctx.config.lxmf_propagation_static_peers_bypass_sequential.get(),
"lxmf_propagation_max_inbound_syncs": ctx.config.lxmf_propagation_max_inbound_syncs.get(),
"lxmf_flood_protection_enabled": ctx.config.lxmf_flood_protection_enabled.get(),
"lxmf_flood_threshold_per_minute": ctx.config.lxmf_flood_threshold_per_minute.get(),
"lxmf_flood_max_stamp_cost": ctx.config.lxmf_flood_max_stamp_cost.get(),

View file

@ -106,6 +106,21 @@ class ConfigManager:
"lxmf_propagation_node_stamp_cost",
16,
) # for propagation node messages
self.lxmf_propagation_sequential_validation = self.BoolConfig(
self,
"lxmf_propagation_sequential_validation",
True,
)
self.lxmf_propagation_static_peers_bypass_sequential = self.BoolConfig(
self,
"lxmf_propagation_static_peers_bypass_sequential",
True,
)
self.lxmf_propagation_max_inbound_syncs = self.IntConfig(
self,
"lxmf_propagation_max_inbound_syncs",
3,
)
self.lxmf_inbound_stamp_cost_before_block = self.IntConfig(
self,
"lxmf_inbound_stamp_cost_before_block",

View file

@ -46,7 +46,7 @@ frozenlist 1.8.0
idna 3.18
License: BSD-3-Clause
Author: Kim Davies <kim+pypi@gumleaf.org>
lxmf 1.0.1
lxmf 1.1.0
License: Reticulum License
Author: Mark Qvist
lxmfy 1.6.5
@ -88,7 +88,7 @@ pyserial 3.5
reticulum-meshchatx 4.8.0
License: 0BSD AND MIT
Author: Quad4
rns 1.3.9
rns 1.4.0
License: Reticulum License
Author: Mark Qvist
wasmtime 46.0.1

View file

@ -8,7 +8,7 @@
{
"name": "aiohttp",
"version": "3.14.1",
"author": "",
"author": "\u2014",
"license": "Apache-2.0 AND MIT"
},
{
@ -32,7 +32,7 @@
{
"name": "audioop-lts",
"version": "0.2.2",
"author": "",
"author": "\u2014",
"license": "PSF-2.0"
},
{
@ -50,7 +50,7 @@
{
"name": "cbor2",
"version": "6.1.1",
"author": "Alex Grönholm <alex.gronholm@nextday.fi>",
"author": "Alex Gr\u00f6nholm <alex.gronholm@nextday.fi>",
"license": "MIT"
},
{
@ -85,7 +85,7 @@
},
{
"name": "lxmf",
"version": "1.0.1",
"version": "1.1.0",
"author": "Mark Qvist",
"license": "Reticulum License"
},
@ -169,7 +169,7 @@
},
{
"name": "rns",
"version": "1.3.9",
"version": "1.4.0",
"author": "Mark Qvist",
"license": "Reticulum License"
},

View file

@ -256,10 +256,22 @@ class IdentityContext:
# 4. Initialize LXMF Router
propagation_stamp_cost = self.config.lxmf_propagation_node_stamp_cost.get()
max_inbound_syncs = self.config.lxmf_propagation_max_inbound_syncs.get()
if not isinstance(max_inbound_syncs, int) or max_inbound_syncs < 1:
max_inbound_syncs = 3
sequential_validation = bool(
self.config.lxmf_propagation_sequential_validation.get(),
)
static_sequential = not bool(
self.config.lxmf_propagation_static_peers_bypass_sequential.get(),
)
self.message_router = create_lxmf_router(
identity=self.identity,
storagepath=self.lxmf_router_path,
propagation_cost=propagation_stamp_cost,
max_inbound_syncs=max_inbound_syncs,
sequential_validation=sequential_validation,
static_sequential=static_sequential,
)
self.message_router.PROCESSING_INTERVAL = 1
self.message_router.delivery_per_transfer_limit = (

View file

@ -340,7 +340,7 @@ class InterfaceEditor:
interface_details: dict,
data: dict,
) -> str | None:
"""Persist BackboneInterface fast-flapping options (RNS 1.3.9)."""
"""Persist BackboneInterface fast-flapping options (RNS 1.4.0)."""
err = InterfaceEditor.apply_yes_no_option(
interface_details,
data,

View file

@ -11,7 +11,14 @@ import RNS.vendor.umsgpack as msgpack
from LXMF import LXMRouter
def create_lxmf_router(identity, storagepath, propagation_cost=None):
def create_lxmf_router(
identity,
storagepath,
propagation_cost=None,
max_inbound_syncs=None,
sequential_validation=None,
static_sequential=None,
):
"""Construct an LXMF.LXMRouter without signal-handler crashes off the main thread.
signal.signal only works on the main thread; on workers it is temporarily
@ -20,23 +27,27 @@ def create_lxmf_router(identity, storagepath, propagation_cost=None):
if propagation_cost is None:
propagation_cost = 0
kwargs = {
"identity": identity,
"storagepath": storagepath,
"propagation_cost": propagation_cost,
}
if max_inbound_syncs is not None:
kwargs["max_inbound_syncs"] = max_inbound_syncs
if sequential_validation is not None:
kwargs["sequential_validation"] = sequential_validation
if static_sequential is not None:
kwargs["static_sequential"] = static_sequential
if threading.current_thread() != threading.main_thread():
original_signal = signal.signal
try:
signal.signal = lambda s, h: None
return LXMF.LXMRouter(
identity=identity,
storagepath=storagepath,
propagation_cost=propagation_cost,
)
return LXMF.LXMRouter(**kwargs)
finally:
signal.signal = original_signal
else:
return LXMF.LXMRouter(
identity=identity,
storagepath=storagepath,
propagation_cost=propagation_cost,
)
return LXMF.LXMRouter(**kwargs)
def parse_bool_query_param(value: str | None) -> bool:

View file

@ -293,7 +293,7 @@ class RNSHSession:
# same shared instance and rpc_key) as the MeshChatX app. Without this
# rnsh bootstraps its own default config, becomes a mismatched shared
# instance client and fails RPC auth with "digest sent was rejected".
# RNS 1.3.9+: --rnsconfig is the Reticulum config dir. -c/--config is
# RNS 1.4.0+: --rnsconfig is the Reticulum config dir. -c/--config is
# the rnsh config directory (HOME/.rnsh via _build_env).
config_path = self.resolved_config_dir
if config_path:

View file

@ -3699,6 +3699,59 @@
{{ $t("app.propagation_stamp_description") }}
</div>
</div>
<label
v-if="config.lxmf_local_propagation_node_enabled"
class="setting-toggle"
>
<Toggle
id="propagation-sequential-validation"
v-model="config.lxmf_propagation_sequential_validation"
@update:model-value="onLxmfPropagationSequentialValidationChange"
/>
<span class="setting-toggle__label">
<span class="setting-toggle__title">{{
$t("app.propagation_sequential_validation")
}}</span>
<span class="setting-toggle__description">{{
$t("app.propagation_sequential_validation_description")
}}</span>
</span>
</label>
<label
v-if="config.lxmf_local_propagation_node_enabled"
class="setting-toggle"
>
<Toggle
id="propagation-static-peers-bypass-sequential"
v-model="config.lxmf_propagation_static_peers_bypass_sequential"
@update:model-value="onLxmfPropagationStaticPeersBypassChange"
/>
<span class="setting-toggle__label">
<span class="setting-toggle__title">{{
$t("app.propagation_static_peers_bypass_sequential")
}}</span>
<span class="setting-toggle__description">{{
$t("app.propagation_static_peers_bypass_sequential_description")
}}</span>
</span>
</label>
<div v-if="config.lxmf_local_propagation_node_enabled" class="space-y-2">
<div class="text-sm font-medium text-gray-900 dark:text-gray-100">
{{ $t("app.propagation_max_inbound_syncs") }}
</div>
<input
v-model.number="config.lxmf_propagation_max_inbound_syncs"
type="number"
min="1"
max="64"
placeholder="3"
class="input-field"
@input="onLxmfPropagationMaxInboundSyncsChange"
/>
<div class="text-xs text-gray-600 dark:text-gray-400">
{{ $t("app.propagation_max_inbound_syncs_description") }}
</div>
</div>
</div>
</section>
@ -3893,6 +3946,9 @@ export default {
lxmf_propagation_transfer_limit_in_bytes: 1000 * 256,
lxmf_propagation_sync_limit_in_bytes: 1000 * 10240,
lxmf_local_propagation_node_enabled: null,
lxmf_propagation_sequential_validation: true,
lxmf_propagation_static_peers_bypass_sequential: true,
lxmf_propagation_max_inbound_syncs: 3,
lxmf_preferred_propagation_node_destination_hash: null,
lxmf_preferred_propagation_node_auto_select: null,
archives_max_storage_gb: 1,
@ -5351,6 +5407,30 @@ export default {
);
}, 1000);
},
async onLxmfPropagationSequentialValidationChange(value) {
await this.updateConfig({
lxmf_propagation_sequential_validation: value,
});
},
async onLxmfPropagationStaticPeersBypassChange(value) {
await this.updateConfig({
lxmf_propagation_static_peers_bypass_sequential: value,
});
},
async onLxmfPropagationMaxInboundSyncsChange() {
if (this.saveTimeouts.propagation_max_inbound_syncs) {
clearTimeout(this.saveTimeouts.propagation_max_inbound_syncs);
}
this.saveTimeouts.propagation_max_inbound_syncs = setTimeout(async () => {
let v = Number(this.config.lxmf_propagation_max_inbound_syncs);
if (!v || v < 1) v = 1;
else if (v > 64) v = 64;
this.config.lxmf_propagation_max_inbound_syncs = v;
await this.updateConfig({
lxmf_propagation_max_inbound_syncs: v,
});
}, 1000);
},
async onLxmfFloodProtectionEnabledChange(value) {
await this.updateConfig({
lxmf_flood_protection_enabled: value,

View file

@ -358,6 +358,12 @@ export const CORE_SETTINGS_SECTION_KEYWORDS = {
"app.auto_sync_interval",
"app.propagation_stamp_cost",
"app.propagation_stamp_description",
"app.propagation_sequential_validation",
"app.propagation_sequential_validation_description",
"app.propagation_static_peers_bypass_sequential",
"app.propagation_static_peers_bypass_sequential_description",
"app.propagation_max_inbound_syncs",
"app.propagation_max_inbound_syncs_description",
],
location: [
"app.location",

View file

@ -501,7 +501,13 @@
"remote_management_allowed": "Remote management allow-list",
"remote_management_allowed_description": "Identity hashes allowed to query this instance remotely with rnstatus/rnpath. One 32-character hex hash per line.",
"remote_management_allowed_placeholder": "9fb6d773498fb3feda407ed8ef2c3229",
"remote_management_allowed_save": "Save allow-list"
"remote_management_allowed_save": "Save allow-list",
"propagation_sequential_validation": "Sequential PN Stamp Validation",
"propagation_sequential_validation_description": "Validate inbound propagation sync stamp batches one at a time. Other peers receive a throttle response while a batch is validating. Disable only on fast hosts.",
"propagation_static_peers_bypass_sequential": "Static Peers Bypass Sequential Validation",
"propagation_static_peers_bypass_sequential_description": "Allow configured static peers to sync immediately even when another stamp batch is already validating.",
"propagation_max_inbound_syncs": "Max Concurrent Inbound Syncs",
"propagation_max_inbound_syncs_description": "How many concurrent inbound propagation sync transfers to accept. Extra offers are throttled. Range: 1-64. Default: 3."
},
"common": {
"open": "Öffnen",
@ -1386,7 +1392,7 @@
"location_cmd_placeholder": "/path/to/gps-script",
"location_cmd_hint": "Absoluter Pfad zu einem Programm, das latitude,longitude,height ausgibt. Reticulum startet es für Discovery-Announces (RNS location_cmd).",
"block_fast_flapping_label": "Schnell wechselnde Clients blockieren",
"block_fast_flapping_hint": "Ignoriert Clients, die sich zu schnell verbinden und trennen (RNS 1.3.9 BackboneInterface-Standard).",
"block_fast_flapping_hint": "Ignoriert Clients, die sich zu schnell verbinden und trennen (RNS 1.4.0 BackboneInterface-Standard).",
"fast_flapping_block_time_label": "Sperrzeit (Minuten)",
"fast_flapping_threshold_label": "Schwelle (Sekunden)",
"fast_flapping_grace_label": "Kulanz-Flaps"

View file

@ -275,6 +275,12 @@
"last_synced_never": "Last synced: never.",
"propagation_stamp_cost": "Propagation Node Stamp Cost",
"propagation_stamp_description": "Require proof-of-work stamps for messages sent through your node. Higher values require more work. Range: 13-254. Default: 16. **Note:** Changing this requires a restart.",
"propagation_sequential_validation": "Sequential PN Stamp Validation",
"propagation_sequential_validation_description": "Validate inbound propagation sync stamp batches one at a time. Other peers receive a throttle response while a batch is validating. Disable only on fast hosts.",
"propagation_static_peers_bypass_sequential": "Static Peers Bypass Sequential Validation",
"propagation_static_peers_bypass_sequential_description": "Allow configured static peers to sync immediately even when another stamp batch is already validating.",
"propagation_max_inbound_syncs": "Max Concurrent Inbound Syncs",
"propagation_max_inbound_syncs_description": "How many concurrent inbound propagation sync transfers to accept. Extra offers are throttled. Range: 1-64. Default: 3.",
"language": "Language",
"select_language": "Select your preferred language.",
"tagline": "All-in-one Reticulum client",
@ -1261,7 +1267,7 @@
"location_cmd_placeholder": "/path/to/gps-script",
"location_cmd_hint": "Absolute path to an executable that prints latitude,longitude,height. Reticulum runs it for discovery announces (RNS location_cmd).",
"block_fast_flapping_label": "Block fast-flapping clients",
"block_fast_flapping_hint": "Ignore clients that connect and disconnect too quickly (RNS 1.3.9 BackboneInterface defaults).",
"block_fast_flapping_hint": "Ignore clients that connect and disconnect too quickly (RNS 1.4.0 BackboneInterface defaults).",
"fast_flapping_block_time_label": "Block time (minutes)",
"fast_flapping_threshold_label": "Threshold (seconds)",
"fast_flapping_grace_label": "Grace flaps",

View file

@ -501,7 +501,13 @@
"remote_management_allowed": "Remote management allow-list",
"remote_management_allowed_description": "Identity hashes allowed to query this instance remotely with rnstatus/rnpath. One 32-character hex hash per line.",
"remote_management_allowed_placeholder": "9fb6d773498fb3feda407ed8ef2c3229",
"remote_management_allowed_save": "Save allow-list"
"remote_management_allowed_save": "Save allow-list",
"propagation_sequential_validation": "Sequential PN Stamp Validation",
"propagation_sequential_validation_description": "Validate inbound propagation sync stamp batches one at a time. Other peers receive a throttle response while a batch is validating. Disable only on fast hosts.",
"propagation_static_peers_bypass_sequential": "Static Peers Bypass Sequential Validation",
"propagation_static_peers_bypass_sequential_description": "Allow configured static peers to sync immediately even when another stamp batch is already validating.",
"propagation_max_inbound_syncs": "Max Concurrent Inbound Syncs",
"propagation_max_inbound_syncs_description": "How many concurrent inbound propagation sync transfers to accept. Extra offers are throttled. Range: 1-64. Default: 3."
},
"common": {
"open": "Abierto",
@ -1334,7 +1340,7 @@
"location_cmd_placeholder": "/path/to/gps-script",
"location_cmd_hint": "Ruta absoluta a un ejecutable que imprime latitude,longitude,height. Reticulum lo ejecuta para announces de discovery (RNS location_cmd).",
"block_fast_flapping_label": "Bloquear clientes de flapping rápido",
"block_fast_flapping_hint": "Ignora clientes que se conectan y desconectan demasiado rápido (valores por defecto de BackboneInterface en RNS 1.3.9).",
"block_fast_flapping_hint": "Ignora clientes que se conectan y desconectan demasiado rápido (valores por defecto de BackboneInterface en RNS 1.4.0).",
"fast_flapping_block_time_label": "Tiempo de bloqueo (minutos)",
"fast_flapping_threshold_label": "Umbral (segundos)",
"fast_flapping_grace_label": "Flaps de gracia"

View file

@ -501,7 +501,13 @@
"remote_management_allowed": "Remote management allow-list",
"remote_management_allowed_description": "Identity hashes allowed to query this instance remotely with rnstatus/rnpath. One 32-character hex hash per line.",
"remote_management_allowed_placeholder": "9fb6d773498fb3feda407ed8ef2c3229",
"remote_management_allowed_save": "Save allow-list"
"remote_management_allowed_save": "Save allow-list",
"propagation_sequential_validation": "Sequential PN Stamp Validation",
"propagation_sequential_validation_description": "Validate inbound propagation sync stamp batches one at a time. Other peers receive a throttle response while a batch is validating. Disable only on fast hosts.",
"propagation_static_peers_bypass_sequential": "Static Peers Bypass Sequential Validation",
"propagation_static_peers_bypass_sequential_description": "Allow configured static peers to sync immediately even when another stamp batch is already validating.",
"propagation_max_inbound_syncs": "Max Concurrent Inbound Syncs",
"propagation_max_inbound_syncs_description": "How many concurrent inbound propagation sync transfers to accept. Extra offers are throttled. Range: 1-64. Default: 3."
},
"common": {
"open": "Avaa",
@ -1334,7 +1340,7 @@
"location_cmd_placeholder": "/path/to/gps-script",
"location_cmd_hint": "Absoluuttinen polku ohjelmaan, joka tulostaa latitude,longitude,height. Reticulum ajaa sen discovery-announceja varten (RNS location_cmd).",
"block_fast_flapping_label": "Estaa nopea flapping",
"block_fast_flapping_hint": "Ohittaa asiakkaat, jotka yhdistavat ja katkaisevat liian nopeasti (RNS 1.3.9 BackboneInterface-oletukset).",
"block_fast_flapping_hint": "Ohittaa asiakkaat, jotka yhdistavat ja katkaisevat liian nopeasti (RNS 1.4.0 BackboneInterface-oletukset).",
"fast_flapping_block_time_label": "Estoaika (minuuttia)",
"fast_flapping_threshold_label": "Kynnys (sekuntia)",
"fast_flapping_grace_label": "Armo-flapit"

View file

@ -501,7 +501,13 @@
"remote_management_allowed": "Remote management allow-list",
"remote_management_allowed_description": "Identity hashes allowed to query this instance remotely with rnstatus/rnpath. One 32-character hex hash per line.",
"remote_management_allowed_placeholder": "9fb6d773498fb3feda407ed8ef2c3229",
"remote_management_allowed_save": "Save allow-list"
"remote_management_allowed_save": "Save allow-list",
"propagation_sequential_validation": "Sequential PN Stamp Validation",
"propagation_sequential_validation_description": "Validate inbound propagation sync stamp batches one at a time. Other peers receive a throttle response while a batch is validating. Disable only on fast hosts.",
"propagation_static_peers_bypass_sequential": "Static Peers Bypass Sequential Validation",
"propagation_static_peers_bypass_sequential_description": "Allow configured static peers to sync immediately even when another stamp batch is already validating.",
"propagation_max_inbound_syncs": "Max Concurrent Inbound Syncs",
"propagation_max_inbound_syncs_description": "How many concurrent inbound propagation sync transfers to accept. Extra offers are throttled. Range: 1-64. Default: 3."
},
"common": {
"open": "Ouvrir",
@ -1334,7 +1340,7 @@
"location_cmd_placeholder": "/path/to/gps-script",
"location_cmd_hint": "Chemin absolu vers un exécutable qui affiche latitude,longitude,height. Reticulum l'exécute pour les announces de discovery (RNS location_cmd).",
"block_fast_flapping_label": "Bloquer le flapping rapide",
"block_fast_flapping_hint": "Ignore les clients qui se connectent et se déconnectent trop rapidement (défauts BackboneInterface RNS 1.3.9).",
"block_fast_flapping_hint": "Ignore les clients qui se connectent et se déconnectent trop rapidement (défauts BackboneInterface RNS 1.4.0).",
"fast_flapping_block_time_label": "Durée de blocage (minutes)",
"fast_flapping_threshold_label": "Seuil (secondes)",
"fast_flapping_grace_label": "Flaps de grace"

View file

@ -501,7 +501,13 @@
"remote_management_allowed": "Remote management allow-list",
"remote_management_allowed_description": "Identity hashes allowed to query this instance remotely with rnstatus/rnpath. One 32-character hex hash per line.",
"remote_management_allowed_placeholder": "9fb6d773498fb3feda407ed8ef2c3229",
"remote_management_allowed_save": "Save allow-list"
"remote_management_allowed_save": "Save allow-list",
"propagation_sequential_validation": "Sequential PN Stamp Validation",
"propagation_sequential_validation_description": "Validate inbound propagation sync stamp batches one at a time. Other peers receive a throttle response while a batch is validating. Disable only on fast hosts.",
"propagation_static_peers_bypass_sequential": "Static Peers Bypass Sequential Validation",
"propagation_static_peers_bypass_sequential_description": "Allow configured static peers to sync immediately even when another stamp batch is already validating.",
"propagation_max_inbound_syncs": "Max Concurrent Inbound Syncs",
"propagation_max_inbound_syncs_description": "How many concurrent inbound propagation sync transfers to accept. Extra offers are throttled. Range: 1-64. Default: 3."
},
"common": {
"open": "Apri",
@ -1386,7 +1392,7 @@
"location_cmd_placeholder": "/path/to/gps-script",
"location_cmd_hint": "Percorso assoluto a un eseguibile che stampa latitude,longitude,height. Reticulum lo esegue per gli announce di discovery (RNS location_cmd).",
"block_fast_flapping_label": "Blocca client con flapping rapido",
"block_fast_flapping_hint": "Ignora client che si connettono e disconnettono troppo in fretta (default BackboneInterface RNS 1.3.9).",
"block_fast_flapping_hint": "Ignora client che si connettono e disconnettono troppo in fretta (default BackboneInterface RNS 1.4.0).",
"fast_flapping_block_time_label": "Tempo di blocco (minuti)",
"fast_flapping_threshold_label": "Soglia (secondi)",
"fast_flapping_grace_label": "Flap di grazia"

View file

@ -501,7 +501,13 @@
"remote_management_allowed": "Remote management allow-list",
"remote_management_allowed_description": "Identity hashes allowed to query this instance remotely with rnstatus/rnpath. One 32-character hex hash per line.",
"remote_management_allowed_placeholder": "9fb6d773498fb3feda407ed8ef2c3229",
"remote_management_allowed_save": "Save allow-list"
"remote_management_allowed_save": "Save allow-list",
"propagation_sequential_validation": "Sequential PN Stamp Validation",
"propagation_sequential_validation_description": "Validate inbound propagation sync stamp batches one at a time. Other peers receive a throttle response while a batch is validating. Disable only on fast hosts.",
"propagation_static_peers_bypass_sequential": "Static Peers Bypass Sequential Validation",
"propagation_static_peers_bypass_sequential_description": "Allow configured static peers to sync immediately even when another stamp batch is already validating.",
"propagation_max_inbound_syncs": "Max Concurrent Inbound Syncs",
"propagation_max_inbound_syncs_description": "How many concurrent inbound propagation sync transfers to accept. Extra offers are throttled. Range: 1-64. Default: 3."
},
"common": {
"open": "Openen",
@ -1334,7 +1340,7 @@
"location_cmd_placeholder": "/path/to/gps-script",
"location_cmd_hint": "Absoluut pad naar een uitvoerbaar bestand dat latitude,longitude,height print. Reticulum voert dit uit voor discovery-announces (RNS location_cmd).",
"block_fast_flapping_label": "Snelle flapping-clients blokkeren",
"block_fast_flapping_hint": "Negeert clients die te snel verbinden en verbreken (RNS 1.3.9 BackboneInterface-standaarden).",
"block_fast_flapping_hint": "Negeert clients die te snel verbinden en verbreken (RNS 1.4.0 BackboneInterface-standaarden).",
"fast_flapping_block_time_label": "Blokkeertijd (minuten)",
"fast_flapping_threshold_label": "Drempel (seconden)",
"fast_flapping_grace_label": "Grace-flaps"

View file

@ -501,7 +501,13 @@
"remote_management_allowed": "Remote management allow-list",
"remote_management_allowed_description": "Identity hashes allowed to query this instance remotely with rnstatus/rnpath. One 32-character hex hash per line.",
"remote_management_allowed_placeholder": "9fb6d773498fb3feda407ed8ef2c3229",
"remote_management_allowed_save": "Save allow-list"
"remote_management_allowed_save": "Save allow-list",
"propagation_sequential_validation": "Sequential PN Stamp Validation",
"propagation_sequential_validation_description": "Validate inbound propagation sync stamp batches one at a time. Other peers receive a throttle response while a batch is validating. Disable only on fast hosts.",
"propagation_static_peers_bypass_sequential": "Static Peers Bypass Sequential Validation",
"propagation_static_peers_bypass_sequential_description": "Allow configured static peers to sync immediately even when another stamp batch is already validating.",
"propagation_max_inbound_syncs": "Max Concurrent Inbound Syncs",
"propagation_max_inbound_syncs_description": "How many concurrent inbound propagation sync transfers to accept. Extra offers are throttled. Range: 1-64. Default: 3."
},
"common": {
"open": "Открыть",
@ -1386,7 +1392,7 @@
"location_cmd_placeholder": "/path/to/gps-script",
"location_cmd_hint": "Абсолютный путь к исполняемому файлу, печатающему latitude,longitude,height. Reticulum запускает его для discovery-announces (RNS location_cmd).",
"block_fast_flapping_label": "Блокировать быстрый flapping",
"block_fast_flapping_hint": "Игнорирует клиентов, которые слишком быстро подключаются и отключаются (стандарты BackboneInterface RNS 1.3.9).",
"block_fast_flapping_hint": "Игнорирует клиентов, которые слишком быстро подключаются и отключаются (стандарты BackboneInterface RNS 1.4.0).",
"fast_flapping_block_time_label": "Время блокировки (минуты)",
"fast_flapping_threshold_label": "Порог (секунды)",
"fast_flapping_grace_label": "Льготные flaps"

View file

@ -501,7 +501,13 @@
"remote_management_allowed": "Remote management allow-list",
"remote_management_allowed_description": "Identity hashes allowed to query this instance remotely with rnstatus/rnpath. One 32-character hex hash per line.",
"remote_management_allowed_placeholder": "9fb6d773498fb3feda407ed8ef2c3229",
"remote_management_allowed_save": "Save allow-list"
"remote_management_allowed_save": "Save allow-list",
"propagation_sequential_validation": "Sequential PN Stamp Validation",
"propagation_sequential_validation_description": "Validate inbound propagation sync stamp batches one at a time. Other peers receive a throttle response while a batch is validating. Disable only on fast hosts.",
"propagation_static_peers_bypass_sequential": "Static Peers Bypass Sequential Validation",
"propagation_static_peers_bypass_sequential_description": "Allow configured static peers to sync immediately even when another stamp batch is already validating.",
"propagation_max_inbound_syncs": "Max Concurrent Inbound Syncs",
"propagation_max_inbound_syncs_description": "How many concurrent inbound propagation sync transfers to accept. Extra offers are throttled. Range: 1-64. Default: 3."
},
"common": {
"open": "打开",
@ -1334,7 +1340,7 @@
"location_cmd_placeholder": "/path/to/gps-script",
"location_cmd_hint": "可执行文件的绝对路径,输出 latitude,longitude,height。Reticulum 会在发现通告时运行它RNS location_cmd。",
"block_fast_flapping_label": "阻止快速抖动客户端",
"block_fast_flapping_hint": "忽略连接与断开过快的客户端RNS 1.3.9 BackboneInterface 默认值)。",
"block_fast_flapping_hint": "忽略连接与断开过快的客户端RNS 1.4.0 BackboneInterface 默认值)。",
"fast_flapping_block_time_label": "封锁时间(分钟)",
"fast_flapping_threshold_label": "阈值(秒)",
"fast_flapping_grace_label": "宽限抖动次数"

View file

@ -21,10 +21,10 @@ classifiers = [
]
dependencies = [
"aiohttp>=3.14.1",
"lxmf>=1.0.1",
"lxmf>=1.1.0",
"psutil>=7.2.2",
"pyserial>=3.5",
"rns>=1.3.9",
"rns>=1.4.0",
"websockets>=16.0",
"bcrypt>=5.0.0,<6.0.0",
"aiohttp-session>=2.12.1,<3.0.0",

View file

@ -542,9 +542,9 @@ idna==3.18 \
--hash=sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2 \
--hash=sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848
# via yarl
lxmf==1.0.1 \
--hash=sha256:96fbf9a02dcd311fd31129724c08faa5f42a0e4e1bab63bea7552fbc983f4fb8 \
--hash=sha256:d12ead448296cbd09203462d8dc96a26eb71ef168b4197b133a970d483a607ea
lxmf==1.1.0 \
--hash=sha256:187e5cba257163ba1cffa1b49a33dc8893cf3b982397ccd77461ceaa5cd54af1 \
--hash=sha256:3836550364e059aa94eb955f9a862d20287643518d7a0b2a167b3b58c1d07cbd
# via
# lxst
# reticulum-meshchatx
@ -985,9 +985,9 @@ pyserial==3.5 \
# via
# reticulum-meshchatx
# rns
rns==1.3.9 \
--hash=sha256:ceeac4799dda72b48a34b1e36de538e64009cba5b7b28d086918c0120ca8bfa4 \
--hash=sha256:fd3745c1aba4dcbf8833fa87a3157ec9900745a32d6d96ba330d623a98932a15
rns==1.4.0 \
--hash=sha256:a88c6ae15c289867b2d8ee8f6ba4363f4dc728283f892b0790dba226f7dddcae \
--hash=sha256:fa9e76d0a78bf253eae66137e6bbdc65f470db3950a95b034ce32ca845ff0e44
# via
# lxmf
# lxst

View file

@ -30,7 +30,7 @@ Options:
--numpy-version V NumPy version used during pycodec2 build (default: 1.26.2)
--lxst-version V LXST wheel version for metadata patch (default: 0.4.8)
--bleak-version V bleak pure-python wheel version to vendor (default: 3.0.2)
--rns-version V rns wheel version to patch (default: 1.3.9)
--rns-version V rns wheel version to patch (default: 1.4.0)
--no-lxst-patch Skip LXST metadata patch
--no-rns-patch Skip RNS Android RNodeInterface patch
--only-recipes LIST Comma-separated recipe directory names under
@ -60,7 +60,7 @@ LIBCODEC2_VERSION="1.2.0"
NUMPY_VERSION="1.26.2"
LXST_VERSION="0.4.8"
BLEAK_VERSION="3.0.2"
RNS_VERSION="1.3.9"
RNS_VERSION="1.4.0"
PATCH_LXST="1"
PATCH_RNS="1"
ONLY_RECIPES=""

View file

@ -227,5 +227,31 @@ def test_lxmf_router_creation_smoke():
identity = RNS.Identity()
with tempfile.TemporaryDirectory() as tmpdir:
with patch("LXMF.LXMRouter") as mock_router:
create_lxmf_router(identity, tmpdir)
mock_router.assert_called()
create_lxmf_router(
identity,
tmpdir,
propagation_cost=16,
max_inbound_syncs=2,
sequential_validation=True,
static_sequential=False,
)
mock_router.assert_called_once()
kwargs = mock_router.call_args.kwargs
assert kwargs["propagation_cost"] == 16
assert kwargs["max_inbound_syncs"] == 2
assert kwargs["sequential_validation"] is True
assert kwargs["static_sequential"] is False
def test_lxmf_router_creation_accepts_new_defaults():
"""LXMF 1.1.0 LXMRouter accepts sequential/inbound sync kwargs."""
import inspect
import LXMF
params = inspect.signature(LXMF.LXMRouter.__init__).parameters
assert "max_inbound_syncs" in params
assert "sequential_validation" in params
assert "static_sequential" in params
assert hasattr(LXMF.LXMRouter, "inbound_count")
assert hasattr(LXMF.LXMRouter, "cancel_all_inbound")

16
uv.lock generated
View file

@ -1081,14 +1081,14 @@ wheels = [
[[package]]
name = "lxmf"
version = "1.0.1"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "rns" },
]
sdist = { url = "https://files.pythonhosted.org/packages/33/2b/b1df90fce728db3a0659da44bfa8f1337670ccc6a937b7a988c119a4b05c/lxmf-1.0.1.tar.gz", hash = "sha256:d12ead448296cbd09203462d8dc96a26eb71ef168b4197b133a970d483a607ea", size = 71315, upload-time = "2026-06-01T12:26:07.874Z" }
sdist = { url = "https://files.pythonhosted.org/packages/8b/69/2ba34eee87c8fa91605d5e35ecb33a3a6e65f43085c7d3e369b724e05e03/lxmf-1.1.0.tar.gz", hash = "sha256:187e5cba257163ba1cffa1b49a33dc8893cf3b982397ccd77461ceaa5cd54af1", size = 74933, upload-time = "2026-07-20T16:58:07.498Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/86/ff/4374bc1ed3a6b5e3d4c9c9e40793b7ecf77998d3a41eae66501155aac8f3/lxmf-1.0.1-py3-none-any.whl", hash = "sha256:96fbf9a02dcd311fd31129724c08faa5f42a0e4e1bab63bea7552fbc983f4fb8", size = 64470, upload-time = "2026-06-01T12:26:06.044Z" },
{ url = "https://files.pythonhosted.org/packages/07/b4/5ad93f8920ced6d233ba2ff9ec6677f71db7412434da508478d49bb40fe2/lxmf-1.1.0-py3-none-any.whl", hash = "sha256:3836550364e059aa94eb955f9a862d20287643518d7a0b2a167b3b58c1d07cbd", size = 67948, upload-time = "2026-07-20T16:58:05.97Z" },
]
[[package]]
@ -1951,14 +1951,14 @@ requires-dist = [
{ name = "bleak", specifier = ">=3.0.2,<4.0" },
{ name = "cbor2", specifier = ">=6.1.1" },
{ name = "cryptography", specifier = ">=49.0.0,<50.0.0" },
{ name = "lxmf", specifier = ">=1.0.1" },
{ name = "lxmf", specifier = ">=1.1.0" },
{ name = "lxst", specifier = ">=0.5.0" },
{ name = "miniaudio", specifier = ">=1.70,<2.0" },
{ name = "ply", specifier = ">=3.11,<4.0" },
{ name = "psutil", specifier = ">=7.2.2" },
{ name = "pycparser", specifier = ">=3.0" },
{ name = "pyserial", specifier = ">=3.5" },
{ name = "rns", specifier = ">=1.3.9" },
{ name = "rns", specifier = ">=1.4.0" },
{ name = "wasmtime", specifier = ">=28.0.0" },
{ name = "websockets", specifier = ">=16.0" },
]
@ -1993,15 +1993,15 @@ wheels = [
[[package]]
name = "rns"
version = "1.3.9"
version = "1.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cryptography" },
{ name = "pyserial" },
]
sdist = { url = "https://files.pythonhosted.org/packages/66/52/28a6db2911f32feeffd9800a88929762321be14700a67a3f5d289ad9ecc0/rns-1.3.9.tar.gz", hash = "sha256:fd3745c1aba4dcbf8833fa87a3157ec9900745a32d6d96ba330d623a98932a15", size = 519005, upload-time = "2026-07-19T00:33:44.851Z" }
sdist = { url = "https://files.pythonhosted.org/packages/3c/45/5aaed5107e0d3f434aada725224e1c54af96555959da312c01e12228c1de/rns-1.4.0.tar.gz", hash = "sha256:fa9e76d0a78bf253eae66137e6bbdc65f470db3950a95b034ce32ca845ff0e44", size = 519727, upload-time = "2026-07-20T17:13:03.624Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ac/ef/b9b3bccc082a70ebdf862147c83f2dc09ecce9fceed034af610fbc50af66/rns-1.3.9-py3-none-any.whl", hash = "sha256:ceeac4799dda72b48a34b1e36de538e64009cba5b7b28d086918c0120ca8bfa4", size = 606018, upload-time = "2026-07-19T00:33:39.354Z" },
{ url = "https://files.pythonhosted.org/packages/56/4e/1a50351fba424b891a61e5f0fed9f3f631d7a034ae5edcc531d79562467c/rns-1.4.0-py3-none-any.whl", hash = "sha256:a88c6ae15c289867b2d8ee8f6ba4363f4dc728283f892b0790dba226f7dddcae", size = 606736, upload-time = "2026-07-20T17:12:58.403Z" },
]
[[package]]

View file

@ -19,7 +19,7 @@ Easily create LXMF bots for the Reticulum Network with this extensible framework
## Installation
**Requirements:** Python 3.11+, [RNS](https://pypi.org/project/rns/) 1.3.5+, [LXMF](https://pypi.org/project/lxmf/) 1.0.1+ (installed automatically with LXMFy).
**Requirements:** Python 3.11+, [RNS](https://pypi.org/project/rns/) 1.4.0+, [LXMF](https://pypi.org/project/lxmf/) 1.1.0+ (installed automatically with LXMFy).
There are many ways to install LXMFy, you pick:

View file

@ -15,8 +15,8 @@ classifiers = [
"Operating System :: OS Independent",
]
dependencies = [
"lxmf>=1.0.1",
"rns>=1.3.5"
"lxmf>=1.1.0",
"rns>=1.4.0"
]
[project.urls]

View file

@ -51,7 +51,7 @@ pip install -e ".[dev]"
# or: make install-user
```
Requires rns>=1.3.9. Current package version: **1.0.0**.
Requires rns>=1.4.0. Current package version: **1.0.0**.
```bash
rns-filesync -v

View file

@ -2,7 +2,7 @@
English README is the source of truth: [README.md](README.md).
Кратко: библиотека и CLI для синхронизации каталогов через Reticulum (`rns>=1.3.9`).
Кратко: библиотека и CLI для синхронизации каталогов через Reticulum (`rns>=1.4.0`).
Конфиг и ACL как у rngit: `~/.rns_filesync/config`, правила `r:hash` / `w:all`, файлы `.allowed`.
```bash

View file

@ -10,7 +10,7 @@ readme = "README.md"
license = { text = "BSD-2-Clause" }
requires-python = ">=3.10"
dependencies = [
"rns>=1.3.9",
"rns>=1.4.0",
]
[project.optional-dependencies]

View file

@ -1 +1 @@
rns>=1.3.9
rns>=1.4.0